Come modificare i valori hash?


126

Vorrei sostituirli valuecon un hash value.some_method.

Ad esempio, per un dato hash semplice:

{"a" => "b", "c" => "d"}` 

ogni valore dovrebbe essere .upcased, quindi sembra:

{"a" => "B", "c" => "D"}

Ho provato #collecte ho #mapsempre recuperato le matrici. Esiste un modo elegante per farlo?

AGGIORNARE

Dannazione, ho dimenticato: l'hash si trova in una variabile di istanza che non dovrebbe essere modificata. Ho bisogno di un nuovo hash con i valori modificati, ma preferirei non definire quella variabile in modo esplicito e quindi passare in rassegna l'hash riempendolo. Qualcosa di simile a:

new_hash = hash.magic{ ... }

il secondo esempio nella mia risposta restituisce un nuovo hash. commentalo se vuoi che espanda la magia che sta facendo #inject.
kch,

Risposte:


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

oppure, se preferisci farlo in modo non distruttivo, e restituire un nuovo hash invece di modificare my_hash:

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

Quest'ultima versione ha l'ulteriore vantaggio di poter trasformare anche le chiavi.


26
Il primo suggerimento non è appropriato; la modifica di un elemento enumerabile durante l'iterazione porta a comportamenti indefiniti - questo vale anche in altre lingue. Ecco una risposta di Matz (risponde a un esempio usando un array, ma la risposta è generica): j.mp/tPOh2U
Marcus

4
@Marcus Concordo sul fatto che in generale tale modifica dovrebbe essere evitata. Ma in questo caso è probabilmente sicuro, perché la modifica non cambia le iterazioni future. Ora se fosse assegnata un'altra chiave (che non era quella passata al blocco), allora ci sarebbero problemi.
Kelvin,

36
@Adam @kch each_with_objectè una versione più conveniente di injectquando non vuoi terminare il blocco con l'hash:my_hash.each_with_object({}) {|(k, v), h| h[k] = v.upcase }
Kelvin

6
C'è anche una sintassi molto accurata per convertire un elenco di coppie in un hash, quindi puoi scriverea_new_hash = Hash[ my_hash.map { |k, v| [k, v.upcase] } ]
riscritto il

2
con Ruby 2 puoi scrivere.to_h
roman-roman


35

È possibile raccogliere i valori e convertirli nuovamente da Array in Hash.

Come questo:

config = Hash[ config.collect {|k,v| [k, v.upcase] } ]

23

Questo lo farà:

my_hash.each_with_object({}) { |(key, value), hash| hash[key] = value.upcase }

Al contrario injectdel vantaggio è che non è necessario restituire l'hash all'interno del blocco.


Mi piace che questo metodo non modifichi l'hash originale.
Christian Di Lorenzo,

10

Prova questa funzione:

h = {"a" => "b", "c" => "d"}
h.each{|i,j| j.upcase!} # now contains {"a" => "B", "c" => "D"}.

5
va bene, ma va notato che funziona solo quando j ha un metodo distruttivo che agisce da solo. Quindi non può gestire il caso generale value.some_method.
kch,

Bene, e con il suo aggiornamento, la tua seconda soluzione (non distruttiva) è la migliore.
Chris Doggett,

10

C'è un metodo per questo in ActiveSupport v4.2.0. Si chiama transform_valuese praticamente esegue solo un blocco per ogni coppia chiave-valore.

Dal momento che lo stanno facendo con un eachpenso che non c'è modo migliore che fare un giro.

hash = {sample: 'gach'}

result = {}
hash.each do |key, value|
  result[key] = do_stuff(value)
end

Aggiornare:

Da Ruby 2.4.0 puoi usare #transform_valuese #transform_values!.


2

Potresti voler fare un passo ulteriore e farlo su un hash nidificato. Certamente questo accade in buona parte con i progetti Rails.

Ecco un po 'di codice per garantire che un hash params sia in UTF-8:

  def convert_hash hash
    hash.inject({}) do |h,(k,v)|
      if v.kind_of? String
        h[k] = to_utf8(v) 
      else
        h[k] = convert_hash(v)
      end
      h
    end      
  end    

  # Iconv UTF-8 helper
  # Converts strings into valid UTF-8
  #
  # @param [String] untrusted_string the string to convert to UTF-8
  # @return [String] your string in UTF-8
  def to_utf8 untrusted_string=""
    ic = Iconv.new('UTF-8//IGNORE', 'UTF-8')
    ic.iconv(untrusted_string + ' ')[0..-2]
  end  

1

Faccio qualcosa del genere:

new_hash = Hash [* original_hash.collect {| chiave, valore | [chiave, valore + 1]}. appiattisci]

Ciò ti offre le possibilità di trasformare la chiave o il valore anche tramite qualsiasi espressione (ed è non distruttivo, ovviamente).


1

Ruby ha il tapmetodo ( 1.8.7 , 1.9.3 e 2.1.0 ) che è molto utile per cose come questa.

original_hash = { :a => 'a', :b => 'b' }
original_hash.clone.tap{ |h| h.each{ |k,v| h[k] = v.upcase } }
# => {:a=>"A", :b=>"B"}
original_hash # => {:a=>"a", :b=>"b"}

1

Rails-specifica

Nel caso in cui qualcuno abbia solo bisogno di chiamare to_smethod per ciascuno dei valori e non stia usando Rails 4.2 (che include il link deltransform_values metodo ), puoi fare quanto segue:

original_hash = { :a => 'a', :b => BigDecimal('23.4') }
#=> {:a=>"a", :b=>#<BigDecimal:5c03a00,'0.234E2',18(18)>}
JSON(original_hash.to_json)
#=> {"a"=>"a", "b"=>"23.4"}

Nota: è richiesto l'uso della libreria "json".

Nota 2: questo trasformerà anche le chiavi in ​​stringhe


1

Se sai che i valori sono stringhe, puoi richiamare il metodo di sostituzione durante il ciclo: in questo modo cambierai il valore.

Anche se questo è limitato al caso in cui i valori sono stringhe e quindi non risponde completamente alla domanda, ho pensato che potesse essere utile a qualcuno.


1
new_hash = old_hash.merge(old_hash) do |_key, value, _value|
             value.upcase
           end

# old_hash = {"a" => "b", "c" => "d"}
# new_hash = {"a" => "B", "c" => "D"}

È efficiente?
ioquatix,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.