Reputation: 383
I have the ruby array as following :
array = [{"id"=>8, "book_id"=>14238}, {"id"=>5, "book_id"=>14238}, {"id"=>7, "book_id"=>10743}, {"id"=>9, "book_id"=>10743}]
I want a new array combining the result of ids
having same book_id
.
Expected Result:
array = [{"book_id"=>14238, "id"=>[8,5]}, {"book_id"=>10743, "id"=>[7,9]}]
Upvotes: 1
Views: 69
Reputation: 110675
You can also do this using the form of Hash#update (a.k.a. merge!
) that employs a block to resolve the values of keys that are contained in both of the hashes being merged.
Code
def aggregate(arr)
arr.each_with_object({}) do |g,h|
f = { g["book_id"]=>{ "id"=>[g["id"]], "book_id"=>g["book_id"] } }
h.update(f) do |_,ov,nv|
ov["id"] << nv["id"].first
ov
end
end.values
end
Example
arr = [{"id"=>8, "book_id"=>14238}, {"id"=>5, "book_id"=>14238},
{"id"=>7, "book_id"=>10743}, {"id"=>9, "book_id"=>10743},
{"id"=>6, "book_id"=>10511}]
aggregate(arr)
#=> [{"id"=>[8, 5], "book_id"=>14238},
# {"id"=>[7, 9], "book_id"=>10743},
# {"id"=>[6], "book_id"=>10511}]
Alternative output
Depending on your requirements, you might consider building a single hash instead of another array of hashes:
def aggregate(arr)
arr.each_with_object({}) { |g,h|
h.update({ g["book_id"]=>[g["id"]] }) { |_,ov,nv| ov+nv } }
end
aggregate(arr)
#=> {14238=>[8, 5], 10743=>[7, 9], 10511=>[6]}
Upvotes: 1
Reputation: 160551
I'd use a hash for the output for easier lookups and/or reuse:
array = [{"id"=>8, "book_id"=>14238}, {"id"=>5, "book_id"=>14238}, {"id"=>7, "book_id"=>10743}, {"id"=>9, "book_id"=>10743}]
hash = array.group_by{ |h| h['book_id'] }.map{ |k, v| [k, v.flat_map{ |h| h['id'] }]}.to_h
# => {14238=>[8, 5], 10743=>[7, 9]}
The keys are the book_id
values, and the associated array contains the id
values.
The expected result of
array = [{"book_id"=>14238, "id"=>[8,5]}, {"book_id"=>10743, "id"=>[7,9]}]
isn't a good structure if you're going to do any sort of lookups in it. Imagine having hundreds or thousands of elements and needing to find "book_id" == 10743
in the array, especially if it's not a sorted list; The array would have to be walked until the desired entry was found. That is a slow process.
Instead, simplify the structure to a simple hash, allowing you to easily locate a value using a simple Hash lookup:
hash[10743]
The lookup will never slow down.
If the resulting data is to be iterated in order by sorting, use
sorted_keys = hash.keys.sort
and
hash.values_at(*sorted_keys)
to extract the values in the sorted order. Or iterate over the hash if the key/values need to be extracted, perhaps for insertion into a database.
Upvotes: 0
Reputation: 1985
I can't say that this is easy to understand, but it is concise:
array.group_by {|item| item["book_id"] }.map do |k, v|
{ "book_id" => k, "id" => v.map {|item| item["id"] } }
end
=> [{"book_id"=>14238, "id"=>[8, 5]}, {"book_id"=>10743, "id"=>[7, 9]}]
The first transformation done by group_by
rearranges your array so that items with the same book_id
are grouped together:
array.group_by {|item| item["book_id"] }
=> {14238=>[{"id"=>8, "book_id"=>14238}, {"id"=>5, "book_id"=>14238}], 10743=>[{"id"=>7, "book_id"=>10743}, {"id"=>9, "book_id"=>10743}]}
The second transformation (map
) reformats the hash produced by the group_by into a list of hashes, and the second map
collects the id's into a list.
Upvotes: 2