Narfanator
Narfanator

Reputation: 5803

Change values of variables in array

I have an array made from variables, and I want to perform the same operation on each, and store the result in the original variable:

(one, two, three) = [1, 2, 3]

[one, two, three].map!{|e| e += 1}
# => [2, 3, 4] 

# But:
[one, two, three]
# => [1, 2, 3]

# You have to:

(one, two, three) = [one, two, three].map{|e| e += 1}
# => [2, 3, 4] 

[one, two, three]
# => [2, 3, 4]

This doesn't seem like the "right way" to do it, but I'm not managing to find that "right way". I also have some vague ideas as to what's going on, but I'm not too sure, so an explanation would be appreciated.


My actual use case is that I've got named parameters and I'm e = File.new(e) if e.is_a? String

Upvotes: 2

Views: 5975

Answers (3)

Whit Kemmey
Whit Kemmey

Reputation: 2230

Try this:

a = [one, two, three]
a.map!{|e| e += 1}

The problem is that [one, two, three] is not a variable that stores an array, it's a brand new array each time you write it. Once you set a = [one, two, three], you have a variable that stores that value which you can then operate on.


Darshan pointed out in the comments that this doesn't actually modify the values of the original variables one, two, and three, and he's correct. But there is a way to do it:

["one", "two", "three"].each{ |e| eval "#{e} += 1" }

But that's pretty ugly, relies on using strings in the array instead of the actual variables, and is probably a lot worse than what you already came up with:

(one, two, three) = [one, two, three].map{|e| e += 1}

Upvotes: 2

Aaron K
Aaron K

Reputation: 6961

Numbers (such as Fixnum) in Ruby are immutable. You cannot change the underlying value.

Once you assign one = 1, it is not possible to change the value of one without a new assignment. When you do one += 1. You are actually assigning the new value 2 to the variable one; it's a whole new object.

You can see this more clearly by looking at the object_id (a.k.a. __id__):

one = 1
1.object_id     # => 2
one.object_id   # => 2
one += 1
one.object_id   # => 5
2.object_id     # => 5

Now in your Array#map! statement, you're not actually changing the one object. A reference to this object is stored in the array; not the actual variable. When you enumerate with map!, the object returned by the block is then stored in the internal reference location at the same position. Think of the first pass over map! similar to the following:

one = 1
one.object_id     # => 2

arr = [one]

arr[0].object_id  # => 2

arr[0] += 1   # You are re-setting the object at index 0
              # not changing the original `one`'s value

arr[0]            # => 2
arr[0].object_id  # => 5

one               # => 1
one.object_id     # => 2

Since these Fixnum objects are immutable, there is no way to change their value. This is why you have to de-reference the result of your map back to the original values:

(one, two, three) = [1, 2, 3]
one.object_id      # => 3
two.object_id      # => 5
three.object_id    # => 7

(one, two, three) = [one, two, three].map{|e| e += 1}
one.object_id      # => 5
two.object_id      # => 7
three.object_id    # => 9

Upvotes: 4

Darshan Rivka Whittle
Darshan Rivka Whittle

Reputation: 34031

If you really want to change the value of variables that refer to fixnums, then what you're doing is the best you can do in Ruby. That said, you might be better off not storing them as three separate variables. Rather than having one, two, and three, you can have a[0] through a[2] and pass a around, or h[:one] through h[:three] and pass h around.

a = [1, 2, 3]
a.map!{|e| e += 1}
a # => [2, 3, 4]

h = {:one=>1, :two=>2, :three=>3}
h.each_key{|k| h[k] += 1}
h # => {:one=>2, :two=>3, :three=>4}

The second option, using a Hash, is probably closer to what you want, because h[:some_name] is closer to using a variable name.

Upvotes: 0

Related Questions