supercuteboy
supercuteboy

Reputation: 53

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)
  array.map { |row|
    return true if row.keep_if{|i| i != 1 } == []
  }
  false;
end

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

$ ruby test.rb output:

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

I can't get it to work. I've even tried .select{true} 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]

Upvotes: 3

Views: 197

Answers (2)

Wayne Conrad
Wayne Conrad

Reputation: 108129

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 }
  end
end

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)
  Marshal.load(Marshal.dump(o))
end

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
end

This should be avoided because it's slow.

Upvotes: 7

Motombo
Motombo

Reputation: 1787

Ruby is pass by value

Via sitepoint

Ruby has variables defined within different scopes, which you probably know already. I found that most tutorials describe them briefly (the variable types), but they fail to mention precisely what their scope is.

Here are the details:

Class variable (@@a_variable): Available from the class definition and any sub-classes. Not available from anywhere outside.

Instance variable (@a_variable): Available only within a specific object, across all methods in a class instance. Not available directly from class definitions.

Global variable ($a_variable): Available everywhere within your Ruby script.

Local variable (a_variable): It depends on the scope. You’ll be working with these most and thus encounter the most problems, because their scope depends on various things.

Upvotes: 0

Related Questions