Sanyam Jain
Sanyam Jain

Reputation: 383

Ruby Array Having Hash Pairs?

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

Answers (3)

Cary Swoveland
Cary Swoveland

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

the Tin Man
the Tin Man

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

Todd Agulnick
Todd Agulnick

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

Related Questions