1

This is the input hash:

p Score.periods #{"q1"=>0, "q2"=>1, "q3"=>2, "q4"=>3, "h1"=>4, "h2"=>5}

This is my current code to exchange the keys with the values, while converting the keys to symbols:

periods = Score.periods.inject({}) do |hsh,(k,v)|
  hsh[v] = k.to_sym
  hsh
end

Here is the result:

p periods #{0=>:q1, 1=>:q2, 2=>:q3, 3=>:q4, 4=>:h1, 5=>:h2}

It just seems like my code is clunky and it shouldn't take 4 lines to do what I'm doing here. Is there a cleaner way to write this?

appleLover
  • 14,835
  • 9
  • 33
  • 50
  • Duplicate of [Swapping keys and values in a hash](http://stackoverflow.com/questions/10989259/swapping-keys-and-values-in-a-hash) –  Apr 09 '14 at 23:44
  • 1
    Not a dup, @Cupcake, a close cousin. Here the keys are strings and are converted to values that are symbols. – Cary Swoveland Apr 09 '14 at 23:50
  • @CarySwoveland that's a good point. –  Apr 09 '14 at 23:50

3 Answers3

4

You can do this:

Hash[periods.values.zip(periods.keys.map(&:to_sym))]

Or if you're using a version of Ruby where to_h is available for arrays, you can do this:

periods.values.zip(periods.keys.map(&:to_sym)).to_h

What the two examples above do is make arrays of the keys and values of the original hash. Note that the string keys of the hash are mapped to symbols by passing to_sym to map as a Proc:

periods.keys.map(&:to_sym)
# => [:q1, :q2, :q3, :q4, :h1, :h2]

periods.values
# => [0, 1, 2, 3, 4, 5]

Then it zips them up into an array of [value, key] pairs, where each corresponding elements of values is matched with its corresponding key in keys:

periods.values.zip(periods.keys.map(&:to_sym))
# => [[0, :q1], [1, :q2], [2, :q3], [3, :q4], [4, :h1], [5, :h2]]

Then that array can be converted back into a hash using Hash[array] or array.to_h.

  • nice. i have a to_sym function in there too, so i think it has to be this, Score.periods.values.zip(Score.periods.keys.map{|k| k.to_sym}).to_h – appleLover Apr 09 '14 at 23:31
  • 1
    @appleLover I made it even shorter than that, check it out. –  Apr 09 '14 at 23:37
  • @appleLover is `Score` an Active Record relation from Ruby on Rails? If it is, be careful not to query the database more than once when calling `Score.periods` twice like that. I'm not sure if it's actually going to be queried twice in your case, just something you might want to double check that you're not doing. –  Apr 09 '14 at 23:42
3

The simplest way is:

data = {"q1"=>0, "q2"=>1, "q3"=>2, "q4"=>3, "h1"=>4, "h2"=>5}

Hash[data.invert.collect { |k, v| [ k, v.to_sym ] }]

The Hash[] method converts an array of key/value pairs into an actual Hash. Quite handy for situations like this.

If you're using Ruby on Rails this could be even easier:

data.symbolize_keys.invert
tadman
  • 208,517
  • 23
  • 234
  • 262
2
h = {"q1"=>0, "q2"=>1, "q3"=>2, "q4"=>3, "h1"=>4, "h2"=>5}

h.each_with_object({}) { |(k,v),g| g[v] = k.to_sym  }
  #=> {0=>:q1, 1=>:q2, 2=>:q3, 3=>:q4, 4=>:h1, 5=>:h2} 

The steps are as follows (for the benefit of Ruby newbies).

enum = h.each_with_object({})
  #=> #<Enumerator: {0=>"q1", 1=>"q2", 2=>"q3", 3=>"q4",
  #                  4=>"h1", 5=>"h2"}:each_with_object({})>

The elements that will be generated by the enumerator and passed to the block can be seen by converting the enumerator to an array, using Enumerable#entries or Enumerable#to_a.

enum.entries
  #=> [[["q1", 0], {}], [["q2", 1], {}], [["q3", 2], {}],
  #    [["q4", 3], {}], [["h1", 4], {}], [["h2", 5], {}]] 

Continuing,

enum.each { |(k,v),g| g[v] = k.to_sym  }
  #=> {0=>:q1, 1=>:q2, 2=>:q3, 3=>:q4, 4=>:h1, 5=>:h2}

In the last step, Enumerator#each passes the first element generated by enum to the block and assigns the three block variables. Consider the first element of enum that is passed to the block and the associated calculation of values for the three block variables. (I must first execute enum.rewind to reinitialize enum, as each above took the enumerator to its end. See Enumerator#rewind).

(k, v), g = enum.next
  #=> [["q1", 0], {}]
k #=> "q1"
v #=> 0
g #=> {}

See Enumerator#next. The block calculation is therefore

g[v] = k.to_sym
  #=> :q1

Hence,

g #=> {0=>:q1}

The next element of enum is passed to the block and similar calculations are performed.

(k, v), g = enum.next
  #=> [["q2", 1], {0=>:q1}] 
k #=> "q2" 
v #=> 1 
g #=> {0=>:q1} 
g[v] = k.to_sym
  #=> :q2 
g #=> {0=>:q1, 1=>:q2} 

The remaining calculations are similar.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100