Como alterar os valores de hash?


126

Eu gostaria de substituir cada valueum com um hash value.some_method.

Por exemplo, para um hash simples:

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

todo valor deve ser .upcased, para que se pareça com:

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

Eu tentei #collecte #mapsempre recuperei as matrizes. Existe uma maneira elegante de fazer isso?

ATUALIZAR

Porra, eu esqueci: O hash está em uma variável de instância que não deve ser alterada. Eu preciso de um novo hash com os valores alterados, mas preferiria não definir essa variável explicitamente e fazer um loop sobre o hash que a preenchia. Algo como:

new_hash = hash.magic{ ... }

o segundo exemplo na minha resposta retorna um novo hash. comente se você quiser que eu expanda a mágica que o #inject está fazendo.
kch

Respostas:


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

ou, se você preferir fazê-lo de forma não destrutiva e retornar um novo hash em vez de modificar my_hash:

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

Esta última versão possui o benefício adicional de que você também pode transformar as chaves.


26
A primeira sugestão não é apropriada; modificar um elemento enumerável enquanto a iteração leva a um comportamento indefinido - isso também é válido em outros idiomas. Aqui está uma resposta de Matz (ele responde a um exemplo usando uma matriz, mas a resposta é genérica): j.mp/tPOh2U
Marcus

4
@ Marcus Concordo que, em geral, essa modificação deve ser evitada. Mas, neste caso, é provavelmente seguro, porque a modificação não altera as iterações futuras. Agora, se outra chave (que não foi a que foi passada para o bloco) foi atribuída, haveria problemas.
Kelvin

36
@Kch @ Adam each_with_objecté uma versão mais conveniente de injectquando você não quer terminar o bloco com o hash:my_hash.each_with_object({}) {|(k, v), h| h[k] = v.upcase }
Kelvin

6
Também existe uma sintaxe muito clara para converter uma lista de pares em um hash, para que você possa escrevera_new_hash = Hash[ my_hash.map { |k, v| [k, v.upcase] } ]
reescrito

2
com Ruby 2 você pode escrever.to_h
roman-romana


35

Você pode coletar os valores e convertê-lo de Matriz em Hash novamente.

Como isso:

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

23

Isso fará isso:

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

Em oposição à injectvantagem, você não precisa devolver o hash novamente dentro do bloco.


Eu gosto que este método não modifique o hash original.
Christian Di Lorenzo

10

Tente esta função:

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

5
isso é bom, mas deve-se notar que ele só funciona quando j possui um método destrutivo que age por si mesmo. Portanto, ele não pode lidar com o caso geral value.some_method.
kch

Bom ponto, e com a atualização dele, sua segunda solução (não destrutiva) é a melhor.
Chris Doggett

10

Há um método para isso no ActiveSupport v4.2.0. É chamado transform_valuese basicamente apenas executa um bloco para cada par de valores-chave.

Já que eles estão fazendo isso com um each, acho que não há maneira melhor do que fazer um loop.

hash = {sample: 'gach'}

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

Atualizar:

Desde o Ruby 2.4.0, você pode usar nativamente #transform_valuese #transform_values!.


2

Você pode dar um passo adiante e fazer isso em um hash aninhado. Certamente isso acontece com os projetos Rails.

Aqui está um código para garantir que um hash de params esteja no 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

Eu faço algo assim:

new_hash = Hash [* original_hash.collect {| chave, valor | [chave, valor + 1]}. achatar]

Isso fornece a você as facilidades para transformar a chave ou o valor por meio de qualquer expressão também (e é não destrutivo, é claro).


1

Ruby tem o tapmétodo ( 1.8.7 , 1.9.3 e 2.1.0 ) que é muito útil para coisas como esta.

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-specific

Caso alguém precise chamar o to_smétodo para cada um dos valores e não esteja usando o Rails 4.2 (que inclui o link dotransform_values método ), você pode fazer o seguinte:

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: O uso da biblioteca 'json' é necessário.

Nota 2: Isso também transformará chaves em strings


1

Se você souber que os valores são cadeias, você pode chamar o método de substituição neles enquanto estiver no ciclo: dessa forma, você alterará o valor.

Embora isso seja limitado ao caso em que os valores são cadeias de caracteres e, portanto, não responde totalmente à pergunta, achei que poderia ser útil a alguém.


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

Isso é eficiente?
ioquatix
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.