How to get this hash in one line?

Each key in the hash has a value, which is 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 reset the top-level key and replace it with the key and the value of the nested hash objects.

{ 1 => 'ruby', 2 => 'enumerables', 3 => 'can', 4 => 'cause', 5 => 'occasional', 6 => 'insanity' } 

It works for me, but my method uses merge! and requires the creation of another hash to store the values. I am curious to see if this can be done on one line. I tried using reduce() but could not get it to work.

+3
source share
6 answers

It works:

 hash.values.inject(&:merge) 

Edit: Another option, using reduce (the same as inject ), and noting the tokland comment that to_proc is automatically called when using the symbol:

 hash.values.reduce(:merge) 

Then it becomes not only short, but also very readable.

+10
source

I like @MarkThomas answer best, but for speed and memory efficiency I suggest:

 flatter = {}.tap{ |h| original.values.each{ |h2| h.merge!(h2) } } 

Benchmarking 200,000 iterations of current answers shows that this is 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 @ tokland- original.values.reduce(:update) -modulates the original hash, we cannot compare it directly with other methods. However, if we modify all the tests to put a duplicate of the first hash back in the original each iteration, @tokland's answer will become the fastest, although still not as fast as mine:

  user system total real tokland 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 OK, to change the initial values, use @tokland's answer. If you do this and want to keep the original unmixed hashes intact, you can:

 first_k,orig_v = original.each{ |k,v| break [k,v.dup] } merged = original.values.reduce(:update) original[first_k] = orig_v 

Please note that the title of the question indicates traverse ; if you really do not want to combine the values ​​- if you want to double-click the duplicate key instead of the last in the winnings, just do:

 original.values.each{ |h| h.each{ |k,v| # hey, we're traversing inside! } } 
+5
source

Since you don't value top-level keys, use #values ​​to get an array of values ​​(in this case also hashes). You can then use #inject to create a new hash, merge with it along the way.

 yourhash.values.inject{|hash, thing| hash.merge(thing)} 

There may be other ways to do this.

+3
source
 Hash[original_hash.values.flat_map(&:to_a)] 
+3
source

Just took a hit at him, first tried brute force and the best methods (in both senses of the word ...) there.

 h.map {|k,v| v}.inject({}) {|i,h| i.merge(h)} 
+2
source

Although this is not as brief as some of the other answers, I think each_with_object deserves a presentation.

 output = input.each_with_object Hash.new do |(_,subhash), hash| hash.merge! subhash end 
+2
source

Source: https://habr.com/ru/post/1392848/


All Articles