Merge hashes with same key value pair inside array - ruby

How to merge hashes within array with same key, value pair? I have following array,
arry = [{"id" => 1, "val1" => 123},
{"id" => 2, "val1" => 234},
{"id" => 1, "val2" => 321},
{"id" => 1, "val3" => 445},
{"id" => 3, "val3" => 334}]
Want to get
arry = [{"id" => 1, "val1" => 123, "val2" => 321, "val3" => 445},
{"id" => 2, "val1" => 234},
{"id" => 3, "val3" => 334}]
is there ruby way to do it? Tried few ways but no success so far.

The arry you have posted is not a valid ruby array in the first place (I fixed it in my edit.)
arry.
group_by { |h| h["id"] }.
values.
map { |a| a.reduce(&:merge) }
#⇒ [{"id"=>1, "val1"=>123, "val2"=>321, "val3"=>445},
# {"id"=>2, "val1"=>234}, {"id"=>3, "val3"=>334}]
If your input might have same keys within the same "id" (like {"id" => 1, "val1" => 123}, {"id" => 1, "val1" => 456},) you are to decide how to merge them. In any case, Hash#merge with a block would be your friend there.

arry.each_with_object({}) { |g,h| h.update(g["id"]=>g) { |_,o,n| o.merge(n) } }.values
#=> [{"id"=>1, "val1"=>123, "val2"=>321, "val3"=>445},
# {"id"=>2, "val1"=>234},
# {"id"=>3, "val3"=>334}]
Note the receiver of values is:
{1=>{"id"=>1, "val1"=>123, "val2"=>321, "val3"=>445},
2=>{"id"=>2, "val1"=>234},
3=>{"id"=>3, "val3"=>334}}
This uses the form of Hash#update (aka merge) that employs the block { |_,o,n| o.merge(n) } to determine the values of keys that are present in both hashes being merged. See the doc for descriptions of the three block variables. (I've used an underscore for the first, the common key, to signify that it is not used in the block calculation.)

This should work too,
arry.group_by { |a| a['id'] }.map{|_, ar| ar.reduce(:merge)}
This would return,
[{"id"=>1, "val1"=>123, "val2"=>321, "val3"=>445}, {"id"=>2, "val1"=>234}, {"id"=>3, "val3"=>334}]

Related

How can I achieve certain values of a hash?

In the following nested hash,
hash = {a: 2, b: 4, c: {name: "John", id: 12, age: 33}}
I want to return the values that are hash:
{:name => "John", :id => 12, :age => 33}
I want to returned a hash. I thought the following code will do the job:
hash.select! {|_k, v| v.is_a?(Hash)}
# => {:c => {:name => "John", :id => 12, :age => 33}}
but I get both k/v pairs. Did I miss anything on the code? How can I achieve the return value as mentioned?
I would do something like:
hash.values.find(&Hash.method(:===))
#=> {:name=>"John", :id=>12, :age=>33}
select returns the key and value that matched. Add .values to get just the values without the keys:
hash.select! { |_k, v| v.is_a?(Hash) }.values
This will return an array of the values that were matched by select:
[{:name=>"John", :id=>12, :age=>33}]
If you know there will only ever be one result, you can get the desired value by calling first:
hash.select! { |_k, v| v.is_a?(Hash) }.values.first
{:name=>"John", :id=>12, :age=>33}

Delete element from nested hash in Ruby

I have a two dimensional hashes in Ruby.
h = { "a" => {"v1" => 0, "v2" => 1}, "c" => {"v1" => 2, "v2" => 3} }
I would like to delete those elements from the hash, where value 1 (v1) is 0, for example, so my result would be:
{ "c" => {"v1" => 2, "v2" => 3} }
I wanted to achieve this by iterating trough the hash with delete_if, but I'm not sure how to handle the nested parts with it.
Is that what you're looking for?
h.delete_if { |_, v| v['v1'].zero? }
#=> {"c" => {"v1" => 2, "v2" => 3}}
As #TomLord says, it also may be variant, when v1 can be not defined or equal to nil, in this case, it would be better to use v['v1'] == 0
You can use Hash#value? in your block to check if any of the values in the nested hashes equal 0:
hash.delete_if { |k,v| v.value? 0 } #=> { "c" => { "v1" => 2, "v2" => 3 } }

Ruby : How to sort an array of hash in a given order of a particular key

I have an array of hashes, id being one of the keys in the hashes. I want to sort the array elements according to a given order of ID values.
Suppose my array(size=5) is:
[{"id"=>1. ...}, {"id"=>4. ...}, {"id"=>9. ...}, {"id"=>2. ...}, {"id"=>7. ...}]
I want to sort the array elements such that their ids are in the following order:
[1,3,5,7,9,2,4,6,8,10]
So the expected result is:
[{'id' => 1},{'id' => 7},{'id' => 9},{'id' => 2},{'id' => 4}]
Here is a solution for any custom index:
def my_index x
# Custom code can be added here to handle items not in the index.
# Currently an error will be raised if item is not part of the index.
[1,3,5,7,9,2,4,6,8,10].index(x)
end
my_collection = [{"id"=>1}, {"id"=>4}, {"id"=>9}, {"id"=>2}, {"id"=>7}]
p my_collection.sort_by{|x| my_index x['id'] } #=> [{"id"=>1}, {"id"=>7}, {"id"=>9}, {"id"=>2}, {"id"=>4}]
Then you can format it in any way you want, maybe this is prettier:
my_index = [1,3,5,7,9,2,4,6,8,10]
my_collection.sort_by{|x| my_index.index x['id'] }
I would map the hash based on the values like so:
a = [{"id"=>1}, {"id"=>4}, {"id"=>9}, {"id"=>2}, {"id"=>7}]
[1,3,5,7,9,2,4,6,8,10].map{|x| a[a.index({"id" => x})] }.compact
#=> [{"id"=>1}, {"id"=>7}, {"id"=>9}, {"id"=>2}, {"id"=>4}]
General note on sorting. Use #sort_by method of the ruby's array class:
[{'id' => 1},{'id'=>3},{'id'=>2}].sort_by {|x|x['id'] }
# => [{"id"=>1}, {"id"=>2}, {"id"=>3}]
Or with usage #values method as a callback:
[{'id' => 1},{'id'=>3},{'id'=>2}].sort_by(&:values)
# => [{"id"=>1}, {"id"=>2}, {"id"=>3}]
or you can use more obvious version with #sort method:
[{'id' => 1},{'id'=>3},{'id'=>2}].sort {|x,y| x['id'] <=> y['id'] }
# => [{"id"=>1}, {"id"=>2}, {"id"=>3}]
For your case, to sort with extended condition use #% to split even and odd indexes:
[{'id' => 1},{'id'=>4},{'id'=>9},{'id'=>2},{'id'=>7}].sort do |x,y|
u = y['id'] % 2 <=> x['id'] % 2
u == 0 && y['id'] <=> x['id'] || u
end
# => [{"id"=>1}, {"id"=>7}, {"id"=>9}, {"id"=>2}, {"id"=>4}]
For your case, to sort with extended condition use #% to split according the index, even id value is absent in the index array:
index = [1,3,5,7,4,2,6,8,10] # swapped 2 and 4, 9 is absent
[{'id' => 1},{'id'=>4},{'id'=>9},{'id'=>2},{'id'=>7}].sort do |x,y|
!index.rindex( x[ 'id' ] ) && 1 || index.rindex( x[ 'id' ] ) <=> index.rindex( y[ 'id' ] ) || -1
end
# => [{"id"=>1}, {"id"=>7}, {"id"=>4}, {"id"=>2}, {"id"=>9}]
Why not just sort?
def doit(arr, order)
arr.sort { |h1,h2| order.index(h1['id']) <=> order.index(h2['id']) }
end
order = [1,3,5,7,9,2,4,6,8,10]
arr = [{'id' => 1}, {'id' => 4}, {'id' => 9}, {'id' => 2}, {'id' => 7}]
doit(arr, order)
#=> [{'id' => 1}, {'id' => 7}, {'id' => 9}, {'id' => 2}, {'id' => 4}]
a= [{"id"=>1}, {"id"=>4}, {"id"=>9}, {"id"=>2}, {"id"=>7}]
b=[1,3,5,7,9,2,4,6,8,10]
a.sort_by{|x| b.index (x['id'])}

Joining an array of keys to a hash with key value pairs like excel vlookup

I've got an unsorted array of keys like this:
keys = ["ccc", "ddd", "ggg", "aaa", "bbb"]
and a hash
hash = {"ddd" => 4, "aaa" => 1, "bbb" => 2, "eee" => 5, "fff" => 6}
I'd like to join these two data structures to return a hash in the original order of keys to the first keys:
{"ccc" => nil, "ddd" => 4, "ggg" => nil, "aaa" => 1, "bbb" => 2}
Items NOT in the hash (like "ggg") should return nil.
This is analogous to the "v-lookup" function in excel.
this is in ruby. Thanks!
Cryptic:
Hash[keys.zip(hash.values_at *keys)]
Or a bit longer, a bit less cryptic:
keys.map.with_object({}) {|key, memo| memo[key] = hash[key]}

What is the best way to remap a Hash in Ruby?

Is there a simple way of remapping a hash in ruby the following way:
from:
{:name => "foo", :value => "bar"}
to:
{"foo" => "bar"}
Preferably in a way that makes it simple to do this operation while iterating over an array of this type of hashes:
from:
[{:name => "foo", :value => "bar"}, {:name => "foo2", :value => "bar2"}]
to:
{"foo" => "bar", "foo2" => "bar2"}
Thanks.
arr = [ {:name=>"foo", :value=>"bar"}, {:name=>"foo2", :value=>"bar2"}]
result = {}
arr.each{|x| result[x[:name]] = x[:value]}
# result is now {"foo2"=>"bar2", "foo"=>"bar"}
A modified version of Vanson Samuel's code does the intended.
It's a one-liner, but quite a long one.
arr = [{:name=>"foo", :value=>"bar"}, {:name=>"foo2", :value=>"bar2"}]
arr.inject({}){|r,item| r.merge({item['name'] => item['value']})}
# result {"foo" => "bar", "foo2" => "bar2"}
I wouldn't say that it's prettier than Gishu's version, though.
As a general rule of thumb, if you have a hash of the form {:name => "foo", :value => "bar"}, you're usually better off with using a tuple of ["foo", "bar"].
arr = [["foo", "bar"], ["foo2", "bar2"]]
arr.inject({}) { |accu, (key, value)| accu[key] = value; accu }
I know this is old, but the neatest way to achieve this is to map the array of hashes to an array of tuples, then use Hash[] to build a hash from that, as follows:
arr = [{:name => "foo", :value => "bar"}, {:name => "foo2", :value => "bar2"}]
Hash[ array.map { |item| [ item[:name], item[:value] ] } ]
# => {"foo"=>"bar", "foo2"=>"bar2"}
a bit late but:
[{ name: "foo", value: "bar" },{name: "foo2", value: "bar2"}].map{ |k| k.values }.to_h

Resources