Question: Ruby Unable to map values to array of hash, searching for alternative

Question

Ruby Unable to map values to array of hash, searching for alternative

Answers 3
Added at 2017-01-03 13:01
Tags
Question

I have an array of hashes:

data1 = [{:first => 1, :second => 'whatever'},{:first => 2, :second => 'something'}]

test_merge = {'whatever' => {:third => 1, :fourth => 1}, 'something' => {:third => 2, :fourth => 2}}

I want this output:

[{:first=>"MODIFIED", :second=>"whatever", :test_field=>"added", :third=>1, :fourth=>1, "sdsds"=>"sdsds", "2x4"=>8}, {:first=>"MODIFIED", :second=>"something", :test_field=>"added", :third=>2, :fourth=>2, "sdsds"=>"sdsds", "2x4"=>8}]

I did map and merge:

data2 = data1.map do |d|
    d[:test_field] = 'added'
    d[:first] = 'MODIFIED'
    d2 = test_merge[d[:second]]
    d = d.merge(d2)
    d['sdsds'] = 'sdsds'
    d['2x4'] = 2*4
  end

I got:

data1 #=> [{:first=>"MODIFIED", :second=>"whatever", :test_field=>"added"}, {:first=>"MODIFIED", :second=>"something", :test_field=>"added"}]
data2 #=> [8, 8]

This can be fixed if I do

data2 = data1.map do |d|
        d[:test_field] = 'added'
        d[:first] = 'MODIFIED'
        d2 = {:test_field_two => 'notadded'}
        d = d.merge(d2)
        d['sdsds'] = 'sdsds'
        d['2x4'] = 2*4
        d #or d=d
end

But why do I have to insert that d in the end (d or d=d)? It looks ugly to write d in the end. Isn't there an alternative way to do it, I want the exact output as mentioned above?

Edit: 3 Jan 2017 (Don't read this edit if you are reading the question for first time, everything is mentioned above)

I have to necessarily use merge inside the .map, because I have got another hash as follows:

test_merge = {'whatever' => {:third => 1, :fourth => 1}, 'something' => {:third => 2, :fourth => 2}}

which needs to be merged like this inside the .each (merge doesn't work in .each unfortunately so I have to use map) or .map

d = d.merge(test_merge[d[:second]])

Also '2x4' , :first and other keys are important which needs to be set/modified.

And if I need to modify d[:first] , it should get modified.

I tried .each as mentioned by steve,

data1 = [{:first => 1, :second => 'whatever'},{:first => 2, :second => 'something'}]

    test_merge = {'whatever' => {:third => 1, :fourth => 1}, 'something' => {:third => 2, :fourth => 2}}
      data1.each do |d|
        d[:test_field] = 'added'
        d[:first] = 'MODIFIED'
        d2 = test_merge[d[:second]]
        d = d.merge(d2)
        d['sdsds'] = 'sdsds'
        d['2x4'] = 2*4
      end

And this is the result I got:

[{:first=>"MODIFIED", :second=>"whatever", :test_field=>"added"}, {:first=>"MODIFIED", :second=>"something", :test_field=>"added"}]

That is not my expected output. Is there any hack to this?

Answers
nr: #1 dodano: 2017-01-03 14:01

When doing a map with a block the last executed line of the block is what's returned in the new array.

So if the last line is...

d['2x4'] = 2*4

Then it's 2*4 (8) that's returned.

Setting the last line of the block to...

d

...means that the value of the d variable (the entire hash) is returned.

You don't need to use map if you just want to change the hashes and don't need an array returned. Use each instead. (Edited to take into account Eric's observation about shallow duplication).

data2 = data1.map{|d| d.dup}
data2.each do |d|
  d[:test_field] = 'added'
  d[:test_field_two] = 'notadded'
  d['sdsds'] = 'sdsds'
  d['2x4'] = 2*4
end
nr: #2 dodano: 2017-01-03 14:01

Hash#merge returns a merged Hash without changing the original one, so you can add new |key,value| pairs and return the desired hash directly :

data2 = data1.map do |h1|
  h1.merge(:test_field     => 'added',
           :test_field_two => 'not_added',
           'sdsds'         => 'sdsds',
           '2x4'           => 2 * 4)
end

data2 is now :

[{:first=>1,
  :second=>"whatever",
  :test_field=>"added",
  :test_field_two=>"not_added",
  "sdsds"=>"sdsds",
  "2x4"=>8},
 {:first=>2,
  :second=>"something",
  :test_field=>"added",
  :test_field_two=>"not_added",
  "sdsds"=>"sdsds",
  "2x4"=>8}]

while data1 still is :

[{:first=>1, :second=>"whatever"}, {:first=>2, :second=>"something"}]
nr: #3 dodano: 2017-01-03 14:01

I think this might be better! Though I am not really sure if i understood the question correctly. Hope this helps.

data1 = [{:first => 1, :second => 'whatever'},{:first => 2, :second => 'something'}]

d2 = {:test_field_two => 'notadded', "sdsds"=>"sdsds", "2x4"=>2*4}

data2 = data1.map do |d| d.merge(d2) end

#=> [{:first=>1, :second=>"whatever", :test_field_two=>"notadded", "sdsds"=>"sdsds", "2x4"=>8}, {:first=>2, :second=>"something", :test_field_two=>"notadded", "sdsds"=>"sdsds", "2x4"=>8}]
Source Show
◀ Wstecz