Consolidating duplicate array items - ruby

I have an array of hashes...
array = [
{
'keyword' => 'A',
'total_value' => 50
},
{
'keyword' => 'B',
'total_value' => 25
},
{
'keyword' => 'C',
'total_value' => 40
},
{
'keyword' => 'A',
'total_value' => 10
},
{
'keyword' => 'C',
'total_value' => 15
}]
I need to consolidate the hashes with an identical keyword value. By consolidate, I mean combine total_values. For example, after consolidation of the above array, there should only be one hash with 'keyword' => 'A' with a 'total_value => 60

array = [
{
'keyword' => 'A',
'total_value' => 50
},
{
'keyword' => 'B',
'total_value' => 25
},
{
'keyword' => 'C',
'total_value' => 40
},
{
'keyword' => 'A',
'total_value' => 10
},
{
'keyword' => 'C',
'total_value' => 15
}]
m = array.inject(Hash.new(0)) do |hs,i|
hs[i['keyword']] += i['total_value']
hs
end
p m
Output:
{"A"=>60, "B"=>25, "C"=>55}
By consolidate, I mean combine total_values. For example, after consolidation of the above array, there should only be one hash with 'keyword' => 'A' with a 'total_value => 60
Here is how it can be done:
m = array.each_with_object(Hash.new(0)) do |h,ob|
if h['keyword'] == 'A'
h['total_value'] += ob['total_value']
ob.update(h)
end
end
p m
#=> {"keyword"=>"A", "total_value"=>60}

A simple method is doing this as you add items to a collection. Start to add an item, check if keyword is there. if (a) it's there, then just add new item's total_value to its. else (b) add new item to the collection.

array.group_by{|h| h["keyword"]}
.map{|k, v| {
"keyword" => k,
"total_value" => v.map{|h| h["total_value"]}.inject(:+)
}}

Related

Pushing objects into a hash inside a loop

I'm trying to achieve the following JSON results:
{
"movie" =>
[{
"title": "Thor",
"year" : 2011,
},
{
"title": "Iron Man",
"year" : 2008,
}],
"tv" =>
[{
"title": "Parks and Recreation"
"year": 2009
},
{
"title": "Friends"
"year": 1994
}]
}
With JavaScript, I would loop through my results and do something like:
results['movie'].push(item);
results['tv'].push(item);
With Ruby code, the farthest I've gone is this:
#results = Hash.new
results['Search'].each do |r|
if r['Type'] == 'movie'
#results['movie'] << {
'title' => r['Title'],
'year' => r['Year']
}
elsif r['Type'] == 'series'
#results['tv'] << {
'title' => r['Title'],
'year' => r['Year']
}
end
end
What am I missing here?
I think you can get what you want by using Enumerable#each_with_object and assigning a default value to the hash.
def group_search_results(items)
results = Hash.new { |hash, key| hash[key] = [] }
items.each_with_object(results) do |item|
results[item['Type']] << {'title' => item['Title'], 'year' => item['Year']}
end
end
describe "search_results" do
it "groups into an object" do
items = [
{'Type' => 'movie', 'Title' => 'Thor', 'Year' => 2011},
{'Type' => 'movie', 'Title' => 'Iron Man', 'Year' => 2008},
{'Type' => 'series', 'Title' => 'Parks and Recreation', 'Year' => 2009},
{'Type' => 'series', 'Title' => 'Friends', 'Year' => 1994},
]
results = group_search_results(items)
expect(results).to eq({
'movie' => [
{'title' => 'Thor', 'year' => 2011},
{'title' => 'Iron Man', 'year' => 2008},
],
'series' => [
{'title' => 'Parks and Recreation', 'year' => 2009},
{'title' => 'Friends', 'year' => 1994},
],
})
end
end
I believe the problem has to do with the initialization of your hash. The movie and tv keys aren't currently an array. You can initialize your hash like this:
#results = { 'movie' => [], 'tv' => [] }
Here's how it looks with the rest of your code:
#results = { 'movie' => [], 'tv' => [] }
results['Search'].each do |r|
if r['Type'] == 'movie'
#results['movie'] << {
'title' => r['Title'],
'year' => r['Year']
}
elsif r['Type'] == 'series'
#results['tv'] << {
'title' => r['Title'],
'year' => r['Year']
}
end
end
results = {
search: {
movie: [
{ title: 'Thor', year: 2011 },
{ title: 'Iron Man', year: 2008 },
],
tv: [
{ title: 'Parks and Recreation', year: 2009 },
{ title: 'Friends', year: 1994 },
]
}
}
#results = Hash.new{|k, v| k[v] = []}
results[:search].each do |type, array|
#results[type].push(*array)
end
results[:search].each_with_object(Hash.new{|k, v| k[v] = []}) do |(type, array), hash|
hash[type].push(*array)
end

Remove all but one duplicate from array of hashes

I have an array of hashes like this:
[
{ :color => 'red', :animal => 'dog' },
{ :color => 'blue', :animal => 'cat' },
{ :color => 'yellow', :animal => 'frog' },
{ :color => 'red', :animal => 'cat' },
{ :color => 'red', :animal => 'mouse' }
]
What I want to do is remove all but one of the duplicates based on one of the keys.
So in this case, I want to remove all but one of the items where color is red. Doesn't matter which one.
Final output would be something like this:
[
{ :color => 'blue', :animal => 'cat' },
{ :color => 'yellow', :animal => 'frog' },
{ :color => 'red', :animal => 'mouse' }
]
Again, when removing the duplicates, the one to keep does not matter.
.group_by { |x| x[:color] }.values.map(&:first)
.inject({}) { |xs, x| xs[x[:color]] = x; xs }.values
Another way of achieving this would be
.uniq { |h| h[:color] }
=> [{:color=>"red", :animal=>"dog"}, {:color=>"blue",
:animal=>"cat"}, {:color=>"yellow", :animal=>"frog"}]
As #Victor suggested this is for ruby 1.9.2+

merge every 3rd Hash in Array

Hi I can't find how to merge every 3 hashes of an array.
here is my array of hashes.
[
{:key1=>"v1"}, {:ky2 => "v2"}, {:key3 => "v3"},
{:key1=>"v4"}, {:ky2 => "v5"}, {:key3 => "v6"},
{:key1=>"v7"}, {:ky2 => "v8"}, {:key3 => "v9"},..
]
What I would need is to merge every 3 hashes to look like this :
[
{:key1=>"v1", :ky2 => "v2", :key3 => "v3"},
{:key1=>"v4", :ky2 => "v5", :key3 => "v6"},
{:key1=>"v7", :ky2 => "v8", :key3 => "v9"},..
]
Thank in advance for you help.
I'd do
hs = [
{:key1=>"v1"}, {:ky2 => "v2"}, {:key3 => "v3"},
{:key1=>"v4"}, {:ky2 => "v5"}, {:key3 => "v6"},
{:key1=>"v7"}, {:ky2 => "v8"}, {:key3 => "v9"}
]
hs.each_slice(3).map { |grouped_hs| grouped_hs.inject(:merge) }
# => [{:key1=>"v1", :ky2=>"v2", :key3=>"v3"},
# {:key1=>"v4", :ky2=>"v5", :key3=>"v6"},
# {:key1=>"v7", :ky2=>"v8", :key3=>"v9"}]
a.flat_map(&:to_a).each_slice(3).map(&:to_h)
#=> [{:key1=>"v1", :ky2=>"v2", :key3=>"v3"},
#=> {:key1=>"v4", :ky2=>"v5", :key3=>"v6"},
#=> {:key1=>"v7", :ky2=>"v8", :key3=>"v9"}]
Array#to_h was added in v2.1.
a = [
{ :key1=>'v1' }, { :ky2 => 'v2' }, { :key3 => 'v3' },
{ :key1=>'v4' }, { :ky2 => 'v5' }, { :key3 => 'v6' },
{ :key1=>'v7' }, { :ky2 => 'v8' }, { :key3 => 'v9' }
]
a.each_slice(3).map{ |e| e.inject(&:merge) }

ruby one-liner from two hashes

a = {"rows" => [{"id" => "231-z", "name" => 'jon', "age"=> 27, "state" => 'AL'},
{"id" => "4121-x", "name" => 'ton', "age"=> 37, "state" => 'VA'}
]
}
b = {"rows" => [{"key" => ["xyz","4121-x"], "value" =>{"sum" => 12312, "realage" => 29}},
{"key" => ["xyz","231-z"], "value" =>{"sum" => 1212, "realage" => 33}}
]
}
In hash a, age is incorrect
In hash b, realage is correct. Also in hash b id is the second value in the first array that maps to id of hash a . Those are 4121-x, 231-z correspond to hash a
I want to correct the age in hash a and swap it with the realage of hash b
I can do it in multiple steps, but is it possible to do it in one liner or very short? So finally correct hash a should look like
a = {"rows" => [{"id" => "231-z", "name" => 'jon', "age"=> 33, "state" => 'AL'},
{"id" => "4121-x", "name" => 'ton', "age"=> 29, "state" => 'VA'}
]
}
does this look reasonable?
a['rows'].each_with_index do |ah, i|
(bh = b['rows'].select {|h| h['key'].last == ah['id'] }.first) &&
a['rows'][i] = ah.update('age' => bh['value']['realage'])
end
p a
{
"rows" => [
[0] {
"id" => "231-z",
"name" => "jon",
"age" => 33,
"state" => "AL"
},
[1] {
"id" => "4121-x",
"name" => "ton",
"age" => 29,
"state" => "VA"
}
]
}
Please note it will update a only if corresponding id found in b.
Also, the rows order does not matter, nor matter the rows number, it is only important b to have a row with same id as processed row in a
Here is a Working Demo

ruby language - merge an array into another by finding same element

A = [
{ :id => 1, :name => 'good', :link => nil },
{ :id => 2, :name => 'bad', :link => nil }
]
B = [
{ :id => 3, :name => 'good' },
{ :id => 4, :name => 'good' },
{ :id => 5, :name => 'bad' }
]
I need to merge array B into A so that :link in array A includes the entry in array B if :name is the same value in each array.
For example, after processing array A should be:
A = [
{ :id => 1, :name => 'good', :link => [{ :id => 3, :name => 'good' }, { :id => 4, :name => 'good' }] },
{ :id => 2, :name => 'bad', :link => [{ :id => 5, :name => 'bad' }] }
]
thanks.
The short version;
a.each { | item | item[:link] = b.find_all { | x | x[:name] == item[:name] } }
Demo here.
In ruby the constants begin with an uppercase letter, so you should use lowercase letter:
A => a, B => b
a.each do |ha|
b.each do |hb|
if ha[:name] == hb[:name]
ha[:link] |= []
ha[:link] << hb
end
end
end
Functional approach:
B_grouped = B.group_by { |h| h[:name] }
A2 = A.map { |h| h.merge(:link => B_grouped[h[:name]]) }
#=> [{:link=>[{:name=>"good", :id=>3}, {:name=>"good", :id=>4}], :name=>"good", :id=>1},
# {:link=>[{:name=>"bad", :id=>5}], :name=>"bad", :id=>2}]

Resources