Zengetsu
Zengetsu

Reputation: 109

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

Upvotes: 0

Views: 52

Answers (3)

tadman
tadman

Reputation: 211560

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.

Upvotes: 1

Zengetsu
Zengetsu

Reputation: 109

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.

Upvotes: 1

axiac
axiac

Reputation: 72186

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)

Upvotes: 1

Related Questions