Merge the value of same key names in ruby on rails - ruby-on-rails

I want to merge the ids of same name values
hashes = [{
id: 3456824,
name: 'John'
},{
id: 6578954,
name: 'Vicky'
},{
id: 987456,
name: 'John'
}]
Expected:
[{
id: [3456824,987456],
name: 'John'
},{
id: 6578954,
name: 'Vicky'
}]
how I can achieve this in ruby on rails?

Here is a one liner:
hashes = [{
id: 3456824,
name: 'John'
},{
id: 6578954,
name: 'Vicky'
},{
id: 987456,
name: 'John'
}]
result = hashes.group_by{|h| h[:name] }.map{|k, v| {id: v.map{|x| x[:id]}, name: k}}
puts result
Check this repl: https://repl.it/repls/ShadowyCornyVideogames

Here are two ways to compute the desired result.
Use the form of Hash::new that takes a block
hashes.each_with_object(Hash.new { |h,k| h[k] = [] }) do |g,h|
h[g[:name]] << g[:id]
end.map { |name,id| { id: id, name: name } }
#=> [{:id=>[3456824, 987456], :name=>"John"},
# {:id=>[6578954], :name=>"Vicky"}]
The first step of this calculation1 is
hashes.each_with_object(Hash.new { |h,k| h[k] = [] }) do |g,h|
h[g[:name]] << g[:id]
end
#=> {"John"=>[3456824, 987456], "Vicky"=>[6578954]}
If a hash is defined
h = Hash.new { |h,k| h[k] = [] }
and (possibly after having added key-value pairs) h has no key k, h[k] in
h[k] << v
causes the block { |h,k| h[k] = [] } to be executed, resulting in the key value pair k=>[] being added to h, then << v is executed, changing h[k] from [] to [k].
Notice that this returns :id=>[6578954], rather than :id=>6578954, which was asked for by the question. Having all values of :id return an array avoids the need to check if :id returns an array or integer in subsequent code that processes the return value of this operation.
If :id=>6578954, were desired, one could write
hashes.each_with_object(Hash.new { |h,k| h[k] = [] }) do |g,h|
h[g[:name]] << g[:id]
end.transform_values { |v| v.size==1 ? v.first : v }.
map { |name,id| { id: id, name: name } }
#=> [{:id=>[3456824, 987456], :name=>"John"},
# {:id=>6578954, :name=>"Vicky"}]
See Hash#transform_values.
Use the form of Hash#update (a.k.a. merge!) that employs a block to determine the values of keys that are present in both hashes being merged
arr.each_with_object({}) do |g,h|
h.update(g[:name]=>[g[:id]]) { |_,o,n| o+n }
end.map { |name,id| { id: id, name: name } }
#=> [{:id=>[3456824, 987456], :name=>"John"},
# {:id=>[6578954], :name=>"Vicky"}]
If :id=>6578954, rather than :id=>[6578954], were desired:
arr.each_with_object({}) do |g,h|
h.update(g[:name]=>g[:id]) { |_,o,n| [*o,n] }
end.map { |name,id| { id: id, name: name } }
#=> [{:id=>[3456824, 987456], :name=>"John"},
# {:id=>6578954, :name=>"Vicky"}]
Notice that here update's argument is g[:name]=>g[:id] whereas it was previously g[:name]=>[g[:id]].
The first step is as follows.
arr.each_with_object({}) do |g,h|
h.update(g[:name]=>g[:id]) { |_,o,n| [*o,n] }
end
#=> {"John"=>[3456824, 987456], "Vicky"=>6578954}
In general, one or both of these approaches can be taken when Enumerable#group_by can be used. The reverse if often true also. The choice among these methods is a matter of personal taste.
1. A variant of the first part of this calculation is hashes.each_with_object({}) { |g,h| (h[g[:name]] ||= []) << g[:id] } #=> {"John"=>[3456824, 987456], "Vicky"=>[6578954]}.

You can do it like this:
args = [{
id: 3456824,
name: 'John'
},{
id: 6578954,
name: 'Vicky'
},{
id: 987456,
name: 'John'
}]
value_pairs = args.map { |h| h.values_at(:name, :id) }
grouped_by_name = value_pairs.group_by(&:first).transform_values { |arr| arr.map(&:last) }
as_hashes = grouped_by_name.map { |name, ids| { id: ids, name: name } }

One more possible solution is:
array = [
{
id: 3456824,
name: "John"
},
{
id: 6578954,
name: "Vicky"
},
{
id: 987456,
name: "John"
}
]
grouped_by_name = array.each_with_object(Hash.new {|h,k| h[k] = [] }) do |hash, result|
result[hash[:name]] << hash[:id]
end
=> {"John"=>[3456824, 987456], "Vicky"=>[6578954]}
grouped_by_name.map do |grouped_hash|
{
id: grouped_hash.last,
name: grouped_hash.first
}
end
=> [{:id=>[3456824, 987456], :name=>"John"}, {:id=>[6578954], :name=>"Vicky"}]

Related

How to find sum on basis of type and name key in ruby? (ruby array of hashes)

How to find a sum on basis of type and name key in ruby? (ruby array of hashes)
eatables = [{type: "fruit", name: "apple", count: 2},
{type: "vegetable", name: "pumpkin", count: 3},
{type: 'fruit', name: 'apple', count: 1},
{type: "vegetable", name: "pumpkin", count: 2}]
Desired Output
[{type: "fruit", name: "apple", count: 3},
{type: "vegetable", name: "pumpkin", count: 5}]
eatables.group_by { |h| h.slice(:name, :type) }
.map { |key, grouped| key.merge(count: grouped.sum{ |h| h[:count] }) }
The first operation splits the array into groups based on the name and type.
{{:name=>"apple", :type=>"fruit"}=>[{:type=>"fruit", :name=>"apple", :count=>2}, {:type=>"fruit", :name=>"apple", :count=>1}], {:name=>"pumpkin", :type=>"vegetable"}=>[{:type=>"vegetable", :name=>"pumpkin", :count=>3}, {:type=>"vegetable", :name=>"pumpkin", :count=>2}]}
We then map across that hash and return an array of hashes with the type, name, and sum which outputs:
=> [{:name=>"apple", :type=>"fruit", :count=>3}, {:name=>"pumpkin", :type=>"vegetable", :count=>5}]
eatables.inject(Hash.new(0)) { |h, item|
h[item.slice(:type, :name)] += item[:count]
h
}.map { |k, v|
{**k, count: v}
}
This type of problem can be solved with a reduce
output = eatables.reduce({}) do |hsh, current|
if hsh.has_key?(current[:type]+current[:name])
hsh[current[:type]+current[:name]][:count] += current[:count]
else
hsh[current[:type]+current[:name]] = current
end
hsh
end.values

Merge a single array into nested array in rails

I have a list of objects something like this:
a = [
{
id: 0,
name: "ABC",
work: "ABC2"
},
{
id: 0,
name: "XYZ",
work: "XYZ2"
},
{
id: 1,
name: "ACD",
work: "ACD2"
}
]
And I want to convert it into something like this:
b = [
{
id: 0,
fields: [
{
name: "ABC",
work: "ABC2"
},
{
name: "XYZ",
work: "XYZ2"
}
]
},
{
id: 1,
fields: [
{
name: "ACD",
work: "ACD2"
}
]
}
]
The idea is to group the objects (by id) in one array.
The approach I tried is:
b = []
rest_object = []
a.each_with_index do |aa, idx|
aa.delete(:id)
rest_object << aa
if idx == 0
next
end
puts a[idx][:id], a[idx-1][:id]
if a[idx][:id] != a[idx-1][:id]
b << {id: a[idx-1][:id], names: rest_object}
rest_object = []
end
end
But I am getting an empty output.
Also, if it is possible to achieve the same in some cleaner way.
That would be helpful.
Something like this does the job. This deletes the :id key-value pair from each hash and uses the value to group the remainder of the hash. Then map the resulting hash to created an array and transform the data into {id: ..., fields: ...} format.
a = [{id: 0, name: "ABC", work: "ABC2"}, {id: 0, name: "XYZ", work: "XYZ2"}, {id: 1, name: "ACD", work: "ACD2"}]
b = a.group_by { |hash| hash.delete(:id) }
.map { |id, fields| {id: id, fields: fields} }
#=> [{:id=>0, :fields=>[{:name=>"ABC", :work=>"ABC2"}, {:name=>"XYZ", :work=>"XYZ2"}]}, {:id=>1, :fields=>[{:name=>"ACD", :work=>"ACD2"}]}]
Note: This mutates the hashes in the a array. If you don't want this change a.group_by to a.map(&:dup).group_by. Which first duplicates all hashes before doing any mutations.
Try following,
required_keys = a[0].except(:id).keys
b = a.group_by { |x| x[:id] }
b = b.inject([]) do |m,(k,v)|
arr = { id: k }
required_keys.each do |key|
arr[key.to_s.pluralize.to_sym] = v.map { |z| z.slice(key) }
end
m << arr
end
# => [{:id=>0, :names=>[{:name=>"ABC"}, {:name=>"XYZ"}]}, {:id=>1, :names=>[{:name=>"ACD"}]}]
a = [
{
id: 0,
name: "ABC"
},
{
id: 0,
name: "XYZ"
},
{
id: 1,
name: "ACD"
}
]
ary = []
a.each do|val|
ary[val[:id]] = {id: val[:id]} unless ary[val[:id]]
ary[val[:id]][:names] = [] unless ary[val[:id]][:names]
ary[val[:id]][:names].push({name: val[:name]})
end
If I understand, given a more general array like this:
a = [ { id: 0, name: "ABC", etc: "01" },
{ id: 0, name: "XYZ", etc: "02" },
{ id: 1, name: "ACD", etc: "11" } ]
The first idea I came up with is doing something like this:
require 'active_support/inflector' # for using pluralize
res = a.group_by{ |h| h[:id] }.values.map do |ary|
h = Hash.new{ |h,k| h[k] = [] }
ary.each { |hh| hh.each { |k,v| h[k] << v } }
h[:id] = h[:id].first
h.transform_keys do |k|
unless k == :id
k.to_s.pluralize.to_sym
else
k
end
end
end
res #=> [{:id=>0, :names=>["ABC", "XYZ"], :etcs=>["01", "02"]}, {:id=>1, :names=>["ACD"], :etcs=>["11"]}]
This is not exactly the format you require, but to get that format, just change this line
ary.each { |hh| hh.each { |k,v| h[k] << v } }
to
ary.each { |hh| hh.each { |k,v| h[k] << { k => v } } }

Create a deep nested hash using loops in Ruby

I want to create a nested hash using four values type, name, year, value. ie, key of the first hash will be type, value will be another hash with key name, then value of that one will be another hash with key year and value as value.
The array of objects I'm iterating looks like this:
elements = [
{
year: '2018',
items: [
{
name: 'name1',
value: 'value1',
type: 'type1',
},
{
name: 'name2',
value: 'value2',
type: 'type2',
},
]
},
{
year: '2019',
items: [
{
name: 'name3',
value: 'value3',
type: 'type2',
},
{
name: 'name4',
value: 'value4',
type: 'type1',
},
]
}
]
And I'm getting all values together using two loops like this:
elements.each do |element|
year = element.year
element.items.each |item|
name = item.name
value = item.value
type = item.type
# TODO: create nested hash
end
end
Expected output is like this:
{
"type1" => {
"name1" => {
"2018" => "value1"
},
"name4" => {
"2019" => "value4"
}
},
"type2" => {
"name2" => {
"2018" => "value2"
},
"name3" => {
"2019" => "value3"
}
}
}
I tried out some methods but it doesn't seems to work out as expected. How can I do this?
elements.each_with_object({}) { |g,h| g[:items].each { |f|
h.update(f[:type]=>{ f[:name]=>{ g[:year]=>f[:value] } }) { |_,o,n| o.merge(n) } } }
#=> {"type1"=>{"name1"=>{"2018"=>"value1"}, "name4"=>{"2019"=>"value4"}},
# "type2"=>{"name2"=>{"2018"=>"value2"}, "name3"=>{"2019"=>"value3"}}}
This uses the form of Hash#update (aka merge!) that employs a block (here { |_,o,n| o.merge(n) } to determine the values of keys that are present in both hashes being merged. See the doc for definitions of the three block variables (here _, o and n). Note that in performing o.merge(n) o and n will have no common keys, so a block is not needed for that operation.
Assuming you want to preserve the references (unlike in your desired output,) here you go:
elements = [
{
year: '2018',
items: [
{name: 'name1', value: 'value1', type: 'type1'},
{name: 'name2', value: 'value2', type: 'type2'}
]
},
{
year: '2019',
items: [
{name: 'name3', value: 'value3', type: 'type2'},
{name: 'name4', value: 'value4', type: 'type1'}
]
}
]
Just iterate over everything and reduce into the hash. On the structures of known shape is’s a trivial task:
elements.each_with_object(
Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) } # for deep bury
) do |h, acc|
h[:items].each do |item|
acc[item[:type]][item[:name]][h[:year]] = item[:value]
end
end
#⇒ {"type1"=>{"name1"=>{"2018"=>"value1"},
# "name4"=>{"2019"=>"value4"}},
# "type2"=>{"name2"=>{"2018"=>"value2"},
# "name3"=>{"2019"=>"value3"}}}

Rails: How to merge two hashes if a specific key has the same value?

I'm trying to merge hashes if a specific key has the same value.
here is the array
[{
id: 77,
member_phone: "9876543210",
created_at: "2017-05-03T11:06:03.000Z",
name: "Sure"
},
{
id: 77,
member_phone: "123456789",
created_at: "2017-05-03T11:06:03.000Z",
name: "Sure"
},
{
id: 78,
member_phone: "12345",
created_at: "2017-05-03T11:06:03.000Z",
name: "XYZ"
}]
and the required output:
[{
id: 77,
member_phone: "123456789,9876543210",
created_at: "2017-05-03T11:06:03.000Z",
name: "Sure"
},
{
id: 78,
member_phone: "12345",
created_at: "2017-05-03T11:06:03.000Z",
name: "XYZ"
}]
here's the code I tried:
merge_users.group_by { |h1| h1["id"] }.map do |k,v|
{ "id" => k, :member_phone => v.map { |h2| h2[:member_phone] }.join(", ") }
end
how can I do it?
The following code would work for your given example.
code
result = arr.group_by {|h| h[:id]}.values.map do |arr|
arr.reduce do |h1, h2|
h1.merge(h2) do |k, ov, nv|
ov.eql?(nv) ? ov : [ov, nv].join(",")
end
end
end
p result
#=>[{:id=>77, :member_phone=>"9876543210,123456789", :created_at=>"2017-05-03T11:06:03.000Z", :name=>"Sure"}, {:id=>78, :member_phone=>"12345", :created_at=>"2017-05-03T11:06:03.000Z", :name=>"XYZ"}]
How about:
grouped = data.group_by do |item|
item[:id]
end
combined = grouped.map do |_id, hashes|
hashes.inject({}) do |memo, hash|
memo.merge(hash)
end
end
It works in two passes:
First group all hashes by the value of the :id key
This returns a Hash with the id as key, and an array (of all the hashes with this id) as value.
In a second pass all the hashes are merged and mapped to an array again.
arr = [
{ id: 77, phone: "9876543210", name: "Sure" },
{ id: 77, phone: "123456789", name: "Sure" },
{ id: 78, phone: "12345", name: "XYZ" }
]
You could use the form of Hash#update (aka merge!) that uses a block to compute the values of keys that are present in both hashes being merged.
arr.each_with_object({}) { |g,h| h.update(g[:id]=>g) { |_,o,n|
o.merge(phone: "#{o[:phone]}#{n[:phone]}") } }.values
#=> [{:id=>77, :phone=>"9876543210123456789", :name=>"Sure"},
# {:id=>78, :phone=>"12345", :name=>"XYZ"}]
Note that the receiver of Hash#values is the following.
#=> {77=>{:id=>77, :phone=>"9876543210123456789", :name=>"Sure"},
# 78=>{:id=>78, :phone=>"12345", :name=>"XYZ"}}
See the doc for Hash#update for definitions of the three block variables _, o and n. I used an underscore for the first variable (a valid name for a local variable) to signify that it is not used in the block calculation (a common practice).
Note that Hash#update can almost always be used when Enumerable#group_by can be used, and vice-versa.
Here's one way to use Hash#group_by here.
arr.group_by { |h| h[:id] }.
map { |_,a| a.first.merge(phone: a.map { |h| h[:phone] }.join) }
#=> [{:id=>77, :phone=>"9876543210123456789", :name=>"Sure"},
# {:id=>78, :phone=>"12345", :name=>"XYZ"}]
Note that
arr.group_by { |h| h[:id] }
#=> {77=>[{:id=>77, :phone=>"9876543210", :name=>"Sure"},
# {:id=>77, :phone=>"123456789", :name=>"Sure"}],
# 78=>[{:id=>78, :phone=>"12345", :name=>"XYZ"}]}

Ruby: how to perform "left join" on two arrays of hashes efficiently

I am trying to left join the following arrays of hashes:
input:
a = [{id: 1, name: 'Bob'}, {id: 2, name: 'Jack'}, {id: 3, name: 'Tom'}]
b = [{id: 3, age: 12}, {id: 2, age: 7}]
output:
[{id: 1, name: 'Bob', age: nil}, {id: 2, name: 'Jack', age: 7}, {id: 3, name: 'Tom', age: 12}]
Currently I am doing something along the lines with:
a.map do |x|
{
id: x[:id],
name: x[:name],
age: (b.detect{|y| x[:id] == y[:id]} || {age: nil}).fetch(:age)
}
end
It works, but it is super slow when the data set is large.
Is there any better way to perform the "join" operation more efficiently?
[a, b].map { |a| a.group_by { |e| e[:id] } }
.reduce do |a, b|
a.merge(b) { |_, v1, v2| v1.first.merge v2.first }
end.values
.map do |e|
Array === e ? {age:nil, name:nil}.merge(e.first) : e
end
The whole preparation step takes O(N) and the merge then is done as O(N), plus the finalization takes O(N).
h = b.each_with_object({}) { |g,h| h[g[:id]] = g[:age] }
#=> {3=>12, 2=>7}
a.map { |g| g.merge(age: h[g[:id]]) }
#=> [{:id=>1, :name=>"Bob", :age=>nil},
# {:id=>2, :name=>"Jack", :age=>7},
# {:id=>3, :name=>"Tom", :age=>12}]
If a is to be modified in place, change the second line to
a.each { |g| g[:age] = h[g[:id]] }
a #=> [{:id=>1, :name=>"Bob", :age=>nil},
# {:id=>2, :name=>"Jack", :age=>7},
# {:id=>3, :name=>"Tom", :age=>12}]

Resources