Unusual behavior of a Ruby hash

Rubysters,

I had the following code working nicely:

(a) http://pastie.org/private/8qn4f36mfeppbobm7b2wsa

At the beginning of every iteration, I expect “essential_params” hash
(in
line 8) to look like this: essential_params = {:val_1 => 0, :val_2 =>
[x,y,z], :val_3 =>0}. However it does repetitive work to initialize a
hash
from a database (in line 8).

So as to reduce repetitive database querying (at every iteration), I
have
just moved hash initialization to (line 7) outside a loop:

(b) http://pastie.org/private/iudfe0q2s41vszxby6vva

Now the code acts wierd: At every iteration, my “essential_params” hash
contains data accumulated from the previous iterations. For example, at
the
nth iteration, essential_params may look like this: {:val_1 => 25,
:val_2 =>
[x,y,z], :val_3 =>24} which are results from (n-1)th iteration (instead
of
the default values: {:val_1 => 0, :val_2 => [x,y,z], :val_3 =>0}).

Does anyone have a better explanation about this? Are Ruby hashed
assigned
by value or by reference? (Specifically, why is the pre-populated hash
at
line 7 in (b) getting values from lines 22 and 23 ? )

Kind regards,


Edmond

On Tue, Oct 25, 2011 at 3:50 PM, Edmond K.
[email protected] wrote:

Does anyone have a better explanation about this? Are Ruby hashed assigned
by value or by reference? (Specifically, why is the pre-populated hash at
line 7 in (b) getting values from lines 22 and 23 ? )

Ruby only has object references (there are some internal optimizations
but for now that is adequate). You have only one object which you
reuse during every iteration. Hence during each iteration you will
see modifications from previous iterations. A simplified example of
what happens is this:

irb(main):008:0> a = []
=> []
irb(main):009:0> 4.times do |i|
irb(main):010:1* printf “%d %-10s %p\n”, i, “before”, a
irb(main):011:1> a << i
irb(main):012:1> printf “%d %-10s %p\n”, i, “after”, a
irb(main):013:1> end
0 before []
0 after [0]
1 before [0]
1 after [0, 1]
2 before [0, 1]
2 after [0, 1, 2]
3 before [0, 1, 2]
3 after [0, 1, 2, 3]
=> 4
irb(main):014:0> a
=> [0, 1, 2, 3]

You can also look at #object_id to see it’s really the same object all
the time.

Kind regards

robert

On Tue, Oct 25, 2011 at 3:50 PM, Edmond K.
[email protected] wrote:

Well, first of all, it seems that essential_params has a key
:params_hash, so I guess it’s something like:

essential_params = {:params_hash => {:val_1 => 0, :val_2 => [x,y,z],
:val_3 =>0}}

If this is the case, then you are overwriting the values of the
params_hash in lines 22 and 23. When you do this:

new_data[:params_hash] = essential_params[:params_hash]

you are referencing one hash (essential_params[:params_hash]) from the
other hash. So when, after that, you do this:

new_data[:params_hash][:val_1] = 25

you are changing the original hash object.

Does anyone have a better explanation about this? Are Ruby hashed assigned
by value or by reference? (Specifically, why is the pre-populated hash at
line 7 in (b) getting values from lines 22 and 23 ? )

Everything in Ruby is an object. When you assign to a variable, you
are making the variable reference the object. When you call a method
through the variable, you are sending that message to the object. An
object can be referenced by several variables. Check this:

ruby-1.8.7-p334 :001 > a = {}
=> {}
ruby-1.8.7-p334 :002 > a.object_id
=> -611696338
ruby-1.8.7-p334 :003 > b = a
=> {}
ruby-1.8.7-p334 :004 > b.object_id
=> -611696338

Both a and b are variables that point to the same object. There’s only
one hash. So when you change the hash, be it through a or through b
you are changing the same object:

ruby-1.8.7-p334 :005 > b[:val] = 3
=> 3
ruby-1.8.7-p334 :006 > a
=> {:val=>3}

This is basically what you are seeing in your code (a is
essential_params[:params_hash] and b is new_data[:params_hash]).
You might want to create a copy of the hash so that the original hash
is not modified. You can do that with Object#dup.

ruby-1.8.7-p334 :007 > a = {:val => 3}
=> {:val=>3}
ruby-1.8.7-p334 :008 > b = a.dup
=> {:val=>3}
ruby-1.8.7-p334 :009 > b[:val] = 5
=> 5
ruby-1.8.7-p334 :010 > a
=> {:val=>3}
ruby-1.8.7-p334 :011 > b
=> {:val=>5}

So, modify your line 15 to this:

  new_data[:params_hash]  = essential_params[:params_hash].dup

and let us know if it worked for you.

Hope this helps,

Jesus.

In fact, Jesus should be the program saviour (i’m kidding).

2011/10/25 Jess Gabriel y Galn [email protected]

Well, first of all, it seems that essential_params has a key :params_hash,
so I guess it’s something like:

essential_params = {:params_hash => {:val_1 => 0, :val_2 => [x,y,z],

:val_3 =>0}}

I agree. In fact, :params_hash in essential_params is an array of
hashes.

So, modify your line 15 to this:

 new_data[:params_hash]  = essential_params[:params_hash].dup

and let us know if it worked for you.

Hope this helps,

I should say, it took me this long to get all this was getting messed
up. As
it was rightly put by Jesus, I needed to duplicate
essential_params[:params_hash]. Since essential_params[:params_hash] is
also
an array of hashes, at every iteration I have to duplicate each hash
element
of the “essential_params[:params_hash]” array. Though I do not love the
repetitive “semi-manual” duplication, but it is much more efficient than
the
repetitive database querying.

In due course, I have learned two good methods and their differences:
*clone

Thanks folks!!


Edmond

2011/10/25 Jess Gabriel y Galn [email protected]

On Thu, Oct 27, 2011 at 10:53 AM, Edmond K.
[email protected] wrote:

:val_3 =>0}}

Hope this helps,
In due course, I have learned two good methods and their differences: *clone

Thanks folks!!

If you need to deep copy a nested object structure you can use this
idiom:

x = [[…]] # whatever, a nested set of arrays, etc
y = Marshal.load(Marshal.dump(x))

Take into account, though, that there are things that Marshal.dump
will not dump, like default_procs of hashes, for example.
But if you just have regular arrays and hashes it should be fine.

Jesus.

2011/10/27 Jess Gabriel y Galn [email protected]

If you need to deep copy a nested object structure you can use this idiom:

x = [[…]] # whatever, a nested set of arrays, etc
y = Marshal.load(Marshal.dump(x))

That was a program saver!! It has worked the “magickie” like Superman!!
(Indeed, One key to career growth is never to stop learning (
http://tek.io/n4EDNP))


Edmond
*
One key to career growth is never to stop learning
(http://tek.io/n4EDNP)*

2011/10/27 Jess Gabriel y Galn [email protected]