34

How would I reverse the elements in the hash, keeping the same values and keys, but reversing their order in the hash.

Like so:

{ "4" => "happiness", "10" => "cool", "lala" => "54", "1" => "spider" }

And convert that to:

{ "1" => "spider", "lala" => "54", "10" => "cool", "4" => "happiness" }

Or, perhaps I could run a each loop backwards, starting from the last element in the hash, rather than the first?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
slyv
  • 828
  • 1
  • 10
  • 18
  • 11
    Not to be confused with inverting a hash (switching keys with values). http://ruby-doc.org/core-2.0/Hash.html#method-i-invert – mahemoff Mar 06 '13 at 03:47

9 Answers9

69

You could convert the Hash to an Array, reverse that, and then convert it back to a Hash:

reversed_h = Hash[h.to_a.reverse]

Hash#to_a gives you an array of arrays, the inner arrays are simple [key,value] pairs, then you reverse that array using Array#reverse, and Hash[] converts the [key,value] pairs back into a Hash.

Ruby 2.1 adds an Array#to_h method so you can now say:

reversed_h = h.to_a.reverse.to_h
mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • 9
    reversed_h = Hash[h.to_a.collect(&:reverse)] – Minqi Pan Apr 16 '13 at 02:32
  • 1
    @P.S.V.R: There seems to be some confusion between the text of the question ("reverse the elements in the hash, keeping the **same values and keys**, but reversing their order in the hash" [emphasis mine]) and the example given. Apparently the text in the question was correct or slyv would have accepted steenslag's answer instead. – mu is too short Apr 16 '13 at 03:11
  • Thanks, it gives me a hint to do this: `{"happiness"=>"4", "cool"=>"10", "54"=>"lala", "spider"=>"1"}` – staticor Jun 29 '13 at 17:41
  • 1
    {"happiness"=>"4", "cool"=>"10", "54"=>"lala", "spider"=>"1"} by `Hash[h.to_a.map {|x| x=x.reverse}]` – staticor Jun 29 '13 at 17:42
  • This throws a few warnings on ruby 2.1.2, you should instead use Hash.new(h.to_a.reverse) – Waclock Jan 10 '15 at 19:39
  • @Waclock: What warnings are you getting with 2.1.2? Works fine for me. OTOH, with 2.1+ you can say `h.to_a.reverse.to_h`. – mu is too short Jan 10 '15 at 20:08
  • Actually I ended up using hash.reverse, Hash.new made an array of arrays. – Waclock Jan 11 '15 at 15:44
13

In Ruby 2.1+ you can combine reverse_each and to_h:

{foo: 1, bar: 2}.reverse_each.to_h
#=> {:bar=>2, :foo=>1}
Stefan
  • 109,145
  • 14
  • 143
  • 218
  • 1
    Nice, no temporary array that `reverse` would produce. [Array#to_h](http://ruby-doc.org/core-2.2.3/Array.html#method-i-to_h came with v 2.1, but [Enumerable#reverse_each](http://ruby-doc.org/core-1.9.3/Array.html) has been around since at least v1.9.3. Alternatively, `h.reverse_each.with_object({}) } |(k,v),g| g[k]=v }`. – Cary Swoveland Aug 26 '15 at 05:52
5

In pure ruby, you can do it by hash.map(&:reverse).to_h or hash.reverse_each.to_h

In rails, you can do it by hash.invert

khaled_gomaa
  • 3,382
  • 21
  • 24
4
hash = { "4" => "happiness", "10" => "cool", "lala" => "54", "1" => "spider" }
reversed_hash = Hash[hash.to_a.reverse]
aromero
  • 25,681
  • 6
  • 57
  • 79
4
h = { "4" => "happiness", "10" => "cool", "lala" => "54", "1" => "spider" }
p Hash[h.reverse_each.map{|e| e}]
#=> {"1"=>"spider", "lala"=>"54", "10"=>"cool", "4"=>"happiness"}

But this leaves a bad taste (just like the other answers, which work fine just like this one). If you have to do this, it could be an indication that a Hash was not the best choice.

steenslag
  • 79,051
  • 16
  • 138
  • 171
1

Alternatively, you can use reduce and merge to add the item to the front of a new hash:

hash = { "4" => "happiness", "10" => "cool", "lala" => "54", "1" => "spider" }
hash.reduce({}){ |memo, object| Hash[*object].merge(memo) }

but, that's crazy :D

Christopher Manning
  • 4,527
  • 2
  • 27
  • 36
1
reversed_h = Hash[h.to_a.collect(&:reverse)]
Minqi Pan
  • 2,722
  • 2
  • 22
  • 26
0

In Ruby 1.8.7, the order of elements in a hash is documented to be not under our control, so none of the above methods work. In Ruby 1.9.3, things work and are documented in the way that the other answers rely upon.

$ irb1.8
h = { "4" => "happiness", "10" => "cool", "lala" => "54", "1" => "spider" }
Hash[h.to_a().reverse()]
=> {"lala"=>"54", "1"=>"spider", "10"=>"cool", "4"=>"happiness"}
quit
$ irb1.9.1
h = { "4" => "happiness", "10" => "cool", "lala" => "54", "1" => "spider" }
Hash[h.to_a().reverse()]
=>{"1"=>"spider", "lala"=>"54", "10"=>"cool", "4"=>"happiness"}

The Ruby 1.8.7 way was ingrained so firmly for me that I misunderstood the question for quite some time. I thought it requested a way to Hash#invert: ie to transform the hash such that the range maps to the domain. That method discards duplicates. Luís Ramalho proffers a method that doesn't, but it's a bit clunky. This is a little shorter:

$ irb
def invertWithDuplicates(original)
  inverse = Hash.new() { |hash, key| hash[key] = []; }
  original.each_pair() { |key, value| inverse[value].push(key); }
  return inverse
end
h = { "4" => "happiness", "10" => "cool", "lala" => "54", "1" => "cool" }
invertWithDuplicates(h)
=> {"happiness"=>["4"], "cool"=>["1", "10"], "54"=>["lala"]}

Sorry to drift away from the OP's intended topic, though I submit that this does fit the post's title "Reverse a hash in Ruby".

Martin Dorey
  • 2,944
  • 2
  • 24
  • 16
  • 1
    This is best answer so far. In fact, I'd guess that in most languages, there's no guarantee of key ordering. The keys are a set, not a list. It's important to keep in mind. It's so prevalent, I'd never use any of the techniques on this page. Instead, I'd order the hash as needed depending on the view's needs. – Dogweather Oct 09 '13 at 02:05
0

if need:

hash = {:a => :x, :b => :y, :c => :y, :d => :z}

to:

{:x => [:a], :y => [:b, c], :z => [:d] }

can:

h={};hash.to_a.each{|e|h[e[1]]||=[];h[e[1]]<<e[0]};h
dmitryck
  • 61
  • 1
  • 4