web-dev-qa-db-fra.com

Comment renommer avec élégance toutes les clés d'un hachage en Ruby?

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
       }
90
Chanpory
ages = { "Bruce" => 32, "Clark" => 28 }
mappings = {"Bruce" => "Bruce Wayne", "Clark" => "Clark Kent"}

ages.map {|k, v| [mappings[k], v] }.to_h
181
Jörg W Mittag

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.

49
barbolo

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 }
9
steel

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.

8
the Tin Man

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
5
Greg

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
3
TFR

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}
2
Tyler Rick

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}
2
Cary Swoveland
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
1
dan

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"
}
0
Jon Kern
>> 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.

0
juanpastas