How to change all the keys of a hash by a new set of given keys

21,722

Solution 1

Ruby 2.5 has Hash#transform_keys! method. Example using a map of keys

h = {a: 1, b: 2, c: 3}
key_map = {a: 'A', b: 'B', c: 'C'}

h.transform_keys! {|k| key_map[k]}
# => {"A"=>1, "B"=>2, "C"=>3} 

You can also use symbol#toproc shortcut with transform_keys Eg:

h.transform_keys! &:upcase
# => {"A"=>1, "B"=>2, "C"=>3}

Solution 2

Assuming you have a Hash which maps old keys to new keys, you could do something like

hsh.transform_keys(&key_map.method(:[]))

Solution 3

i assume you want to change the hash keys without changing the values:

hash = {
   "nr"=>"123",
   "name"=>"Herrmann Hofreiter",
   "pferd"=>"010 000 777",
   "land"=>"hight land"
}
header = ["aa", "bb", "cc", "dd"]
new_hash = header.zip(hash.values).to_h

Result:

{
   "aa"=>"123",
   "bb"=>"Herrmann Hofreiter",
   "cc"=>"010 000 777",
   "dd"=>"high land"
}

Solution 4

Another way to do it is:

hash = {
  'foo' => 1,
  'bar' => 2
}

new_keys = {
  'foo' => 'foozle',
  'bar' => 'barzle'
}

new_keys.values.zip(hash.values_at(*new_keys.keys)).to_h 
# => {"foozle"=>1, "barzle"=>2}

Breaking it down:

new_keys
.values # => ["foozle", "barzle"]
.zip(
  hash.values_at(*new_keys.keys) # => [1, 2]
) # => [["foozle", 1], ["barzle", 2]]
.to_h 
# => {"foozle"=>1, "barzle"=>2}

It's benchmark time...

While I like the simplicity of Jörn's answer, I'm wasn't sure it was as fast as it should be, then I saw selvamani's comment:

require 'fruity'

HASH = {
  'foo' => 1,
  'bar' => 2
}

NEW_KEYS = {
  'foo' => 'foozle',
  'bar' => 'barzle'
}

compare do
  mittag    { HASH.dup.map {|k, v| [NEW_KEYS[k], v] }.to_h }
  ttm       { h = HASH.dup; NEW_KEYS.values.zip(h.values_at(*NEW_KEYS.keys)).to_h }
  selvamani { h = HASH.dup; h.keys.each { |key| h[NEW_KEYS[key]] = h.delete(key)}; h }
end

# >> Running each test 2048 times. Test will take about 1 second.
# >> selvamani is faster than ttm by 39.99999999999999% ± 10.0%
# >> ttm is faster than mittag by 10.000000000000009% ± 10.0%

These are running very close together speed wise, so any will do, but 39% pays off over time so consider that. A couple answers were not included because there are potential flaws where they'd return bad results.

Solution 5

The exact solution would depend on the format that you have the new keys in (or if you can derive the new key from the old key.)

Assuming you have a hash h whose keys you want to modify and a hash new_keys that maps the current keys to the new keys you could do:

h.keys.each do |key|
  h[new_keys[key]] = h[key] # add entry for new key
  k.delete(key)             # remove old key
end
Share:
21,722

Related videos on Youtube

JCLL
Author by

JCLL

Updated on January 10, 2020

Comments

  • JCLL
    JCLL over 4 years

    How do I change all the keys of a hash by a new set of given keys?

    Is there a way to do that elegantly?

  • CalebHC
    CalebHC almost 13 years
    You are the man! Your hash is totally awesome! Thanks again for this gem! :)
  • sawa
    sawa over 11 years
    This will remove a value for key when new_keys accidentally happens to return key itself for some key. barbolos's answer to this question: stackoverflow.com/questions/4137824 overcomes this problem.
  • Selvamani
    Selvamani over 8 years
    instead of this you can use h.keys.each { |key| h[new_keys[key]] = h.delete(key)}
  • PJP
    PJP about 8 years
    This breaks if key_map[k] is nil.
  • PJP
    PJP about 8 years
    It also assumes you have key_map defined as a hash of key/value pairs where the key is the old key and the value is the new key being swapped in.
  • PJP
    PJP about 8 years
    @Selvamani, see my answer, and create an answer from your comment.
  • Josh
    Josh over 6 years
    I think calling merge for each key will be comparatively slow
  • Cary Swoveland
    Cary Swoveland over 6 years
    @Josh, you are correct. I re-ran @theTinMan's benchmark with my two methods added and obtained the following results: "selvamani is faster than ttm by 19.99% ± 1.0%; ttm is similar to caryewo (uses each_with_object); caryewo is similar to mittag; mittag is faster than caryred (uses reduce) by 70.0% ± 10.0%".
  • smileart
    smileart over 6 years
    A moment of self promotion ☺️, but just to leave it here: if anything a bit more complex needed (like select particular keys at the same time or coerce values' types and so on), I put together a tiny lib: github.com/smileart/hash_remapper
  • JCLL
    JCLL about 6 years
    You take advantage of the fact that order is preserved in hashes (since 2.0 I think) and by assumption (not given in my question) that new keys are also given in the same order as in the initial hash.
  • buncis
    buncis about 3 years
    anyone could explain how the &key_map.method(:[]) works?