I have the following array of hashes:
books = [
{'author' => 'John Doe', 'month' => 'June', 'sales' => 500},
{'author' => 'George Doe', 'month' => 'June', 'sales' => 600},
{'author' => 'John Doe', 'month' => 'July', 'sales' => 700},
{'author' => 'George Doe', 'month' => 'July', 'sales' => 800}
]
Out of this, how can I get an array, with the following:
[
{'author' => 'John Doe', 'total_sales' => 1200},
{'author' => 'George Doe', 'total_sales' => 1400}
]
You can use each_with_object
with an appropriate default:
default = Hash.new {|h,k| h[k] = { 'author' => k, 'total_sales' => 0 } }
books.each_with_object(default) do |h, memo|
memo[h['author']]['total_sales'] += h['sales']
end.values
Outputs:
[
{"author"=>"John Doe", "total_sales"=>1200},
{"author"=>"George Doe", "total_sales"=>1400}
]
You could use the form of Hash#update (aka merge!
) that employs a block to determine the values of keys that are present in both hashes being merged:
books.each_with_object({}) do |g,h|
h.update(g['author']=>g['sales']) { |_,o,n| o+n }
end.map { |k,v| { 'author'=>k, 'total_sales'=>v } }
#=> [{"author"=>"John Doe", "total_sales"=>1200},
# {"author"=>"George Doe", "total_sales"=>1400}]
See the doc for a description of the block variables _
, o
and n
. (I used _
for the common key, mainly to signal to the reader that it is not used in the block calculation, a common Ruby convention.)
Consider stopping after the hash has been constructed:
books.each_with_object({}) do |g,h|
h.update(g['author']=>g['sales']) { |_,o,n| o+n }
end
#=> {"John Doe"=>1200, "George Doe"=>1400}
You can use Enumerable#group_by
to get an array of array. Then you can merge the hashes of each array using Enumerable#reduce
and Hash#merge
.
group_books = books.group_by { |h| h['author'] }.values
result = group_books.map do |ary|
ary.reduce do |new_hash, h|
new_hash.merge(h) { |k, v1, v2| k == 'sales' ? v1 + v2 : v1 }
end
end
EDIT : I forgot to remove the key month
with Hash#delete
.
result.map { |h| h.delete('month') }
I would use something like this:
sales = ->(hash) { hash['sales'] }
books.group_by { |book| book.slice('author') }
.map { |result, books| result.merge('total_sales' => books.sum(&sales)) }
#=> [
# {"author"=>"John Doe", "total_sales"=>1200},
# {"author"=>"George Doe", "total_sales"=>1400}
# ]
The code mostly speaks for itself. I first group_by
a slice
of the hash. Then merge
the sum
of the sales values from books
.
I've extracted the sum
block to a lambda and saved that in the sales
variable to not clutter the map
block with nested blocks.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.