Ruby - How to invert a Hash with an array values?

天大地大妈咪最大 提交于 2019-12-11 10:42:19

问题


Looking for an answer that works on Ruby 1.8.7 :

For example lets say I have a hash like this:

{"Book Y"=>["author B", "author C"], "Book X"=>["author A", "author B", "author C"]}

and I want to get this:

{ 
    "author A" => ["Book X"],
    "author B" => ["Book Y", "Book X"],
    "author C" => ["Book Y", "Book X"] 
}

I wrote a really long method for it, but with large datasets, it is super slow.

Any elegant solutions?


回答1:


h = {"Book Y"=>["author B", "author C"], "Book X"=>["author A", "author B", "author C"]}

p h.inject(Hash.new([])) { |memo,(key,values)|
  values.each { |value| memo[value] += [key] }
  memo
}
# => {"author B"=>["Book Y", "Book X"], "author C"=>["Book Y", "Book X"], "author A"=>["Book X"]}



回答2:


This is one way:

g = {"Book Y"=>["author B", "author C"],
     "Book X"=>["author A", "author B", "author C"]}

g.each_with_object({}) do |(book,authors),h|
  authors.each { |author| (h[author] ||= []) << book }
end
  #=> {"author B"=>["Book Y", "Book X"],
  #    "author C"=>["Book Y", "Book X"],
  #    "author A"=>["Book X"]} 

The steps:

enum = g.each_with_object({})
  #=> #<Enumerator: {"Book Y"=>["author B", "author C"],
  #   "Book X"=>["author A", "author B", "author C"]}:each_with_object({})> 

We can see the elements of enum, which it will pass into the block, by converting it to an array:

enum.to_a
  #=> [[["Book Y", ["author B", "author C"]], {}],
  #    [["Book X", ["author A", "author B", "author C"]], {}]]

The first element of enum passed to the block and assigned to the block variables is:

(book,authors),h = enum.next
  #=> [["Book Y", ["author B", "author C"]], {}] 
book
  #=> "Book Y" 
authors
  #=> ["author B", "author C"] 
h
  #=> {} 

enum1 = authors.each
  #=> #<Enumerator: ["author B", "author C"]:each>
author = enum1.next
  #=> "author B"
(h[author] ||= []) << book
  #=> (h["author B"] ||= []) << "Book Y"
  #=> (h["author B"] = h["author B"] || []) << "Book Y"
  #=> (h["author B"] = nil || []) << "Book Y"
  #=> h["author B"] = ["Book Y"]
  #=> ["Book Y"]
h #=> {"author B"=>["Book Y"]} 

Next:

author = enum1.next
  #=> "author C" 
(h[author] ||= []) << book
h #=> {"author B"=>["Book Y", "Book Y"], "author C"=>["Book Y"]} 

Having finished with "Book X",

(book,authors),h = enum.next
  #=> [["Book X", ["author A", "author B", "author C"]],
  #    {"author B"=>["Book Y", "Book Y"], "author C"=>["Book Y"]}]
book
  #=> "Book X" 
authors
  #=> ["author A", "author B", "author C"] 
h
  #=> {"author B"=>["Book Y", "Book Y"], "author C"=>["Book Y"]} 

We now repeat the same calculations as as we did for "Book X". The only difference is that when we encounter:

(h[author] ||= []) << book

which is equivalent to

(h[author] = h[author] || []) << book

in most case h[author] on the right of the equals sign will not be nil (e.g., it may be ["Book X"], in which case the above expression reduces to:

h[author] << book

Addendum

For versions of Ruby before the war (e.g., 1.8.7), just initialize the hash first and use each instead of each_with_object (we got the latter with 1.9. I was too young for 1.8.7, but I often wonder how people got along without it.) You just need to remember to return h at the end, as each just returns its receiver.

So change it to:

h = {}
g.each do |book,authors|
  authors.each { |author| (h[author] ||= []) << book }
end
h
  #=> {"author B"=>["Book Y", "Book X"],
  #    "author C"=>["Book Y", "Book X"],
  #    "author A"=>["Book X"]} 



回答3:


I would do something like this in Ruby 1.8:

hash = {"Book Y"=>["author B", "author C"], "Book X"=>["author A", "author B", "author C"]}

library = Hash.new { |h, k| h[k] = [] }

hash.each do |book, authors|
  authors.each { |author| library[author] << book }
end

puts library 
#=> {"author B"=>["Book Y", "Book X"], "author C"=>["Book Y", "Book X"], "author A"=>["Book X"]}


来源:https://stackoverflow.com/questions/28655221/ruby-how-to-invert-a-hash-with-an-array-values

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!