2010-11-09 11 views

risposta

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

ages.map {|k, v| [mappings[k], v] }.to_h 
+0

Grazie, è fantastico! Ora, se voglio solo cambiare alcuni dei nomi delle chiavi, c'è un modo per verificare se esiste una mappatura per la chiave? – Chanpory

+28

Basta usare 'mappings [k] || k' invece di 'mappings [k]' sopra e lascerà le chiavi non nella mappatura così com'è. –

+0

Brillante! Grazie mille :-) – Chanpory

1
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 
+0

età = mappature. inject ({}) {| memo, (old_key, new_key) | memo [new_key] = age [old-key]; appunto} – frogstarr78

5

I scimmia-patched la classe per gestire hash nidificati e Array:

# 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 
+0

Molto utile. Adattato alle mie esigenze per far risaltare lo stile delle chiavi del cammello. – idStar

+0

bello! potrebbe essere più flessibile controllare '.responds_to? (: rename_keys)' invece di '.kind_of? (Hash)', e l'equivalente di 'Array', cosa ne pensi? – caesarsol

48

Mi è piaciuta la risposta di Jörg W Mittag, ma può essere migliorata.

Se si desidera rinominare le chiavi del vostro Hash corrente e non per creare un nuovo hash con le chiavi rinominati, il seguente frammento fa esattamente questo:

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 

C'è anche il vantaggio di un solo rinominando la necessaria chiavi.

Considerazioni sulle prestazioni:

in base alla risposta the Tin Man s', la mia risposta è più veloce circa il 20% di risposta di Jörg W Mittag per un hash con solo due tasti. È possibile ottenere prestazioni ancora più elevate per gli hash con molti tasti, specialmente se ci sono solo poche chiavi da rinominare.

+0

Mi piace. Una cosa che mi ha colpito è che ho usato questo nella chiamata as_json(), e sebbene le chiavi degli attributi principali siano state convertite in string, the options.merge (: methods => [: blah]), che è una chiave nella mappa, non una stringa. – peterept

+1

@peterept puoi provare options.with_indifferent_access.merge (: methods => [: blah]). Ciò renderà le opzioni di accesso stringhe o simboli come chiavi. – barbolo

+0

Ama la risposta ... ma sono confuso su come funzioni effettivamente. Come viene impostato il valore su ciascun set? –

3

Se l'hash di mappatura sarà più piccolo dell'Hash di dati, quindi eseguire iterazione sulle mappature. Questo è utile per rinominare alcuni campi in una grande Hash:

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 
2

Il Facets gemma fornisce un metodo rekey che fa esattamente quello che hai intenzione.

Finché stai bene con una dipendenza dal Facets gemma, si può passare un hash di mappature per rekey e restituirà un nuovo hash con le nuove chiavi:

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} 

Se si desidera per modificare le età hash in luogo, è possibile utilizzare la versione rekey!:

ages.rekey!(mappings) 
ages 
=> {"Bruce Wayne"=>32, "Clark Kent"=>28} 
8

solo per vedere cosa è stato più veloce:

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 

quali uscite:

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% 

Attenzione: la soluzione del bilanciere utilizza if mappings[k], che farà sì che l'hash risultante di sbagliare se mappings[k] si traduce in un valore nullo.

0
>> 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"} 

Questo passerebbe solo attraverso l'hash del nome.

0

Ho usato questo per consentire i nomi di "amici" in una tabella cetriolo per essere analizzati in classe attributi tale che Factory Girl potrebbe creare un'istanza:

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 

Per quello che vale, la tabella di cetriolo assomiglia a questo :

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 | 

E map_to_keys assomiglia a questo:

{ 
       "Name" => "name", 
      "Subdomain" => "subdomain", 
     "Phone Number" => "phone_number", 
      "Address" => "address", 
    "Billing Contact" => "billing_contact", 
    "Billing Address" => "billing_address" 
} 
+0

Grazie per la modifica, @the Tin Man –

8

C'è il each_with_object metodo di sotto-utilizzate in Ruby così:

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

ages.each_with_object({}) { |(k, v), memo| memo[mappings[k]] = v } 
+0

'each_with_object' è decisamente sottoutilizzato, ed è più chiaro e facile da ricordare di' iniettare'. È stata un'aggiunta gradita quando è stata presentata. –

+0

Penso che questa sia la migliore risposta. Si potrebbe anche usare '|| k' per gestire il caso in cui i mapping non hanno la chiave corrispondente: '' 'age.each_with_object ({}) {| (k, v), memo | memo [mappings [k] || k] = v} '' ' – coisnepe

2

Si potrebbe desiderare di utilizzare Object#tap per evitare la necessità di restituire ages dopo che i tasti sono stati modificati:

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} 
Problemi correlati