问题
Each key in a hash has a value that's also a hash.
{ 100 => { 1 => 'ruby', 2 => 'enumerables' }, 50 => { 3 => 'can', 4 => 'cause' }, 15 => { 5 => 'occassional', 6 => 'insanity' } }
For each hash object, I want to discard the top-level key, and replace it with the key and value of the nested hash objects.
{
1 => 'ruby',
2 => 'enumerables',
3 => 'can',
4 => 'cause',
5 => 'occasional',
6 => 'insanity'
}
I have it working, but my method uses a merge!
, and requires creating another hash to store the values. I'm curious to see if it can be done in one line. I tried to use reduce()
, but could not make it work.
回答1:
This works:
hash.values.inject(&:merge)
Edit: Another option, using reduce
(which is the same as inject
), and noting tokland's comment that to_proc is automatically called when you use a symbol:
hash.values.reduce(:merge)
Then it becomes not only concise but very readable.
回答2:
I like the answer by @MarkThomas best, but for speed and memory efficiency I suggest:
flatter = {}.tap{ |h| original.values.each{ |h2| h.merge!(h2) } }
Benchmarking 200,000 iterations of the current answers shows this to be the fastest:
user system total real
Phrogz 0.710000 0.020000 0.730000 ( 0.728706)
Joshua Creek 0.830000 0.010000 0.840000 ( 0.830700)
Mark Thomas symbol 1.460000 0.020000 1.480000 ( 1.486463)
Mark Thomas to_proc 1.540000 0.030000 1.570000 ( 1.565354)
Tim Peters 1.650000 0.030000 1.680000 ( 1.678283)
Since the comment by @tokland—original.values.reduce(:update)
—modifies the original hash we cannot compare it directly to the other methods. However, if we modify all tests to put a duplicate of the first hash back into the original each iteration, @tokland's answer becomes the fastest, though still not quite as fast as mine:
user system total real
tokland's destroyer 0.760000 0.010000 0.770000 ( 0.772774)
Phrogz 1.020000 0.020000 1.040000 ( 1.034755)
Joshua Creek 1.060000 0.000000 1.060000 ( 1.063874)
Mark Thomas symbol 1.780000 0.040000 1.820000 ( 1.816909)
Mark Thomas to_proc 1.790000 0.030000 1.820000 ( 1.819014)
Tim Peters 1.800000 0.040000 1.840000 ( 1.827984)
If you need absolute speed and it's OK to modify the original values, use @tokland's answer. If you do so and want to preserve the original unmerged hashes unscathed, then you can:
first_k,orig_v = original.each{ |k,v| break [k,v.dup] }
merged = original.values.reduce(:update)
original[first_k] = orig_v
Note that your question title says traverse; if you don't really want to merge the values—if you might want to visit a duplicate key twice instead of last-in-wins—then simply do:
original.values.each{ |h| h.each{ |k,v|
# hey, we're traversing inside!
} }
回答3:
Since you don't value about the top level keys, use #values to get an array of the values (in this case, also hashes). Then you can use #inject to build up a new hash, merging them as you go.
yourhash.values.inject{|hash, thing| hash.merge(thing)}
There are probably other ways to do it.
回答4:
Hash[original_hash.values.flat_map(&:to_a)]
回答5:
Just took a stab at it, first try was brute force and better methods (in both senses of the word...) are out there.
h.map {|k,v| v}.inject({}) {|i,h| i.merge(h)}
回答6:
While it isn't as brief as some of the other answers, I think each_with_object
deserves a representation.
output = input.each_with_object Hash.new do |(_,subhash), hash|
hash.merge! subhash
end
来源:https://stackoverflow.com/questions/8997428/how-to-traverse-this-hash-within-one-line