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*

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 Kachale <kachaleedmond@gmail.com> wrote:

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 ? )

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

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}).

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.

···

On Tue, Oct 25, 2011 at 3:50 PM, Edmond Kachale <kachaleedmond@gmail.com> wrote:

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

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
* and *dup* (
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/6326\).

Thanks folks!!

···

2011/10/25 Jesús Gabriel y Galán <jgabrielygalan@gmail.com>

---
*Edmond*

2011/10/25 Jesús Gabriel y Galán <jgabrielygalan@gmail.com>

On Tue, Oct 25, 2011 at 3:50 PM, Edmond Kachale <kachaleedmond@gmail.com> > wrote:
> 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}).
>

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.

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.

···

On Thu, Oct 27, 2011 at 10:53 AM, Edmond Kachale <kachaleedmond@gmail.com> wrote:

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

2011/10/25 Jesús Gabriel y Galán <jgabrielygalan@gmail.com>

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
* and *dup* (
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/6326\).

Thanks folks!!

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\))

···

2011/10/27 Jesús Gabriel y Galán <jgabrielygalan@gmail.com>

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))

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

2011/10/27 Jesús Gabriel y Galán <jgabrielygalan@gmail.com>

On Thu, Oct 27, 2011 at 10:53 AM, Edmond Kachale > <kachaleedmond@gmail.com> wrote:
> In fact, Jesus should be the program saviour (i'm kidding).
>
> 2011/10/25 Jesús Gabriel y Galán <jgabrielygalan@gmail.com>
>
>> 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
> * and *dup* (
> http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/6326\).
>
> 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.