J'ai un Ruby hachage:
ages = { "Bruce" => 32,
"Clark" => 28
}
En supposant que j'ai un autre hachage de noms de remplacement, existe-t-il une façon élégante de renommer toutes les clés afin que je me retrouve avec:
ages = { "Bruce Wayne" => 32,
"Clark Kent" => 28
}
ages = { "Bruce" => 32, "Clark" => 28 }
mappings = {"Bruce" => "Bruce Wayne", "Clark" => "Clark Kent"}
ages.map {|k, v| [mappings[k], v] }.to_h
J'ai aimé la réponse de Jörg W Mittag, mais elle peut être améliorée.
Si vous souhaitez renommer les clés de votre hachage actuel et ne pas créer un nouveau hachage avec les clés renommées, l'extrait de code suivant fait exactement cela:
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
Il y a aussi l'avantage de ne renommer que les clés nécessaires.
Considérations sur les performances:
Sur la base de la réponse de The Tin Man , ma réponse est environ 20% plus rapide que la réponse de Jörg W Mittag pour un Hash avec seulement deux clés. Il peut obtenir des performances encore plus élevées pour les hachages avec de nombreuses clés, surtout s'il n'y a que quelques clés à renommer.
Il y a le sous-utilisé each_with_object
méthode dans Ruby également:
ages = { "Bruce" => 32, "Clark" => 28 }
mappings = { "Bruce" => "Bruce Wayne", "Clark" => "Clark Kent" }
ages.each_with_object({}) { |(k, v), memo| memo[mappings[k]] = v }
Juste pour voir ce qui était plus rapide:
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
Quelles sorties:
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%
Attention: la solution de barbell utilise if mappings[k]
, ce qui entraînera une erreur de hachage résultant si mappings[k]
donne une valeur nulle.
J'ai patché la classe pour gérer les hachages et les tableaux imbriqués:
# Netsted Hash:
#
# str_hash = {
# "a" => "a val",
# "b" => "b val",
# "c" => {
# "c1" => "c1 val",
# "c2" => "c2 val"
# },
# "d" => "d val",
# }
#
# mappings = {
# "a" => "Apple",
# "b" => "boss",
# "c" => "cat",
# "c1" => "cat 1"
# }
# => {"Apple"=>"a val", "boss"=>"b val", "cat"=>{"cat 1"=>"c1 val", "c2"=>"c2 val"}, "d"=>"d val"}
#
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
Si le hachage de mappage sera plus petit que le hachage de données, effectuez une itération sur les mappages à la place. Ceci est utile pour renommer quelques champs dans un grand hachage:
class Hash
def rekey(h)
dup.rekey! h
end
def rekey!(h)
h.each { |k, newk| store(newk, delete(k)) if has_key? k }
self
end
end
ages = { "Bruce" => 32, "Clark" => 28, "John" => 36 }
mappings = {"Bruce" => "Bruce Wayne", "Clark" => "Clark Kent"}
p ages.rekey! mappings
La gemme Facets fournit une méthode rekey
qui fait exactement ce que vous voulez.
Tant que vous êtes d'accord avec une dépendance à la gemme Facets , vous pouvez passer un hachage de mappages à rekey
et il renverra un nouveau hachage avec les nouvelles clés:
require 'facets/hash/rekey'
ages = { "Bruce" => 32, "Clark" => 28 }
mappings = {"Bruce" => "Bruce Wayne", "Clark" => "Clark Kent"}
ages.rekey(mappings)
=> {"Bruce Wayne"=>32, "Clark Kent"=>28}
Si vous souhaitez modifier le hachage des âges en place, vous pouvez utiliser le rekey!
version:
ages.rekey!(mappings)
ages
=> {"Bruce Wayne"=>32, "Clark Kent"=>28}
Vous pouvez utiliser Object # tap pour éviter d'avoir à retourner ages
après la modification des clés:
ages = { "Bruce" => 32, "Clark" => 28 }
mappings = {"Bruce" => "Bruce Wayne", "Clark" => "Clark Kent"}
ages.tap {|h| h.keys.each {|k| (h[mappings[k]] = h.delete(k)) if mappings.key?(k)}}
#=> {"Bruce Wayne"=>32, "Clark Kent"=>28}
ages = { "Bruce" => 32, "Clark" => 28 }
mappings = {"Bruce" => "Bruce Wayne", "Clark" => "Clark Kent"}
ages = mappings.inject({}) {|memo, mapping| memo[mapping[1]] = ages[mapping[0]]; memo}
puts ages.inspect
J'ai utilisé cela pour permettre aux noms "conviviaux" d'une table Cucumber d'être analysés en attributs de classe de sorte que Factory Girl puisse créer une instance:
Given(/^an organization exists with the following attributes:$/) do |table|
# Build a mapping from the "friendly" text in the test to the lower_case actual name in the class
map_to_keys = Hash.new
table.transpose.hashes.first.keys.each { |x| map_to_keys[x] = x.downcase.gsub(' ', '_') }
table.transpose.hashes.each do |obj|
obj.keys.each { |k| obj[map_to_keys[k]] = obj.delete(k) if map_to_keys[k] }
create(:organization, Rack::Utils.parse_nested_query(obj.to_query))
end
end
Pour ce que ça vaut, la table Cucumber ressemble à ceci:
Background:
And an organization exists with the following attributes:
| Name | Example Org |
| Subdomain | xfdc |
| Phone Number | 123-123-1234 |
| Address | 123 E Walnut St, Anytown, PA 18999 |
| Billing Contact | Alexander Hamilton |
| Billing Address | 123 E Walnut St, Anytown, PA 18999 |
Et map_to_keys
ressemble à ça:
{
"Name" => "name",
"Subdomain" => "subdomain",
"Phone Number" => "phone_number",
"Address" => "address",
"Billing Contact" => "billing_contact",
"Billing Address" => "billing_address"
}
>> x={ :a => 'qwe', :b => 'asd'}
=> {:a=>"qwe", :b=>"asd"}
>> rename={:a=>:qwe}
=> {:a=>:qwe}
>> rename.each{|old,new| x[new] = x.delete old}
=> {:a=>:qwe}
>> x
=> {:b=>"asd", :qwe=>"qwe"}
Cela se déroulerait uniquement via le changement de nom de hachage.