Understanding the effect of arguments on .flatten method - ruby

The ruby doc does a great job of explaining what .flatten does without an argument. But I can't for the life of me understand what is happening when a 1 is passed to flatten
flatten(level) → new_ary
Returns a new array that is a one-dimensional flattening of self (recursively).That is, for every element that is an array, extract its elements into the new array.The optional level argument determines the level of recursion to flatten.
s = [ 1, 2, 3 ] #=> [1, 2, 3]
t = [ 4, 5, 6, [7, 8] ] #=> [4, 5, 6, [7, 8]]
a = [ s, t, 9, 10 ] #=> [[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10]
a.flatten #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
a = [ 1, 2, [3, [4, 5] ] ]
a.flatten(1) #=> [1, 2, 3, [4, 5]]
By 'recursion' does it mean that the argument directly effects the number of actions '.flatten' performs to convert the multidimensional array to a regular singular array? Terms that a super-scrub can understand would be greatly appreciated here.
Thank you.

Basically, argument to flatten means how deep should go in attempts to flatten the structure.
a = [ 1, 2, [3, [4, 5] ] ]
# where
[ 1, 2, [3, [4, 5] ] ] # level 0 (original array)
[3, [4, 5] ] # level 1
[4, 5] # level 2
So, if you pass 1, it will go only one level deep and will treat level-2 array as a single element and will not recurse into it. So,
a.flatten(1) #=> [1, 2, 3, [4, 5]]

What #Sergio Tulentsev mentioned in his answer, are correct. But one more thing I would like to add here -
if you don't supply any argument, then flattening will be happened recursively, in all nested level. That is a.flatten will give [1, 2, 3, 4, 5].
Don't think a.flatten is same as a.flatten(0). No, they work differently. If you pass nested level number as 0,1,2, then flattening will be happened as you asked the method to do. But If you don't pass any argument, the method will work, as it is designed to do, i.e it will create a single level array, by breaking all the deeper nested one also.
a = [ 1, 2, [3, [4, [5,[6]]] ] ]
# see no nested level is here, all are flattened.
a.flatten # => [1, 2, 3, 4, 5, 6]

Related

Getting different output from manual vs. programmatic arrays

I’m getting some weird results implementing cyclic permutation on the children of a multidimensional array.
When I manually define the array e.g.
arr = [
[1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5]
]
the output is different from when I obtain that same array by calling a method that builds it.
I’ve compared the manual array to the generated version and they’re exactly the same (class and values, etc).
I tried writing the same algorithm in JS and encountered the same issue.
Any idea what might be going on?
def Build_array(child_arr, n)
#Creates larger array with arr as element, n times over. For example Build_array([1,2,3], 3) returns [[1,2,3], [1,2,3], [1,2,3]]
parent_arr = Array.new(4)
0.upto(n) do |i|
parent_arr[i] = child_arr
end
return parent_arr
end
def Cylce_child(arr, steps_tocycle)
# example: Cylce_child([1, 2, 3, 4, 5], 2) returns [4, 5, 1, 2, 3]
0.upto(steps_tocycle - 1) do |i|
x = arr.pop()
arr.unshift(x)
end
return arr
end
def Permute_array(parent_array, x, y, z)
#x, y, z = number of steps to cycle each child array
parent_array[0] = Cylce_child(parent_array[0], x)
parent_array[1] = Cylce_child(parent_array[1], y)
parent_array[2] = Cylce_child(parent_array[2], z)
return parent_array
end
arr = Build_array([1, 2, 3, 4, 5], 4)
# arr = [[1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5]]
puts "#{Permute_array(arr, 1, 2, 3)}"
# Line 34: When arr = Build_array([1, 2, 3, 4, 5], 4)
# Result (WRONG):
# [[5, 1, 2, 3, 4], [5, 1, 2, 3, 4], [5, 1, 2, 3, 4], [5, 1, 2, 3, 4]]
#
# Line 5: When arr = [[1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, # 2, 3, 4, 5]]
# Result (CORRECT):
# [[5, 1, 2, 3, 4], [4, 5, 1, 2, 3], [3, 4, 5, 1, 2], [1, 2, 3, 4, 5]]
#
The problem is in the way you build the array.
This line:
parent_arr[i] = child_arr
does not put in parent_arr[i] a copy of child_arr but a reference to it.
This means your initial array contains four references to the same child array. Later on, when the code changes parent_arr[0], it changes the same array that child_arr was referring to in the build method. And that array is also parent_arr[1] and parrent_arr[2] and so on.
A simple solution to the problem is to put in parent_arr[i] a copy of child_arr:
parent_arr[i] = Array.new(child_arr)
I see where the bug was. Added the clone method to line 8 so that it now reads:
parent_arr[i] = child_arr.clone
#Old: parent_arr[i] = child_arr
Thanks Robin, for pointing me in the right direction.
This is a fairly common mistake to make in Ruby since arrays do not contain objects per-se, but object references, which are effectively pointers to a dynamically allocated object, not the object itself.
That means this code:
Array.new(4, [ ])
Will yield an array containing four identical references to the same object, that object being the second argument.
To see what happens:
Array.new(4, [ ]).map(&:object_id)
# => => [70127689565700, 70127689565700, 70127689565700, 70127689565700]
Notice four identical object IDs. All the more obvious if you call uniq on that.
To fix this you must supply a block that yields a different object each time:
Array.new(4) { [ ] }.map(&:object_id)
# => => [70127689538260, 70127689538240, 70127689538220, 70127689538200]
Now adding to one element does not impact the others.
That being said, there's a lot of issues in your code that can be resolved by employing Ruby as it was intended (e.g. more "idiomatic" code):
def build_array(child_arr, n)
# Duplicate the object given each time to avoid referencing the same thing
# N times. Each `dup` object is independent.
Array.new(4) do
child_arr.dup
end
end
def cycle_child(arr, steps_tocycle)
# Ruby has a rotate method built-in
arr.rotate(steps_tocycle)
end
# Using varargs (*args) you can just loop over how many positions were given dynamically
def permute_array(parent_array, *args)
# Zip is great for working with two arrays in parallel, they get "zippered" together.
# Also map is what you use for transforming one array into another in a 1:1 mapping
args.zip(parent_array).map do |a, p|
# Rotate each element the right number of positions
cycle_child(p, -a)
end
end
arr = build_array([1, 2, 3, 4, 5], 4)
# => [[1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5]]
puts "#{permute_array(arr, 1, 2, 3)}"
# => [[5, 1, 2, 3, 4], [4, 5, 1, 2, 3], [3, 4, 5, 1, 2]]
A lot of these methods boil down to some very simple Ruby so they're not especially useful now, but this adapts the code as directly as possible for educational purposes.

Ruby code to merge two arrays not working

nums1 = Array[1, 2, 3, 4, 5]
nums2 = Array[5, 6, 7, 8, 9]
def mergeArrays (ar1, ar2)
result = (ar1 << ar2).flatten!
require 'pp'
pp %w(result)
end
As simple as this. I am trying to merge these two arrays and display the result. I am also brand-brand new to Ruby. This is the first function I am writing in this language. Trying to learn here. Also how can I remove the duplicates?
It would help if you give example inputs and outputs so we know exactly what you want. When you use the word "merge", I think you actually just want to add the arrays together:
ar1 = [1, 2, 3]
ar2 = [3, 4, 5]
ar3 = ar1 + ar2 # => [1, 2, 3, 3, 4, 5]
Now if you want to remove duplicates, use Array#uniq:
ar4 = ar3.uniq # => [1, 2, 3, 4, 5]
There is no need to write a method to do any of this since the Ruby Array class already supports it. You should skim through the documentation of the Array class to learn more things you can do with arrays.
What do you mean 'not working'?
Similar questions have been asked here:
Array Merge (Union)
You have two options: the pipe operator (a1 | a2) or concatenate-and-uniq ((a1 + a2).uniq).
Also be careful about using <<, this will modify the original variable, concatenating ar2 onto the end of the original ar1.
nums1 = Array[1, 2, 3, 4, 5]
nums2 = Array[5, 6, 7, 8, 9]
result = (nums1<< nums2).flatten!
nums1
=> [1, 2, 3, 4, 5, 5, 6, 7, 8, 9]
nums2
=> [5, 6, 7, 8, 9]
result
=> [1, 2, 3, 4, 5, 5, 6, 7, 8, 9]
Additionally- just another Ruby tip, you do not need the destructive flatten! with ! versus the regular flatten. The regular flatten method will return a new Array, which you assign to result in your case. flatten! will flatten self in place, altering whatever Array it's called upon, rather than returning a new array.
You can merge Arrays using '+' operator and you can ignore the duplicated values using .uniq
>> nums1 = Array[1, 2, 3, 4, 5]
=> [1, 2, 3, 4, 5]
>> nums2 = Array[5, 6, 7, 8, 9]
=> [5, 6, 7, 8, 9]
>> def mergeArrays (nums1, nums2)
>> result = (nums1 + nums2).uniq
>> end
=> :mergeArrays
>> mergeArrays(nums1,nums2)
=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
nums1 = Array[1, 2, 3, 4, 5]
nums2 = Array[5, 6, 7, 8, 9]
p nums1.concat(nums2).uniq

Creating two arrays containing an original array with different values added at the end

So I am trying to build a multidimensional array by starting with a single array and splitting it into separate arrays to account for possible added values.
Example:
Original Array: [2,3]
adding either 4 or 5
New array: [[2,3,4],[2,3,5]]
I have tried the following:
array=[2,3]
array1=array<<4
array2=array<<5
array=[2,3]
array1=array<<4
array.pop
array2=array<<5
array=[2,3]
array1=array.push 4
array.pop
array2=array.push 5
The results I get are:
[[2,3,4,5],[2,3,4,5]]
[[2,3,5],[2,3,5]]
[[2,3,5],[2,3,5]]
Is there a way to alter the original array only in the new variables so that the variables don't end up equal when I combine them?
There are a number of methods on Array that are in-place modifiers, that is they don't make copies, and << is one of them.
What you might find easier is this:
array = [ 2, 3 ]
array1 = array + [ 4 ]
array2 = array + [ 5 ]
The result in this case is two independent arrays.
Another interesting way to do this is using the splat operator:
array = [2, 3]
array1 = [*array, 4]
# => [2, 3, 4]
array2 = [*array, 5]
# => [2, 3, 5]
If you have several times to add to:
array = [1, 2, 3]
say:
b = [*(4..20)]
#=> [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
you could use the method Array#product:
[array].product(b).map(&:flatten)
#=> [[1, 2, 3, 4], [1, 2, 3, 5], [1, 2, 3, 6], [1, 2, 3, 7],
# [1, 2, 3, 8], [1, 2, 3, 9], [1, 2, 3, 10], [1, 2, 3, 11],
# [1, 2, 3, 12], [1, 2, 3, 13], [1, 2, 3, 14], [1, 2, 3, 15],
# [1, 2, 3, 16], [1, 2, 3, 17], [1, 2, 3, 18], [1, 2, 3, 19],
# [1, 2, 3, 20]]

Sort Array of Arrays by length with tiebreaker

I have an Array of Arrays that I want to sort by longest length to shortest. I achieved this easily enough with a sort_by
> a = [ [1, 2, 9],
[4, 5, 6, 7],
[1, 2, 3] ]
> a.sort_by(&:length).reverse # or a.sort_by {|e| e.length}.reverse
=> [[4, 5, 6, 7], [1, 2, 3], [1, 2, 9]]
What I want, however is to have a sort of tie-breaker for lists of equal length. If two lists' lengths are equal, the list whose last entry is greater should come first. So in the above, [1, 2, 9] and [1, 2, 3] should be switched.
I don't care abouth the case where two lists have both equal length and equal last element, they can be in whatever order if that occurs. I don't know if/how I can acheive this with ruby built-in sorting.
You can still do this with sort_by, you just need to realize that Ruby arrays compare element-by-element:
ary <=> other_ary → -1, 0, +1 or nil
[...]
Each object in each array is compared (using the <=> operator).
Arrays are compared in an “element-wise” manner; the first two elements that are not equal will determine the return value for the whole comparison.
That means that you can use arrays as the sort_by key, then throw in a bit of integer negation to reverse the sort order and you get:
a.sort_by { |e| [-e.length, -e.last] }
That will give you the [[4, 5, 6, 7], [1, 2, 9], [1, 2, 3]] that you're looking for.
If you're not using numbers so the "negation to reverse the order" trick won't work, then use Shaunak's sort approach.
There you go :
a = [ [1, 2, 9],[4, 5, 6, 7],[1, 2, 3] ]
a.sort { |a, b| (b.count <=> a.count) == 0 ? (b.last <=> a.last): (b.count <=> a.count) }
That should give you:
[[4, 5, 6, 7], [1, 2, 9], [1, 2, 3]]
How this works: we pass a block to sort function, which first checks if the array length is same, if not it continues to check for last element.
You could use
a.sort_by {|i| [i.length, i.last] }.reverse
# => [[4, 5, 6, 7], [1, 2, 9], [1, 2, 3]]

How can I interweave items from two arrays?

How can I go from this:
for number in [1,2] do
puts 1+number
puts 2+number
puts 3+number
end
which will return 2,3,4 then 3,4,5 -> 2,3,4,3,4,5. This is just an example, and clearly not the real use.
Instead, I would like it to return 2,3 3,4 4,5 -> 2,3,3,4,4,5. I would like each of the puts to be iterated for each of the possible values of number; In this case 1 and 2 are the two possible values of 'number', before moving on to the next puts.
One way to do this is to create two lists, [2,3,4] and [3,4,5] and then use the zip method to combine them like [2,3,4].zip([3,4,5]) -> [2,3,3,4,4,5].
zip is good. You should also look at each_cons:
1.9.2p290 :006 > [2,3,4].each_cons(2).to_a
=> [[2, 3], [3, 4]]
1.9.2p290 :007 > [2,3,4,5,6].each_cons(2).to_a
=> [[2, 3], [3, 4], [4, 5], [5, 6]]
1.9.2p290 :008 > [2,3,4,5,6].each_cons(3).to_a
=> [[2, 3, 4], [3, 4, 5], [4, 5, 6]]
Because each_cons returns an Enumerator, you can use a block with it, as mentioned in the documentation for it, or convert it to an array using to_a like I did above. That returns the array of arrays, which can be flattened to get a single array:
[2,3,4,5].each_cons(2).to_a.flatten
=> [2, 3, 3, 4, 4, 5]
From the ri docs:
Iterates the given block for each array of consecutive elements. If no
block is given, returns an enumerator.
e.g.:
(1..10).each_cons(3) {|a| p a}
# outputs below
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
[4, 5, 6]
[5, 6, 7]
[6, 7, 8]
[7, 8, 9]
[8, 9, 10]
Maybe not the most readable code but you could use inject on the first range to create an array based on the summed up second range.
(1..3).inject([]){|m,n| (1..2).each{|i| m<<n+i }; m }
=> [2, 3, 3, 4, 4, 5]
This might be a little more readable
res=[]
(1..3).each{|r1| (1..2).each{|r2| res<<r1+r2 } }
[1, 2, 3].each { |i| [1, 2].each { |y| puts i + y } }

Resources