supercuteboy supercuteboy - 11 months ago 43
Ruby Question

How can I get my array to only be manipulated locally (within a function) in Ruby?

Why is my array globally manipulated, when I run the below ruby code? And how can I get arrays to be manipulated only within the function's scope?

a = [[1,0],[1,1]]

def hasRowsWithOnlyOnes(array) { |row|
return true if row.keep_if{|i| i != 1 } == []

puts a.to_s
puts hasRowsWithOnlyOnes(a)
puts a.to_s

$ ruby test.rb

[[1, 0], [1, 1]]
[[0], []]

I can't get it to work. I've even tried
and assign it to a new name. How does the scope work in Ruby for Arrays?
Just for reference,
$ ruby -v

ruby 2.2.1p85 (2015-02-26 revision 49769) [x86_64-linux]

Answer Source

It's not a scope problem, it's an argument passing problem

  • All variables in Ruby are references to objects.

  • When you pass an object to a method, a copy of that object's reference is made and passed to the object.

That means that the variable array in your method and the top-level variable a refer to the exact same Array. Any changes made to array will be also visible as changes to a, since both variables refer to the same object.

Your method does modify the array by calling Array#keep_if. The keep_if method modifies the array in-place.

The fix

The best fix for this is to make it so that your method does not modify the array that was passed in. This can be done pretty neatly using the Enumerable#any? and Enumerable#all? methods:

def has_a_row_with_only_ones(array)
  array.any? do |row|
    row.all? { |e| e == 1 }

This code says that the method returns true if, for any row, every element in that row is 1. These methods do not modify the array. More important, they communicate the method's intent clearly.

The poor workaround

If you want the method to act as through a copy of the array were passed to it, so that the array can be modified without that modification being visible outside the method, then you can make a deep copy of the array. As shown in this answer, you can define this method to make a deep copy:

def deep_copy(o)

Then, at the top of the method, make the deep copy:

def has_a_row_with_only_ones(array)
  array = deep_copy(array)
  # code that modifies array

This should be avoided because it's slow.