How to Change All the Keys of a Hash by a New Set of Given Keys

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

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}

How to replace a hash key with another key

hash[:new_key] = hash.delete :old_key

How to make a particular change in all the keys of a hash?

You have to delete old keys from the hash and insert new ones,

use strict;
use warnings;

sub rename_keys {

my ($hash, $func) = @_;

my @k1 = my @k2 = keys %$hash;
$func->() for @k2;
@$hash{@k2} = delete @$hash{@k1};
}

my %hash = (
'IRQ_VSAFE_LPM_ASC_0' => '140',
'IRQ_VSAFE_LPM_ASC_1' => '141',
);
rename_keys(\%hash, sub { s/ASC_/ASC_1/ });

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 }

How to change all the dictionary keys in a for loop with d.items()?

It's never a good idea to change the object you're iterating over. Normally dict even throws an exception when you attempt it:

name_dict = {1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6}

for k, v in name_dict.items():
name_dict.pop(k)

RuntimeError: dictionary changed size during iteration

However in your case you add one item for every removed item. That makes it more convolved. To understand what's happening you need to know that a dictionary is somewhat like a sparse table. For example a dictionary like {1: 1, 3: 3, 5: 5} could look like this (this changed in Python 3.6, for 3.6 and newer the following isn't correct anymore):

hash    key    value
- - -
1 1 1
- - -
3 3 3
- - -
5 5 5
- - -
- - -
- - -

That's also the order in which it is iterated. So in the first iteration it will go to the second item (where the 1: 1 is stored). Let's assume you change the key to 2 and remove the key 1 the dict would look like this:

hash    key    value
- - -
- - -
2 2 1
3 3 3
- - -
5 5 5
- - -
- - -
- - -

But we're still at the second line, so the next iteration it will go to the next "not-empty" entry which is 2: 1. Oups ...

It's even more complicated with strings as keys because string hashes are randomized (on a per session basis) so the order inside the dictionary is unpredictable.

In 3.6 the internal layout was changed a bit but something similar happens here.

Assuming you have this loop:

name_dict = {1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6}

for k, v in name_dict.items():
# print(k, k+6, name_dict.__sizeof__())
name_dict[k+6] = name_dict.pop(k)
# print(name_dict)

The initial layout is like this:

key   value
1 1
2 2
3 3
4 4
5 5
6 1

The first loop removes 1 but adds 7. Because dictionaries are ordered in 3.6 this inserts a placeholder where 1 had been:

key   value
- -
2 2
3 3
4 4
5 5
6 1
7 2

This goes on until you replace 4 with 10.

key   value
- -
- -
- -
- -
5 5
6 1
7 2
8 3
9 4
10 5

But when you replace 5 with 11 the dictionary will need to increase it's size. Then something special happens: The placeholders are removed:

key   value
6 6
7 1
8 2
9 3
10 4
11 5

So, we were at position 5 in the last iteration and now we change line 6. But line 6 contains 11: 5 right now. Oups...

Never change the object you're iterating over: Don't mess with the keys during iteration (values are okay)!

You could instead keep a "translation table" (don't know if that violates your "without creating a new dict" requirement but you need some kind of storage to make your code work correctly) and do the renaming after the loop:

translate = {}
for k, v in name_dict.items():
print("This is the key: '%s' and this is the value '%s'\n" % (k, v) )
new_key = input("Please enter a new key: ")
translate[k] = new_key
time.sleep(4)

for old, new in translate.items():
name_dict[new] = name_dict.pop(old)

How to change Hash values?

my_hash.each { |k, v| my_hash[k] = v.upcase } 

or, if you'd prefer to do it non-destructively, and return a new hash instead of modifying my_hash:

a_new_hash = my_hash.inject({}) { |h, (k, v)| h[k] = v.upcase; h } 

This last version has the added benefit that you could transform the keys too.

How to change hash keys from `Symbol`s to `String`s?

simply call stringify_keys (or stringify_keys!)

http://apidock.com/rails/Hash/stringify_keys

How to remove a key from Hash and get the remaining hash in Ruby/Rails?

Rails has an except/except! method that returns the hash with those keys removed. If you're already using Rails, there's no sense in creating your own version of this.

class Hash
# Returns a hash that includes everything but the given keys.
# hash = { a: true, b: false, c: nil}
# hash.except(:c) # => { a: true, b: false}
# hash # => { a: true, b: false, c: nil}
#
# This is useful for limiting a set of parameters to everything but a few known toggles:
# @person.update(params[:person].except(:admin))
def except(*keys)
dup.except!(*keys)
end

# Replaces the hash without the given keys.
# hash = { a: true, b: false, c: nil}
# hash.except!(:c) # => { a: true, b: false}
# hash # => { a: true, b: false }
def except!(*keys)
keys.each { |key| delete(key) }
self
end
end

How to use the keys from a Hash to make a new Hash where each key's value is the key itself?

h = {'cat' => 'meow', 'dog' => nil}
#=> {"cat"=>"meow", "dog"=>nil}
Hash[h.keys.map{|k| [k,k]}]
#=> {"cat"=>"cat", "dog"=>"dog"}

Here's another, a bit dirty way (and I think it works in 1.8.6):

h.merge(h){|k,v,v| k}


Related Topics



Leave a reply



Submit