views:

51

answers:

1

I am migrating my PHP code to Ruby and at some point I need to update hash elements inside of a loop. For example:

compositions.each_pair do |element,params|
  params['composition'].each_pair do |item,data| 
    data['af'] /= params['af sum']
    data['mf'] /= params['mass']
  end
end

I could make it using item indexes, but it will be ugly. Is there a nice way to link the loop variables to the corresponding hash items? In PHP I would write &$params and &$data in the corresponding loops. Or may be a better suggestion?


Update

Two tests to demonstrate the problem. The first one:

a={'a'=>1, 'b'=>2, 'c'=>3}

a.each_pair do |k,v|
  v += 1
end
p a # =>  {"a"=>1, "b"=>2, "c"=>3}

and the second

a.each_pair do |k,v|
  a[k] += 1
end
p a # =>  {"a"=>2, "b"=>3, "c"=>4}

Update2

Thanks to Mladen (see below), I have understood the difference between these two cases. However, I still have a question: how to update data item (not just its own items)? Let's say we add

data = data['af'] + data['mf']

to the inner loop.

+2  A: 

It seems that the code from the question works OK:

compositions = {1 => {'af sum' => 100.0, 'mass' => 200.0, 'composition' => {1 => {'af' => 5.0, 'mf' => 6.0}, 2  => {'af' => 7.0, 'mf' => 8.0}, 3  => {'af' => 9.0, 'mf' => 16.0}}}}
#=> {1=>{"af sum"=>100.0, "mass"=>200.0, "composition"=>{1=>{"af"=>5.0, "mf"=>6.0}, 2=>{"af"=>7.0, "mf"=>8.0}, 3=>{"af"=>9.0, "mf"=>16.0}}}}
compositions.each_pair do |element,params|
  params['composition'].each_pair do |item,data| 
    data['af'] /= params['af sum']
    data['mf'] /= params['mass']
  end
end
#=> {1=>{"af sum"=>100.0, "mass"=>200.0, "composition"=>{1=>{"af"=>0.05, "mf"=>0.03}, 2=>{"af"=>0.07, "mf"=>0.04}, 3=>{"af"=>0.09, "mf"=>0.08}}}}

But the example structure is pretty much guessed from the code, OP should post an example he is working with so we could actually make some progress.

Edit:

When you perform + method on an integer, a new object is returned as a result. That object is assigned to v in your first example, but not assigned back to the hash, so it is not preserved. In your second example, you assign it back to the hash.

However, if you work with mutating methods, you alter the very objects in-place, so then you don't need to reassign them to the hash. For example:

{1 => 'foo', 2 => 'bar'}.each{|k,v| v.swapcase!}
#=> {1=>"FOO", 2=>"BAR"}

but

{1 => 'foo', 2 => 'bar'}.each{|k,v| v = 'baz'}
#=> {1=>"foo", 2=>"bar"}

Edit 2:

I think you have problem understanding what k and v in the examples are. They are just local variables in the block, they start by referencing whatever is in the hash, but what do they reference can be changed during course of a block just like with any other variable. That change won't reflect to the hash. You need to alter the very object, not the reference to it, to actually change it.

Take a look at this, even simpler example, no hashes, blocks mumbo-jumbo:

a = 5
b = a
b += 1
a # => 5
b # => 6

It's as simple as that.

Mladen Jablanović
Hmm, now I am very confused. Could you please run the examples I added recently?
Andrey
I have expanded the answer.
Mladen Jablanović
Ok, I am still confused, but there is a good trend. It is true that I haven't run my original example assuming that it will not work, since the first simple one was not successful. Sorry about that. If I understand correctly, the method `data['mf'] /= params['mass']` is "mutating" and `data['mf'] /= 9` is not. Is it correct? How can I make the last one mutating? Like this `data['mf'] = data['mf'].divide_by(9)`?
Andrey
`data['mf'] /= params['mass']` is just a shorthand for `data['mf'] = data['mf'] / params['mass']`, so it _is_ performing the assignment you need for the value to be remembered in the hash. BTW, I added some more stuff to the answer.
Mladen Jablanović
But `data` is a local variable. So if I change the variable, the hash will remain the same. Why it changes when you run the loop? I hope it is not a joke...
Andrey
`data` references a hash element (which is, again, a hash). When you perform `data['mf'] = data['mf'] / params['mass']`, you take what is in the value field in `data` hash, under `'mf'` key, divide it, and assign it back. You didn't change `data` as a reference, you altered the object it is referencing.
Mladen Jablanović
Bingo! Now I have got it. But I still have a question: what if I want to update the whole `data` in the hash structure. How could I do that being inside of the loop?
Andrey
If by _update_ you mean "replace the value in the hash `params['composition']` by the key `item` with `something_else`", then you need to do exactly that: `params['composition'][item] = something_else`.
Mladen Jablanović
Andrey
No, but I'm kinda interested why you would need another way? BTW, I'm not familiar with PHP that much, can you post a working example in PHP?
Mladen Jablanović
Just because I'm so used to it in PHP. Mladen, have a look on the first example on http://www.php.net/manual/en/control-structures.foreach.php and more information about references on http://www.php.net/manual/en/language.references.whatdo.php . As I understand, since everything in Ruby is object, such references are harder to implement. May be as a new operator, e.g. `data !?= 123`
Andrey
A new operator couldn't help you there (BTW operators are nothing but methods in Ruby, so we Ruby people don't have to wait for someone up there to "add a new operator to the language", everyone's free to implement them). `data` is a local variable, and you can't get such thing as "a reference to a reference" in Ruby. Also, `[]` and `[]=` are in fact methods of `Hash` class, so no particular magic is happening when you call `each` and get key and value in the block. But that's another story.
Mladen Jablanović