Getting different output from manual vs. programmatic arrays - ruby

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.

Related

comparing 2 arrays in every position

So what Im trying to accomplish is write a (shorter) condition that makes sure each element is different from the other array. This is confusing but I hope this example clears it up.
array = [1, 2, 3]
new_array = array.shuffle
until array[0] != new_array[0] &&
array[1] != new_array[1] &&
array[2] != new_array[2]
new_array = array.shuffle
end
So what Im doing is making sure that every single element/index pair does not match in the other array.
# [1, 2, 3] => [3, 1, 2] yayyyy
# [1, 2, 3] => [3, 2, 1] not what I want because the 2 didnt move
Is there a better way to do what I want to do? Ive looked up the .any? and .none? but I cant seem to figure out how to implement them. Thanks!
I would do this:
array.zip(new_array).all? { |left, right| left != right }
Here are two approaches that do not involve repeated sampling until a valid sample is obtained:
Sample from the population of valid permutations
Construct the population from which you are sampling:
array = [1, 2, 3, 4]
population = array.permutation(array.size).reject do |a|
a.zip(array).any? { |e,f| e==f }
end
#=> [[2, 1, 4, 3], [2, 3, 4, 1], [2, 4, 1, 3], [3, 1, 4, 2], [3, 4, 1, 2],
# [3, 4, 2, 1], [4, 1, 2, 3], [4, 3, 1, 2], [4, 3, 2, 1]]
Then just choose one at random:
10.times { p population.sample }
# [4, 3, 1, 2]
# [3, 4, 1, 2]
# [3, 4, 1, 2]
# [4, 3, 1, 2]
# [2, 1, 4, 3]
# [2, 1, 4, 3]
# [4, 1, 2, 3]
# [2, 1, 4, 3]
# [4, 3, 1, 2]
# [3, 4, 1, 2]
Sequentially sample for each position in the array
def sample_no_match(array)
a = array.each_index.to_a.shuffle
last_ndx = a[-1]
a.dup.map do |i|
if a.size == 2 && a[-1] == last_ndx
select = a[-1]
else
select = (a-[i]).sample
end
a.delete(select)
array[select]
end
end
10.times.each { p sample_no_match(array) }
# [2, 4, 3, 1]
# [4, 3, 1, 2]
# [2, 1, 3, 4]
# [1, 3, 4, 2]
# [1, 3, 2, 4]
# [1, 3, 2, 4]
# [1, 4, 3, 2]
# [3, 4, 2, 1]
# [1, 3, 4, 2]
# [1, 3, 4, 2]
I have been unable to prove or disprove that the second method produces a random sample. We can, however, determine relative frequencies of outcomes:
n = 500_000
h = n.times.with_object(Hash.new(0)) { |_,h| h[sample_no_match(array)] += 1 }
h.keys.each { |k| h[k] = (h[k]/(n.to_f)).round(4) }
h #=> {[1, 2, 3, 4]=>0.0418, [2, 1, 3, 4]=>0.0414, [1, 4, 2, 3]=>0.0418,
# [3, 4, 2, 1]=>0.0417, [4, 3, 2, 1]=>0.0415, [3, 1, 4, 2]=>0.0419,
# [2, 3, 1, 4]=>0.0420, [4, 2, 3, 1]=>0.0417, [3, 2, 1, 4]=>0.0413,
# [4, 2, 1, 3]=>0.0417, [2, 1, 4, 3]=>0.0419, [1, 3, 2, 4]=>0.0415,
# [1, 2, 4, 3]=>0.0418, [1, 3, 4, 2]=>0.0417, [2, 4, 1, 3]=>0.0414,
# [3, 4, 1, 2]=>0.0412, [1, 4, 3, 2]=>0.0423, [4, 1, 3, 2]=>0.0411,
# [3, 2, 4, 1]=>0.0411, [2, 4, 3, 1]=>0.0418, [3, 1, 2, 4]=>0.0419,
# [4, 3, 1, 2]=>0.0412, [4, 1, 2, 3]=>0.0421, [2, 3, 4, 1]=>0.0421}
avg = (h.values.reduce(:+)/h.size.to_f).round(4)
#=> 0.0417
mn, mx = h.values.minmax
#=> [0.0411, 0.0423]
([avg-mn,mx-avg].max/avg).round(6)
#=> 0.014388
which means that the maximum deviation from the average was only 1.4% percent of the average.
This suggests that the second method is a reasonable way of producing pseudo-random samples.
Initially, the first line of this method was:
a = array.each_index.to_a
By looking at the frequency distribution for outcomes, however, it was clear that that method did not produce a pseudo-random sample; hence, the need to shuffle a.
Here's one possibility:
until array.zip(new_array).reject{ |x, y| x == y }.size == array.size
new_array = array.shuffle
end
Note, though, that it will break for arrays like [1] or [1, 1, 1, 2, 3], where the number of instances of 1 exceeds half the size of the array. Recommend Array#uniq or similar, along with checking for arrays of sizes 0 or 1, depending on how trustworthy your input is!

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]]

Understanding the effect of arguments on .flatten method

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]

How to make an array containing a duplicate of an array

I couldn't find a way to build an array such as
[ [1,2,3] , [1,2,3] , [1,2,3] , [1,2,3] , [1,2,3] ]
given [1,2,3] and the number 5. I guess there are some kind of operators on arrays, such as product of mult, but none in the doc does it. Please tell me. I missed something very simple.
Array.new(5, [1, 2, 3]) or Array.new(5) { [1, 2, 3] }
Array.new(size, default_object) creates an array with an initial size, filled with the default object you specify. Keep in mind that if you mutate any of the nested arrays, you'll mutate all of them, since each element is a reference to the same object.
array = Array.new(5, [1, 2, 3])
array.first << 4
array # => [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]]
Array.new(size) { default_object } lets you create an array with separate objects.
array = Array.new(5) { [1, 2, 3] }
array.first << 4
array #=> [[1, 2, 3, 4], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3]]
Look up at the very top of the page you linked to, under the section entitled "Creating Arrays" for some more ways to create arrays.
Why not just use:
[[1, 2, 3]] * 5
# => [[1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3]]
The documentation for Array.* says:
...returns a new array built by concatenating the int copies of self.
Typically we'd want to create a repeating single-level array:
[1, 2, 3] * 2
# => [1, 2, 3, 1, 2, 3]
but there's nothing to say we can't use a sub-array like I did above.
It looks like mutating one of the subarrays mutates all of them, but that may be what someone wants.
It's like Array.new(5, [1,2,3]):
foo = [[1, 2, 3]] * 2
foo[0][0] = 4
foo # => [[4, 2, 3], [4, 2, 3]]
foo = Array.new(2, [1,2,3])
foo[0][0] = 4
foo # => [[4, 2, 3], [4, 2, 3]]
A work-around, if that's not the behavior wanted is:
foo = ([[1, 2, 3]] * 2).map { |a| [*a] }
foo[0][0] = 4
foo # => [[4, 2, 3], [1, 2, 3]]
But, at that point it's not as convenient, so I'd use the default Array.new(n) {…} behavior.

Why is my concatenation messing up when I pass in an array?

I wrote a method to permute an array (I realize Ruby comes with a permutation function, but I wanted to practice algorithms). I'm encountering a really weird error and have no idea why this is happening.
Here's my code:
def permute(arr)
permutation(arr.sort)
end
def permutation(arr, result=[])
k = nil
result += [arr]
(arr.length-1).times do |i|
if arr[i] < arr[i+1]
k = i
end
end
if k.nil?
return result
else
l = -1
arr.length.times do |i|
if arr[k] < arr[i]
l = i
end
l = nil if l == -1
end
arr[k], arr[l] = arr[l], arr[k]
arr = arr[0..k] + arr[k+1..-1].reverse
return permutation(arr, result)
end
end
The method is recursive, and on each successive call I concatenate arr to my result variable with result += [arr] because I want the method to return a nested array, e.g. [[1, 2, 3], [1, 3, 2]..]
However, when I call this method it gives me a completely weird result.
permute([1,2,3])
=> [[1, 3, 2], [2, 3, 1], [2, 3, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1]]
Why are last three results all [3,2,1]? And the other arrays aren't correct either. The really strange thing is that I can fix this by changing my concatenation to result += arr. With this change, I get the following:
permute([1,2,3])
=> [1, 2, 3, 1, 3, 2, 2, 1, 3, 2, 3, 1, 3, 1, 2, 3, 2, 1]
#I know that I can get my desired nested array like so, but that's beside the point
[1, 2, 3, 1, 3, 2, 2, 1, 3, 2, 3, 1, 3, 1, 2, 3, 2, 1].each_slice(3).to_a
=> [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
I don't get the nested arrays that I want, but the output gives me the correct permutation. Why is it working correctly now, but not which I was using result += [arr]? Is this a Ruby bug, or am I missing something here?
You're being bit by a common ruby mistake - you're modifying the original array since the 'arr' argument to permutation() is a reference to the array
Try changing:
result += [arr]
to:
result += [arr.dup]
And then presto!
[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
(Incidentally, you're still monkeying with the original 'arr' values with this solution and should probably clean that up)

Resources