web-dev-qa-db-fra.com

Comment copier un hachage en Ruby?

J'admettrai que je suis un peu novice Ruby (écrire des scripts de rake, maintenant). Dans la plupart des langues, les constructeurs de copie sont faciles à trouver. Une demi-heure de recherche ne l'a pas trouvé en Ruby. Je souhaite créer une copie du hachage afin de pouvoir le modifier sans affecter l'instance d'origine.

Quelques méthodes attendues qui ne fonctionnent pas comme prévu:

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

En attendant, j'ai eu recours à cette solution de contournement inélégante

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

La méthode clone est la méthode standard de Ruby pour effectuer une copie superficielle :

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

Notez que le comportement peut être remplacé:

Cette méthode peut avoir un comportement spécifique à la classe. Si tel est le cas, ce comportement sera documenté sous la méthode #initialize_copy de la classe.

212
Mark Rushakoff

Comme d'autres l'ont fait remarquer, clone le fera. Sachez que clone d'un hash fait une copie superficielle. C'est-à-dire:

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

Ce qui se passe, c'est que les références du hachage sont en cours de copie, mais pas les objets auxquels les références se réfèrent.

Si vous voulez une copie en profondeur, alors:

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_copy fonctionne pour tout objet pouvant être organisé. La plupart des types de données intégrés (Array, Hash, String, etc.) peuvent être marshallés.

Marshalling est le nom de Ruby pour sérialisation . Avec marshalling, l'objet - avec les objets auxquels il fait référence - est converti en une série d'octets; ces octets sont ensuite utilisés pour créer un autre objet comme l'original.

173
Wayne Conrad

Si vous utilisez Rails, vous pouvez faire:

h1 = h0.deep_dup

http://apidock.com/Rails/Hash/deep_dup

68
lmanners

Hash peut créer un nouveau hash à partir d'un hash existant:

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
13
James Moore

Je suis aussi un débutant pour Ruby et j'ai été confronté à des problèmes similaires lors de la duplication d'un hachage. Utilisez le suivant. Je n'ai aucune idée de la vitesse de cette méthode.

copy_of_original_hash = Hash.new.merge(original_hash)
5
Kapil Aggarwal

Comme mentionné dans la section section Considérations sur la sécurité de la documentation Marshal ,

Si vous devez désérialiser des données non fiables, utilisez JSON ou un autre format de sérialisation ne permettant de charger que des types "primitifs" simples tels que String, Array, Hash, etc.

Voici un exemple de clonage à l'aide de JSON en 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"}
3
Wand Maker

vous pouvez utiliser ci-dessous pour copier en profondeur des objets de hachage.

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

Étant donné que la méthode de clonage standard préserve l’état figé, elle ne convient pas à la création de nouveaux objets immuables à partir de l’objet original, si vous souhaitez que les nouveaux objets soient légèrement différents de l’original (si vous aimez la programmation sans état).

1
kuonirat

Le clone est lent. Pour des performances devraient probablement commencer avec un hachage vide et une fusion. Ne couvre pas les cas de hachages imbriqués ...

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 utilisateur système (réel) 
 clone 1.960000 0.080000 2.040000 (2.029604) 
 fusion 1.690000 0.080000 1.770000 (1.767828) 
 injecter 3.120000 0.030000 3.150000 (3.152627) 
 
1
Justin

Utilisez Object#clone :

_h1 = h0.clone
_

(De façon confuse, la documentation de clone indique que _initialize_copy_ est le moyen de le remplacer, mais le lien pour cette méthode dans Hash vous dirige vers replace.)

1
Josh Lee

Il s'agit d'un cas spécial, mais si vous commencez avec un hachage prédéfini que vous souhaitez récupérer et en faire une copie, vous pouvez créer une méthode qui renvoie un hachage:

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

h1 = johns

Le scénario particulier que j'avais était celui-ci: j'avais une collection de hachages de schéma JSON dans lesquels certains hachages étaient construits à partir d'autres. Au départ, je les définissais comme des variables de classe et je rencontrais ce problème de copie.

0
grumpasaurus

Puisque Ruby a un million de façons de le faire, voici une autre façon d'utiliser Enumerable:

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