Como copiar um hash no Ruby?


197

Admito que sou um pouco novato em rubi (agora estou escrevendo scripts de rake). Na maioria dos idiomas, os construtores de cópias são fáceis de encontrar. Meia hora de busca não encontrou em rubi. Quero criar uma cópia do hash para poder modificá-lo sem afetar a instância original.

Alguns métodos esperados que não funcionam conforme o esperado:

h0 = {  "John"=>"Adams","Thomas"=>"Jefferson","Johny"=>"Appleseed"}
h1=Hash.new(h0)
h2=h1.to_hash

Enquanto isso, eu recorri a essa solução deselegante

def copyhash(inputhash)
  h = Hash.new
  inputhash.each do |pair|
    h.store(pair[0], pair[1])
  end
  return h
end

Se você estiver lidando com Hashobjetos simples , a resposta fornecida é boa. Se você estiver lidando com objetos do tipo Hash provenientes de lugares que você não controla, considere se deseja que a classe singleton associada ao Hash seja duplicada ou não. Consulte stackoverflow.com/questions/10183370/…
Sim

Respostas:


223

O clonemétodo é o padrão de Ruby, maneira integrada de fazer uma cópia superficial :

irb(main):003:0> h0 = {"John" => "Adams", "Thomas" => "Jefferson"}
=> {"John"=>"Adams", "Thomas"=>"Jefferson"}
irb(main):004:0> h1 = h0.clone
=> {"John"=>"Adams", "Thomas"=>"Jefferson"}
irb(main):005:0> h1["John"] = "Smith"
=> "Smith"
irb(main):006:0> h1
=> {"John"=>"Smith", "Thomas"=>"Jefferson"}
irb(main):007:0> h0
=> {"John"=>"Adams", "Thomas"=>"Jefferson"}

Observe que o comportamento pode ser substituído:

Este método pode ter um comportamento específico da classe. Nesse caso, esse comportamento será documentado sob o #initialize_copymétodo da classe.


Clone é um método no Object, BTW, para que tudo tenha acesso a ele. Veja os detalhes da API aqui
Dylan Lacey

29
Adicionando um comentário mais explícito aqui para aqueles que não estão lendo outras respostas, trata-se de uma cópia superficial.
Grumpasaurus 17/11/12

A documentação #initialize_copy parece não existir para o Hash, embora exista um link para ele na página de documentos do Hash ruby-doc.org/core-1.9.3/Hash.html#method-i-initialize_copy
philwhln

14
E para outros iniciantes em Ruby, "cópia superficial" significa que todos os objetos abaixo do primeiro nível ainda são uma referência.
RobW 01/07/19

9
Observe que isso não funcionou para hashes aninhados para mim (conforme mencionado em outras respostas). Eu usei Marshal.load(Marshal.dump(h)).
precisa saber é o seguinte

178

Como outros já apontaram, clonefarão isso. Esteja ciente de que cloneum hash faz uma cópia superficial. Ou seja:

h1 = {:a => 'foo'} 
h2 = h1.clone
h1[:a] << 'bar'
p h2                # => {:a=>"foobar"}

O que está acontecendo é que as referências do hash estão sendo copiadas, mas não os objetos aos quais as referências se referem.

Se você deseja uma cópia profunda, então:

def deep_copy(o)
  Marshal.load(Marshal.dump(o))
end

h1 = {:a => 'foo'}
h2 = deep_copy(h1)
h1[:a] << 'bar'
p h2                # => {:a=>"foo"}

deep_copyfunciona para qualquer objeto que possa ser empacotado. A maioria dos tipos de dados internos (matriz, hash, cadeia etc.) pode ser empacotada.

Marshalling é o nome de Ruby para serialização . Com o empacotamento, o objeto - com os objetos a que se refere - é convertido em uma série de bytes; esses bytes são usados ​​para criar outro objeto como o original.


É bom que você tenha fornecido as informações sobre cópia em profundidade, mas deve vir com um aviso de que isso pode causar efeitos colaterais indesejados (por exemplo, modificar um hash modifica ambos). O principal objetivo da clonagem de um hash é impedir a modificação do original (imutabilidade, etc.).
22915 K. Carpenter

6
@ K.Carpenter Não é uma cópia superficial que compartilha partes do original? Cópia profunda, como eu a entendo, é uma cópia que não compartilha nenhuma parte do original, portanto, modificar uma não modificará a outra.
Wayne Conrad

1
Como exatamente é Marshal.load(Marshal.dump(o))a cópia profunda? Eu realmente não consigo entender o que acontece nos bastidores
Muntasir Alam

O que isso destaca também é que, se você h1[:a] << 'bar'modificar o objeto original (a string apontada por h1 [: a]), mas se o fizer h1[:a] = "#{h1[:a]}bar", criaria um novo objeto de string e apontaria h1[:a]para ele, enquanto h2[:a]está ainda apontando para a string antiga (não modificada).
Max Williams

@MuntasirAlam Adicionei algumas palavras sobre o que o marshalling faz. Espero que ajude.
Wayne Conrad


13

O hash pode criar um novo hash a partir de um hash existente:

irb(main):009:0> h1 = {1 => 2}
=> {1=>2}
irb(main):010:0> h2 = Hash[h1]
=> {1=>2}
irb(main):011:0> h1.object_id
=> 2150233660
irb(main):012:0> h2.object_id
=> 2150205060

24
Observe que isso tem o mesmo problema de cópia profunda que #clone e #dup.
forforf

3
@forforf está correto. Não tente copiar estruturas de dados se você não entender uma cópia profunda ou superficial.
James Moore

5

Também sou novato no Ruby e enfrentei problemas semelhantes ao duplicar um hash. Use o seguinte. Não faço ideia da velocidade desse método.

copy_of_original_hash = Hash.new.merge(original_hash)

3

Conforme mencionado na seção Considerações de segurança da documentação do Marshal ,

Se você precisar desserializar dados não confiáveis, use JSON ou outro formato de serialização capaz de carregar apenas tipos "primitivos" simples, como String, Array, Hash etc.

Aqui está um exemplo de como fazer clonagem usando JSON no Ruby:

require "json"

original = {"John"=>"Adams","Thomas"=>"Jefferson","Johny"=>"Appleseed"}
cloned = JSON.parse(JSON.generate(original))

# Modify original hash
original["John"] << ' Sandler'
p original 
#=> {"John"=>"Adams Sandler", "Thomas"=>"Jefferson", "Johny"=>"Appleseed"}

# cloned remains intact as it was deep copied
p cloned  
#=> {"John"=>"Adams", "Thomas"=>"Jefferson", "Johny"=>"Appleseed"}

1

Use Object#clone:

h1 = h0.clone

(Confusamente, a documentação para clonediz que initialize_copyé a maneira de substituir isso, mas o link para esse método no Hashdireciona você a replacesubstituir ...)


1

Como o método de clonagem padrão preserva o estado congelado, não é adequado para criar novos objetos imutáveis ​​com base no objeto original, se você desejar que os novos objetos sejam ligeiramente diferentes do original (se você gosta de programação sem estado).


1

O clone é lento. Pois o desempenho provavelmente deve começar com o hash e a mesclagem em branco. Não abrange casos de hashes aninhados ...

require 'benchmark'

def bench  Benchmark.bm do |b|    
    test = {'a' => 1, 'b' => 2, 'c' => 3, 4 => 'd'}
    b.report 'clone' do
      1_000_000.times do |i|
        h = test.clone
        h['new'] = 5
      end
    end
    b.report 'merge' do
      1_000_000.times do |i|
        h = {}
        h['new'] = 5
        h.merge! test
      end
    end
    b.report 'inject' do
      1_000_000.times do |i|
        h = test.inject({}) do |n, (k, v)|
          n[k] = v;
          n
        end
        h['new'] = 5
      end
    end
  end
end
  total do sistema do usuário do banco (real)
  clone 1,960000 0,080000 2,040000 (2,029604)
  mesclar 1,690000 0,080000 1,770000 (1,767828)
  injetar 3,120000 0,030000 3,150000 (3,152627)
  

1

Este é um caso especial, mas se você estiver começando com um hash predefinido que deseja pegar e fazer uma cópia, poderá criar um método que retorne um hash:

def johns 
    {  "John"=>"Adams","Thomas"=>"Jefferson","Johny"=>"Appleseed"}
end

h1 = johns

O cenário particular que eu tive foi que eu tinha uma coleção de hashes de esquema JSON onde alguns hashes foram construídos a partir de outros. Inicialmente, eu as defini como variáveis ​​de classe e me deparei com esse problema de cópia.


0

você pode usar abaixo para copiar objetos Hash em profundidade.

deeply_copied_hash = Marshal.load(Marshal.dump(original_hash))

16
Esta é uma duplicata da resposta de Wayne Conrad.
Andrew Grimm

0

Como o Ruby tem um milhão de maneiras de fazer isso, aqui está outra maneira de usar o Enumerable:

h0 = {  "John"=>"Adams","Thomas"=>"Jefferson","Johny"=>"Appleseed"}
h1 = h0.inject({}) do |new, (name, value)| 
    new[name] = value;
    new 
end

-3

Maneira alternativa ao Deep_Copy que funcionou para mim.

h1 = {:a => 'foo'} 
h2 = Hash[h1.to_a]

Isso produziu uma cópia_ profunda, já que h2 é formado usando uma representação de matriz de h1 em vez das referências de h1.


3
Parece promissor, mas não trabalho, esta é uma outra cópia superficial
Ginty
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.