Comment renommer élégamment toutes les clés dans un hachoir en Ruby? [dupliquer]

cette question a déjà une réponse ici:

j'ai un Rubis de hachage:

ages = { "Bruce" => 32,
         "Clark" => 28
       }

en supposant que j'ai un autre hachage de noms de remplacement, y a-t-il une façon élégante de renommer toutes les touches pour que je finisse avec:

ages = { "Bruce Wayne" => 32,
         "Clark Kent" => 28
       }
87
demandé sur the Tin Man 2010-11-09 22:48:32

11 réponses

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

ages.map {|k, v| [mappings[k], v] }.to_h
176
répondu Jörg W Mittag 2016-02-01 04:18:03

j'ai aimé la réponse DE Jörg W Mittag, mais elle peut être améliorée.

si vous voulez renommer les clés de votre hachage actuel et ne pas créer un nouveau hachage avec les clés renommées, le snippet 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.

facteurs de Performance:

Basé sur l'Homme de fer-blanc 's réponse, ma réponse est d'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.

48
répondu barbolo 2017-05-23 12:10:06

il y a aussi la méthode sous-utilisée each_with_object dans Ruby:

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

ages.each_with_object({}) { |(k, v), memo| memo[mappings[k]] = v }
9
répondu steel 2016-04-06 13:33:58

Juste pour voir ce qui a été 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

qui produit:

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 causera le hachage résultant à tort si mappings[k] se traduit par une valeur nulle.

8
répondu the Tin Man 2016-03-28 23:12:02

je singe-patché la classe afin de gérer imbriqués les Hachages et les Tableaux:

   #  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
répondu Greg 2016-03-28 22:18:21

si le hachage de mappage sera plus petit que le hachage de données alors itérer sur mappings à 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
répondu TFR 2016-03-28 22:19:00

vous pouvez utiliser objet#tap pour éviter la nécessité de retourner ages après que les clés ont été modifiées:

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
répondu Cary Swoveland 2015-06-20 19:55:53

Le Facettes gem fournit un rekey méthode qui fait exactement ce que vous êtes désireux.

tant que vous êtes d'accord avec une dépendance sur le facettes gem, vous pouvez passer un hachage de mappings à rekey et il retournera 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 voulez modifier les âges hachage en place, vous pouvez utiliser la rekey! version:

ages.rekey!(mappings)
ages
=> {"Bruce Wayne"=>32, "Clark Kent"=>28}
2
répondu Tyler Rick 2016-03-28 22:22:22
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
répondu dan 2010-11-09 19:56:37
>> 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"}

cette boucle serait juste à travers renames hash.

0
répondu juanpastas 2014-03-04 20:40:55

j'ai utilisé ceci pour permettre des noms "amicaux" dans une table de concombre pour être divisé en attributs de classe tels que la fille D'usine pourrait 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 de concombre 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 à ceci:

{
               "Name" => "name",
          "Subdomain" => "subdomain",
       "Phone Number" => "phone_number",
            "Address" => "address",
    "Billing Contact" => "billing_contact",
    "Billing Address" => "billing_address"
}
0
répondu Jon Kern 2016-03-28 22:25:02