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?
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?
Respostas:
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}%"] } ]
Hash.[]
não aceita uma matriz de pares de matrizes, requer um número par de argumentos diretos (daí a divisão inicial).
Hash#each
produz 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.
my_hash.inject(my_hash){ |h,(k,str)| h[k]="%#{str}%"; h }
, tem que retornar o hash do bloco
No Ruby 2.1 e superior, você pode fazer
{ a: 'a', b: 'b' }.map { |k, str| [k, "%#{str}%"] }.to_h
#transform_values!
como indicado por sschmeck ( stackoverflow.com/a/41508214/6451879 ).
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%"}
Hash#transform_values
(sem o estrondo), que não modifica o receptor. Caso contrário, uma ótima resposta, obrigado!
reduce
:-p
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.
gsub!
.
update
transmite a intenção melhor que merge!
. Eu acho que essa é a melhor resposta.
k
, use _
.
Um pouco mais legível, map
para uma matriz de hashes de elemento único e reduce
que commerge
the_hash.map{ |key,value| {key => "%#{value}%"} }.reduce(:merge)
Hash[the_hash.map { |key,value| [key, "%#{value}%"] }]
Array
(para map
) e depois a Hash
. Em seguida, cada etapa da operação de redução duplicará o "memorando" Hash
e adicionará o novo par de valores-chave. No uso menos :merge!
em reduce
modificar a final Hash
no 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.
nil
se the_hash
estiver vazio
Existe um novo método 'Rails way' para esta tarefa :) http://api.rubyonrails.org/classes/Hash.html#method-i-transform_values
Hash#transform_values
. Este deve ser o caminho a seguir a partir de agora.
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.map
que 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.map
semântica muda no Ruby 2.x]
Hash.map
semântica muda no Ruby 2.x?
my_hash.each do |key, value|
my_hash[key] = "%#{value}%"
end
each_with_object
no Ruby 1.9 (IIRC) que evita a necessidade de acessar o nome diretamente e Map#merge
também pode funcionar. Não tenho certeza de como os detalhes complexos diferem.
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%" }
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%"}
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