How to elegantly rename all keys in a hash in Ruby?

ages = { 'Bruce' => 32, 'Clark' => 28 }
mappings = { 'Bruce' => 'Bruce Wayne', 'Clark' => 'Clark Kent' }

ages.transform_keys(&mappings.method(:[]))
#=> { 'Bruce Wayne' => 32, 'Clark Kent' => 28 }

I liked Jörg W Mittag's answer, but if you want to rename the keys of your current Hash and not to create a new Hash with the renamed keys, the following snippet does exactly that:

ages = { "Bruce" => 32, "Clark" => 28 }
mappings = {"Bruce" => "Bruce Wayne", "Clark" => "Clark Kent"}

ages.keys.each { |k| ages[ mappings[k] ] = ages.delete(k) if mappings[k] }
ages

There's also the advantage of only renaming the necessary keys.

Performance considerations:

Based on the Tin Man's answer, my answer is about 20% faster than Jörg W Mittag's answer for a Hash with only two keys. It may get even higher performance for Hashes with many keys, specially if there are just a few keys to be renamed.


There's the under-utilized each_with_object method in Ruby as well:

ages = { "Bruce" => 32, "Clark" => 28 }
mappings = { "Bruce" => "Bruce Wayne", "Clark" => "Clark Kent" }

ages.each_with_object({}) { |(k, v), memo| memo[mappings[k]] = v }

Just to see what was faster:

require 'fruity'

AGES = { "Bruce" => 32, "Clark" => 28 }
MAPPINGS = {"Bruce" => "Bruce Wayne", "Clark" => "Clark Kent"}

def jörg_w_mittag_test(ages, mappings)
  Hash[ages.map {|k, v| [mappings[k], v] }]
end

require 'facets/hash/rekey'
def tyler_rick_test(ages, mappings)
  ages.rekey(mappings)
end

def barbolo_test(ages, mappings)
  ages.keys.each { |k| ages[ mappings[k] ] = ages.delete(k) if mappings[k] }
  ages
end

class Hash
  def tfr_rekey(h)
    dup.tfr_rekey! h
  end

  def tfr_rekey!(h)
    h.each { |k, newk| store(newk, delete(k)) if has_key? k }
    self
  end
end

def tfr_test(ages, mappings)
  ages.tfr_rekey mappings
end

class Hash
  def rename_keys(mapping)
    result = {}
    self.map do |k,v|
      mapped_key = mapping[k] ? mapping[k] : k
      result[mapped_key] = v.kind_of?(Hash) ? v.rename_keys(mapping) : v
      result[mapped_key] = v.collect{ |obj| obj.rename_keys(mapping) if obj.kind_of?(Hash)} if v.kind_of?(Array)
    end
    result
  end
end

def greg_test(ages, mappings)
  ages.rename_keys(mappings)
end

compare do
  jörg_w_mittag { jörg_w_mittag_test(AGES.dup, MAPPINGS.dup) }
  tyler_rick    { tyler_rick_test(AGES.dup, MAPPINGS.dup)    }
  barbolo       { barbolo_test(AGES.dup, MAPPINGS.dup)       }
  greg          { greg_test(AGES.dup, MAPPINGS.dup)          }
end

Which outputs:

Running each test 1024 times. Test will take about 1 second.
barbolo is faster than jörg_w_mittag by 19.999999999999996% ± 10.0%
jörg_w_mittag is faster than greg by 10.000000000000009% ± 10.0%
greg is faster than tyler_rick by 30.000000000000004% ± 10.0%

Caution: barbell's solution uses if mappings[k], which will cause the resulting hash to be wrong if mappings[k] results in a nil value.

Tags:

Ruby

Hash

Key