Ruby - Group an array of hashes by key - ruby

I have an array of hashes (edited):
data = [
{id: 1, name: "Amy", win: 1, defeat: 0},
{id: 1, name: "Amy", win: 1, defeat: 3},
{id: 2, name: "Carl", win: 0, defeat: 1},
{id: 2, name: "Carl", win: 2, defeat: 1}
]
How can I group or merge into something like this using the key "name" as reference:
data = [
{id: 1, name: "Amy", win: 2, defeat: 3},
{id: 2, name: "Carl", win: 2, defeat: 2}
]
edited I forgot to mention that I have an ID too that can't be added.

Here is my try
#!/usr/bin/env ruby
data = [
{"name"=> "Amy", "win" => 1, "defeat" => 0},
{"name"=> "Amy", "win" => 1, "defeat" => 3},
{"name"=> "Carl", "win" => 0, "defeat" => 1},
{"name"=> "Carl", "win" => 2, "defeat" => 1}
]
merged_hash = data.group_by { |h| h['name'] }.map do |_,val|
val.inject do |h1,h2|
h1.merge(h2) do |k,o,n|
k == 'name' ? o : o + n
end
end
end
merged_hash
# => [{"name"=>"Amy", "win"=>2, "defeat"=>3},
# {"name"=>"Carl", "win"=>2, "defeat"=>2}]
Answer to the edited post :-
#!/usr/bin/env ruby
data = [
{id: 1, name: "Amy", win: 1, defeat: 0},
{id: 1, name: "Amy", win: 1, defeat: 3},
{id: 2, name: "Carl", win: 0, defeat: 1},
{id: 2, name: "Carl", win: 2, defeat: 1}
]
merged_hash = data.group_by { |h| h.values_at(:name, :id) }.map do |_,val|
val.inject do |h1,h2|
h1.merge(h2) do |k,o,n|
%i(id name).include?(k) ? o : o + n
end
end
end
merged_hash
# => [{:id=>1, :name=>"Amy", :win=>2, :defeat=>3},
# {:id=>2, :name=>"Carl", :win=>2, :defeat=>2}]

You can do it in one pass with each_with_object and a Hash-memo with an appropriate default. For example:
data.each_with_object(Hash.new { |h, k| h[k] = { :id => k.first, :name => k.last, :win => 0, :defeat => 0 } }) do |h, m|
k = h.values_at(:id, :name)
m[k][:win ] += h[:win ]
m[k][:defeat] += h[:defeat]
end.values
The basic trick is to cache the results indexed by an appropriate key ([ h[:id], h[:name] ] in this case) and use the values to store what you're after. The default proc on the m Hash autovivifies cached values and then you can apply simple summations during iteration. And a final values call to unwrap the cache.

Good place where you can use group_by
result = []
data.group_by{|d| d[:id]}.each do {|name, records|
win = 0
defeat = 0
records.each do |r|
win += r[:win]
defeat += r[:defeat]
end
f = records.first
results << {:id => f[:id], :name => f[:name], :win => win, :defeat => defeat}
end

Related

Optimize grouping array of hashes by date

Given array
[
{date: '2014-01-01', a: 5, b:1},
{date: '2014-01-01', xyz: 11},
{date: '2014-10-10', qbz: 5},
{date: '2014-10-10', v: 4, q: 1, strpm: -99}
]
I want to group by date and output an array of hashes
ouput = [
{date: 2014-01-01, a: 5, b:1, xyz: 11 },
{date: 2014-10-10, qbz: 5, v: 4, q: 1, strpm: -99},
]
I am solving it in Ruby and have a solution but it seems inefficient.
def arrayt(inputarray)
outputarray=[];
inputarray.each do |x|
tindex = includes(outputarray,x[:date])
if tindex == -1
outputarray.push(x)
else
outputarray[tindex].merge!(x)
end
end
return outputarray
end
def includes(array,date)
array.each_with_index do |temp,index|
if date==temp[:date]
return index
end
end
return -1
end
Any help with a more elegant solution would be appreciated
[
{date: '2014-01-01', a: 5, b:1},
{date: '2014-01-01', xyz: 11},
{date: '2014-10-10', qbz: 5},
{date: '2014-10-10', v: 4, q: 1, strpm: -99}
]
.group_by(&:first).map{|_, v| v.inject(:merge)}
Here's a way that employs the form of Hash#update (aka merge!) that uses a block to determine the values of keys that are present in both hashes being merged:
arr.each_with_object({}) { |g,h|
h.update({ g[:date]=>g }) { |_,ov,nv| ov.merge(nv) } }.values
To wit:
hash = arr.each_with_object({}) { |g,h|
h.update({ g[:date]=>g }) { |_,ov,nv| ov.merge(nv) } }
#=>{"2014-01-01"=>{:date=>"2014-01-01", :a=>5, :b=>1, :xyz=>11},
# "2014-10-10"=>{:date=>"2014-10-10", :qbz=>5, :v=>4, :q=>1, :strpm=>-99}}
hash.values
#=> [{:date=>"2014-01-01", :a=>5, :b=>1, :xyz=>11},
# {:date=>"2014-10-10", :qbz=>5, :v=>4, :q=>1, :strpm=>-99}]

Collect values from an array of hashes

I have a data structure in the following format:
data_hash = [
{ price: 1, count: 3 },
{ price: 2, count: 3 },
{ price: 3, count: 3 }
]
Is there an efficient way to get the values of :price as an array like [1,2,3]?
First, if you are using ruby < 1.9:
array = [
{:price => 1, :count => 3},
{:price => 2, :count => 3},
{:price => 3, :count => 3}
]
Then to get what you need:
array.map{|x| x[:price]}
There is a closed question that redirects here asking about handing map a Symbol to derive a key. This can be done using an Enumerable as a middle-man:
array = [
{:price => 1, :count => 3},
{:price => 2, :count => 3},
{:price => 3, :count => 3}
]
array.each.with_object(:price).map(&:[])
#=> [1, 2, 3]
Beyond being slightly more verbose and more difficult to understand, it also slower.
Benchmark.bm do |b|
b.report { 10000.times { array.map{|x| x[:price] } } }
b.report { 10000.times { array.each.with_object(:price).map(&:[]) } }
end
# user system total real
# 0.004816 0.000005 0.004821 ( 0.004816)
# 0.015723 0.000606 0.016329 ( 0.016334)

Subtract values in hash from corresponding values in another hash

I'd like to be able to subtract two hashes and get a third hash in Ruby.
The two hashes look like this:
h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}
h1.default = 0
h2 = {"cat" => 50, "dog" => 3, "BIRD" => 4, "Mouse" => 75, "Snake" => 10}
h2.default = 0
I'd like to be able to call a method on h1 like this:
h1.difference(h2)
and get this hash as a result:
{"Cat" => 50, "Dog" => 2, "BIRD" => -2, "Mouse" => -75}
I'd like to create a new hash with keys from both Hashes and the values of the new hash to be the value of the key in the first hash minus the value of that key in the second hash. The catch is that I'd like this Hash method to work regardless of the case of the keys. In other words, I'd like "Cat" to match up with "cat".
Here's what I have so far:
class Hash
def difference(another_hash)
(keys + another_hash.keys).map { |key| key.strip }.uniq.inject(Hash.new(0)) { |acc, key| acc[key] = (self[key] - another_hash[key]); acc }.delete_if { |key, value| value == 0 }
end
end
This is OK, but, unfortunately, the result isn't what I want.
Any help would be appreciated.
How about converting the hashes to sets.
require 'set'
h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}
h1.default = 0
h2 = {"cat" => 50, "dog" => 3, "BIRD" => 4, "Mouse" => 75, "Snake" => 10}
h2.default = 0
p (h1.to_set - h2.to_set)
#=> #<Set: {["Cat", 100], ["Dog", 5], ["Bird", 2]}>
As a recommendation...
I've used something like this in the past:
class Hash
def downcase_keys
Hash[map{ |k,v| [k.downcase, v]}]
end
def difference(other)
Hash[self.to_a - other.to_a]
end
alias :- :difference
end
which lets me do things like:
irb(main):206:0> h1.downcase_keys - h2.downcase_keys
{
"cat" => 100,
"dog" => 5,
"bird" => 2
}
irb(main):207:0> h2.downcase_keys - h1.downcase_keys
{
"cat" => 50,
"dog" => 3,
"bird" => 4,
"mouse" => 75
}
The alias gives you the nice syntax of using - instead of difference, similar to using - for Arrays and Sets. You can still use difference though:
irb(main):210:0> h1.downcase_keys.difference(h2.downcase_keys)
{
"cat" => 100,
"dog" => 5,
"bird" => 2
}
irb(main):211:0> h2.downcase_keys.difference(h1.downcase_keys)
{
"cat" => 50,
"dog" => 3,
"bird" => 4,
"mouse" => 75
}
I always normalize my hash keys, and don't allow variants to leak in. It makes processing the hashes much too difficult when you don't know what the keys are called, so I'd highly recommend doing that as a first step. It's a code-maintenance issue.
Otherwise, you could create a map of the original key names and their normalized names, but you run into problems if your hash contains two unique-case keys, such as 'key' and 'KEY', because normalizing will stomp on one.
Sorry that due to the time limit (I have to take care of my baby boy now), only figured out this stupid but working code:
h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}
h1.default = 0
h2 = {"cat" => 50, "dog" => 3, "BIRD" => 4, "Mouse" => 75, "Snake" => 10}
h2.default = 0
h3 = {"Cat" => 50, "Dog" => 2, "BIRD" => -2, "Mouse" => -75}
class Hash
def difference(subtrahend)
diff = {}
self.each_pair do |k1, v1|
flag = false
subtrahend.each_pair do |k2, v2|
if k1.downcase == k2.downcase
flag = true
v_diff = v1 - v2
break if v_diff == 0
v_diff > 0 ? diff[k1] = v_diff : diff[k2] = v_diff
end
end
diff[k1] = v1 unless flag
end
subtrahend.each_pair do |k2, v2|
flag = false
self.each_pair do |k1, v1|
if k1.downcase == k2.downcase
flag = true
break
end
end
diff[k2] = -v2 unless flag
end
return diff
end
end
h1.difference(h2) == h3 ? puts("Pass") : puts("Fail") #=> "Pass"
I got this to the resque https://github.com/junegunn/insensitive_hash
then follow your procedure but slighly tweaked as requirement
require 'insensitive_hash'
h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}.insensitive
h1.default = 0
h2 = {"cat" => 50, "dog" => 3, "BIRD" => 4, "Mouse" => 75, "Snake" => 10}.insensitive
h2.default = 0
class Hash
def difference(another_hash)
(keys + another_hash.keys).map { |key|
key.downcase }.uniq.inject(Hash.new(0)) do |acc, key|
val = self[key] - another_hash[key]
acc[key] = val if val!= 0
acc
end
end
end
h1.difference(h2)
# => {"cat"=>50, "dog"=>2, "bird"=>-2, "mouse"=>-75}
This time I would like to provide another solution: normalized -> store original key value pairs -> grab the original key who has larger value as the key for the difference.
h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}
h1.default = 0
h2 = {"cat" => 50, "dog" => 3, "BIRD" => 4, "Mouse" => 75, "Snake" => 10}
h2.default = 0
h3 = {"Cat" => 50, "Dog" => 2, "BIRD" => -2, "Mouse" => -75}
class Hash
def difference(subtrahend)
# create a hash which contains all normalized keys
all_pairs = (self.keys.map{|x| x.downcase} + subtrahend.keys.map{|x| x.downcase}).uniq.inject({}) do |pairs, key|
pairs[key] = []
pairs
end
#=> {"mouse"=>[], "cat"=>[], "snake"=>[], "bird"=>[], "dog"=>[]}
# push original key value pairs into array which is the value of just created hash
[self, subtrahend].each_with_index do |hsh, idx|
hsh.each_pair { |k, v| all_pairs[k.downcase].push([k, v]) }
all_pairs.each_value { |v| v.push([nil, 0]) if v.size == idx }
end
#=> {"mouse"=>[[nil, 0], ["Mouse", 75]], "cat"=>[["Cat", 100], ["cat", 50]], "snake"=>[["Snake", 10], ["Snake", 10]], "bird"=>[["Bird", 2], ["BIRD", 4]], "dog"=>[["Dog", 5], ["dog", 3]]}
results = {}
all_pairs.each_value do |values|
diff = values[0][1] - values[1][1]
# always take the key whose value is larger
if diff > 0
results[values[0][0]] = diff
elsif diff < 0
results[values[1][0]] = diff
end
end
return results
end
end
puts h1.difference(h2).inspect #=> {"Cat" => 50, "Dog" => 2, "BIRD" => -2, "Mouse" => -75}
h1.difference(h2) == h3 ? puts("Pass") : puts("Fail") #=> "Pass"
According to what you described, this one does a pretty good job. The result is exactly what you've shown (key is not normalized in the final result, but depends on whose value is bigger).

ruby - group by repeating key of multiple hashes

I have the following array
t = [
{nil => 1, 10 => 2, 16 => 4, 5=> 10},
{nil => 9, 5 => 2, 17 => 3, 10 => 2},
{10 => 4, 5 => 9, 17 => 1}
]
how can I get this as result?
{nil => [1,9,0],10 => [2,2,4], 16 => [4,0,0], 5 => [10,2,9], 17=>[0,3,1]}
I've seen that I can use something like this
t.group_by{|h| h['key']}
but I'm not sure if I can put a regexp inside the brackets
Thanks in advance
Javier
EDIT:
Is just want to group by each key of each hash inside the array, if the key is not present then the value is 0 for that hash
How about this one for illegibility:
t = [
{nil => 1, 10 => 2, 16 => 4, 5=> 10},
{nil => 9, 5 => 2, 17 => 3, 10 => 2},
{10 => 4, 5 => 9, 17 => 1}
]
# Create hash of possible keys
keys = t.reduce({}) { |m, h| h.each_key { |k| m[k] = [] }; m }
# Iterate through array, for each hash, for each key, append the
# value if key is in hash or zero otherwise
t.reduce(keys) { |m, h| m.each_key { |k| m[k] << (h[k] || 0) }; m }
puts keys
#=> {nil=>[1, 9, 0], 10=>[2, 2, 4], 16=>[4, 0, 0], 5=>[10, 2, 9], 17=>[0, 3, 1]}
Not the most elegant code I've ever written, but it does the job and is easy to understand:
def jqq(a)
keys = []
result = {}
a.each do |h|
keys += h.keys
end
keys.uniq.each do |key|
result[key] = []
a.each do |h|
h.default = 0
result[key] << h[key]
end
end
result
end
t = [
{nil => 1, 10 => 2, 16 => 4, 5=> 10},
{nil => 9, 5 => 2, 17 => 3, 10 => 2},
{10 => 4, 5 => 9, 17 => 1}
]
puts jqq(t)
# {nil=>[1, 9, 0], 10=>[2, 2, 4], 16=>[4, 0, 0], 5=>[10, 2, 9], 17=>[0, 3, 1]}
I do not think there is any any function available
Just gave a try with hash
def do_my_work(data)
hash = {}
#get all keys first
arr.map{|m| m.keys}.flatten.uniq.each {|a| hash[a]=[]}
# Now iterate and fill the values
arr.each do |elm|
hash.each do |k,v|
hash[k] << (elm[k].nil? ? 0 : elm[k])
end
end
end
hash = do_my_work(t)
puts hash
# => {nil=>[1, 9, 0], 10=>[2, 2, 4], 16=>[4, 0, 0], 5=>[10, 2, 9], 17=>[0, 3, 1]}

Search an Array of Hashes with a partial Hash in Ruby

Given a hash
z = [{'a' => 1, 'b' => 2}, {'a' => 3, 'b' => 4}, {'a' => 1, 'b' => 4}]
How do I search if the search parameter itself is a hash e.g.
{'a' => 3}
so that I can do something like z.find_by_hash({'a' => 3}) for it to return
{'a' => 3, 'b' => 4}
and also to get a collection of arrays like z.find_by_hash({'a' => 1}) for it to return
[{'a' => 1, 'b' => 2}, {'a' => 1, 'b => 4}]
Thanks
You can do this:
class Array
def find_by_hash(hash)
self.select { |h| h.includes_hash?(hash) }
end
end
class Hash
def includes_hash?(other)
included = true
other.each do |key, value|
included &= self[key] == other[key]
end
included
end
end
This extends Hash by a method to find out if a Hash includes another (with multiple keys and values). Array is extended with the method you wanted, but it's a more generic approach since you can do this:
ary = [ {:a => 1, :b => 3, :c => 5}, {:a => 5, :b => 2, :c => 8} ]
ary.find_by_hash( { :a => 1, :c => 5 } )
Note: You should also consider using Symbols for Hash keys since it is a common practice in Ruby, but my approach does also work with your keys.
z = [{'a' => 1, 'b' => 2}, {'a' => 3, 'b' => 4}, {'a' => 1, 'b' => 4}]
class Array
def search_hash(hash)
key = hash.keys.first
value = hash.values.first
select { |h| h[key] == value }
end
end
z.search_hash({'a' => 3}) #=> [{"a"=>3, "b"=>4}]
or you can type it without curly brackets
z.search_hash('a' => 3)
Basically what you need is something like this:
class Array
def find_by_hash(h)
h.collect_concat do |key, value|
self.select{|h| h[key] == value}
end
end
end
I didn't find an approach in API, so I think we have to implement it of our own.
(by the way, I think #megas' approach is better and more readable)
Code by TDD:
class SearchHashTest < Test::Unit::TestCase
def setup
#array_with_hash_elements = ArrayWithHashElements.new [{'a' => 1, 'b' => 2}, {'a' => 3, 'b' => 4}, {'a' => 1, 'b' => 4}]
end
def test_search_an_array_by_hash_parameter_and_return_single_hash
assert_equal( {'a' => 3, 'b' => 4}, #array_with_hash_elements.search({'a'=>3}) )
end
def test_search_an_array_by_hash_parameter_and_return_an_array
assert_equal( [{'a' => 1, 'b' => 2}, {'a'=> 1, 'b' => 4}], #array_with_hash_elements.search({'a'=>1}))
end
end
implemented code ( just for demo, not production)
class ArrayWithHashElements
def initialize some_array
#elements = some_array
end
def search( query_hash)
puts "search: #{query_hash.inspect}"
result = []
#elements.each do | array_element_in_hash_form|
query_hash.each_pair do | key, value |
if array_element_in_hash_form.has_key?(key) && array_element_in_hash_form[key] == value
puts "adding : #{array_element_in_hash_form.inspect} to result"
result << array_element_in_hash_form
end
end
result
end
return result.size != 1 ? result : result[0]
end
end
result:
sg552#siwei-moto:~/workspace/test$ ruby search_hash_test.rb
Loaded suite search_hash_test
Started
search: {"a"=>1}
adding : {"b"=>2, "a"=>1} to result
adding : {"b"=>4, "a"=>1} to result
.search: {"a"=>3}
adding : {"b"=>4, "a"=>3} to result
.
Finished in 0.000513 seconds.
2 tests, 2 assertions, 0 failures, 0 errors

Resources