问题
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