Alterando todo valor em um hash no Ruby

170

Eu quero alterar todos os valores em um hash para adicionar '%' antes e depois do valor para

{ :a=>'a' , :b=>'b' }

deve ser alterado para

{ :a=>'%a%' , :b=>'%b%' }

Qual é a melhor forma de fazer isso?

theReverseFlick
fonte
1
Esclareça se você deseja alterar os objetos da string original, apenas modifique o original possui ou modifique nada.
Phrogz
1
Então você aceitou a resposta errada. (Sem ofensa para @pst destina, como eu, pessoalmente, também defendem a programação em vez de mutação objetos de estilo funcional.)
Phrogz
Mas ainda assim a abordagem foi boa soo
theReverseFlick 4/11/11

Respostas:

178

Se você deseja que as próprias seqüências de caracteres sejam alteradas no lugar (afetando de forma possível e desejável outras referências aos mesmos objetos de sequência de caracteres):

# Two ways to achieve the same result (any Ruby version)
my_hash.each{ |_,str| str.gsub! /^|$/, '%' }
my_hash.each{ |_,str| str.replace "%#{str}%" }

Se você deseja que o hash mude no lugar, mas não deseja afetar as strings (deseja que ele obtenha novas strings):

# Two ways to achieve the same result (any Ruby version)
my_hash.each{ |key,str| my_hash[key] = "%#{str}%" }
my_hash.inject(my_hash){ |h,(k,str)| h[k]="%#{str}%"; h }

Se você deseja um novo hash:

# Ruby 1.8.6+
new_hash = Hash[*my_hash.map{|k,str| [k,"%#{str}%"] }.flatten]

# Ruby 1.8.7+
new_hash = Hash[my_hash.map{|k,str| [k,"%#{str}%"] } ]
Phrogz
fonte
1
@ Andrew Marshall Você está certo, obrigado. No Ruby 1.8, Hash.[]não aceita uma matriz de pares de matrizes, requer um número par de argumentos diretos (daí a divisão inicial).
Phrogz
2
Na verdade, o Hash. [Key_value_pairs] foi introduzido no 1.8.7, então apenas o Ruby 1.8.6 não precisa do splat & flatten.
Marc-André Lafortune
2
@Aupajo Hash#eachproduz a chave e o valor para o bloco. Nesse caso, eu não me importei com a chave e, portanto, não nomeei nada útil. Os nomes de variáveis ​​podem começar com um sublinhado e, de fato, podem ser apenas um sublinhado. Não há benefício de desempenho em fazer isso, é apenas uma observação sutil de auto-documentação de que não estou fazendo nada com esse primeiro valor de bloco.
Phrogz 01/09/11
1
Eu acho que você quer dizer my_hash.inject(my_hash){ |h,(k,str)| h[k]="%#{str}%"; h }, tem que retornar o hash do bloco
aceofspades
1
Como alternativa, você pode usar o método each_value, que é um pouco mais fácil de entender do que usar um sublinhado para o valor da chave não utilizada.
Strand McCutchen
266

No Ruby 2.1 e superior, você pode fazer

{ a: 'a', b: 'b' }.map { |k, str| [k, "%#{str}%"] }.to_h
shock_one
fonte
5
Obrigado, era realmente o que eu estava procurando. Não sei por que você não é tão votado.
simperreault
7
Isso é muito lento e com muita memória RAM. O Hash de entrada é iterado para produzir um conjunto intermediário de matrizes aninhadas que são convertidas em um novo Hash. Ignorando o pico de uso da RAM, o tempo de execução é muito pior - comparando isso com as soluções de modificação no local em outra resposta, mostra 2,5s versus 1,5s no mesmo número de iterações. Desde Ruby é uma linguagem relativamente lento, evitando os bits lentos da língua lenta faz muito sentido :-)
Andrew Hodgkinson
2
@AndrewHodgkinson, em geral, concordo e não estou advogando não prestar atenção ao desempenho em tempo de execução, não acompanhar todas essas armadilhas de desempenho começa a se tornar uma dor e contraria a filosofia do Ruby? Eu acho que isso é menos um comentário para você e mais um comentário geral sobre o eventual paradoxo que isso nos leva a usar ruby.
Elsurudo 22/05/19
4
O enigma é: bem, já estamos desistindo do desempenho em nossa decisão de usar ruby, então que diferença faz "esse outro pouquinho"? É uma ladeira escorregadia, não é? Para constar, prefiro esta solução à resposta aceita, de uma perspectiva de legibilidade.
elsurudo 22/05
1
Se você usa o Ruby 2.4+, é ainda mais fácil de usar, #transform_values!como indicado por sschmeck ( stackoverflow.com/a/41508214/6451879 ).
Finn
128

O Ruby 2.4 introduziu o método Hash#transform_values!, que você pode usar.

{ :a=>'a' , :b=>'b' }.transform_values! { |v| "%#{v}%" }
# => {:a=>"%a%", :b=>"%b%"} 
sschmeck
fonte
Exatamente o que eu estava procurando!
precisa
4
Claro que há também Hash#transform_values(sem o estrondo), que não modifica o receptor. Caso contrário, uma ótima resposta, obrigado!
IGEL 18/04/19
1
Isso realmente reduzirá meu uso de reduce:-p
iGEL 18/04/19
86

A melhor maneira de modificar os valores de um Hash é

hash.update(hash){ |_,v| "%#{v}%" }

Menos código e intenção clara. Também mais rápido porque nenhum novo objeto é alocado além dos valores que devem ser alterados.

Sim
fonte
Não é exatamente verdade: novas cadeias são alocadas. Ainda assim, uma solução interessante que é eficaz. +1
Phrogz
@Phrogz bom ponto; Eu atualizei a resposta. A alocação de valor não pode ser evitada em geral porque nem todas as transformações de valor podem ser expressas como mutadores como gsub!.
Sim
3
Igual à minha resposta, mas com outro sinônimo, concordo que updatetransmite a intenção melhor que merge!. Eu acho que essa é a melhor resposta.
1
Se você não usar k, use _.
sekrett
28

Um pouco mais legível, mappara uma matriz de hashes de elemento único e reduceque commerge

the_hash.map{ |key,value| {key => "%#{value}%"} }.reduce(:merge)
edgerunner
fonte
2
Mais clara de usarHash[the_hash.map { |key,value| [key, "%#{value}%"] }]
meagar
Essa é uma maneira extremamente ineficiente de atualizar valores. Para cada par de valores, ele cria primeiro um par Array(para map) e depois a Hash. Em seguida, cada etapa da operação de redução duplicará o "memorando" Hashe adicionará o novo par de valores-chave. No uso menos :merge!em reducemodificar a final Hashno lugar. E, no final, você não está modificando os valores do objeto existente, mas criando um novo objeto, que não é o que a pergunta foi feita.
Sim
retorna nilse the_hashestiver vazio
DNNX
16

Um método que não apresenta efeitos colaterais ao original:

h = {:a => 'a', :b => 'b'}
h2 = Hash[h.map {|k,v| [k, '%' + v + '%']}]

O mapa Hash # também pode ser uma leitura interessante, pois explica por Hash.mapque não retorna um Hash (e é por isso que a matriz de [key,value]pares resultante é convertida em um novo Hash) e fornece abordagens alternativas para o mesmo padrão geral.

Feliz codificação.

[Isenção de responsabilidade: não tenho certeza se a Hash.mapsemântica muda no Ruby 2.x]


fonte
7
Matz sabe mesmo se a Hash.mapsemântica muda no Ruby 2.x?
Andrew Grimm #
1
O método Hash # [] é muito útil, mas muito feio. Existe um método mais bonito de converter matrizes em hashes da mesma maneira?
9783 Martijn
15
my_hash.each do |key, value|
  my_hash[key] = "%#{value}%"
end
Andrew Marshall
fonte
Não gosto de efeitos colaterais, mas +1 na abordagem :) Existe each_with_objectno Ruby 1.9 (IIRC) que evita a necessidade de acessar o nome diretamente e Map#mergetambém pode funcionar. Não tenho certeza de como os detalhes complexos diferem.
1
O hash inicial é modificado - tudo bem se o comportamento for antecipado, mas pode causar problemas sutis se "esquecido". Prefiro reduzir a mutabilidade do objeto, mas nem sempre isso é prático. (Ruby é praticamente uma linguagem "side-livre de efeito" ;-)
8

Hash.merge! é a solução mais limpa

o = { a: 'a', b: 'b' }
o.merge!(o) { |key, value| "%#{ value }%" }

puts o.inspect
> { :a => "%a%", :b => "%b%" }
Dorian
fonte
@MichieldeMare Me desculpe, eu não testei isso completamente o suficiente. Você está correto, o bloco precisa ter dois parâmetros. Corrigido.
5

Depois de testá-lo com o RSpec assim:

describe Hash do
  describe :map_values do
    it 'should map the values' do
      expect({:a => 2, :b => 3}.map_values { |x| x ** 2 }).to eq({:a => 4, :b => 9})
    end
  end
end

Você pode implementar o Hash # map_values ​​da seguinte maneira:

class Hash
  def map_values
    Hash[map { |k, v| [k, yield(v)] }]
  end
end

A função então pode ser usada assim:

{:a=>'a' , :b=>'b'}.map_values { |v| "%#{v}%" }
# {:a=>"%a%", :b=>"%b%"}
wedesoft
fonte
1

Se você está curioso para saber qual é a variante mais rápida aqui:

Calculating -------------------------------------
inplace transform_values! 1.265k  0.7%) i/s -      6.426k in   5.080305s
      inplace update      1.300k  2.7%) i/s -      6.579k in   5.065925s
  inplace map reduce    281.367   1.1%) i/s -      1.431k in   5.086477s
      inplace merge!      1.305k  0.4%) i/s -      6.630k in   5.080751s
        inplace each      1.073k  0.7%) i/s -      5.457k in   5.084044s
      inplace inject    697.178   0.9%) i/s -      3.519k in   5.047857s
lzap
fonte