Как скопировать хэш в Ruby?

Я признаю, что я немного рубиновый новичок (теперь записываю скрипты грабли). На большинстве языков конструкторы копирования легко найти. Полчаса поиска не нашли его в рубине. Я хочу создать копию хеша, чтобы изменить ее, не затрагивая оригинальный экземпляр.

Некоторые ожидаемые методы, которые не работают должным образом:

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

Тем временем я прибегал к этому неудобному обходу

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

Ответ 1

Метод clone - это стандарт Ruby, встроенный способ сделать мелководная копия:

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

Обратите внимание, что поведение может быть переопределено:

Этот метод может иметь поведение, специфичное для класса. Если это так, это поведение будет задокументировано с помощью метода #initialize_copy для класса.

Ответ 2

Как указывали другие, clone сделает это. Имейте в виду, что clone хеша делает мелкую копию. То есть:

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

Что происходит в том, что хеш-ссылки копируются, но не объекты, на которые ссылаются ссылки.

Если вы хотите получить глубокую копию, выполните следующие действия:

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 работает для любого объекта, который может быть настроен. Большинство встроенных типов данных (Array, Hash, String и am. C.) Могут быть объединены.

Маршаллинг - это имя Ruby для serialization. При сортировке объект - с объектами, на которые он ссылается - преобразуется в ряд байтов; эти байты затем используются для создания другого объекта, такого как оригинал.

Ответ 4

Хэш может создать новый хэш из существующего хэша:

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

Ответ 5

Я тоже новичок в Ruby, и я столкнулся с подобными проблемами при дублировании хэша. Используйте следующее. Я не знаю о скорости этого метода.

copy_of_original_hash = Hash.new.merge(original_hash)

Ответ 6

Используйте Object#clone:

h1 = h0.clone

(Вряд ли, документация для clone говорит, что initialize_copy - это способ переопределить это, но ссылка для этого метода в Hash направляет вас на replace вместо этого...)

Ответ 7

вы можете использовать ниже для глубокого копирования объектов Hash.

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

Ответ 8

Как упоминалось в разделе "Вопросы безопасности" документации Marshal,

Если вам нужно десериализовать недоверенные данные, используйте JSON или другой формат сериализации, который способен загружать только простые, примитивные типы, такие как String, Array, Hash и т.д.

Вот пример того, как клонировать с помощью JSON в 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"}

Ответ 9

Это особый случай, но если вы начинаете с предопределенного хэша, который хотите захватить и сделать копию, вы можете создать метод, который возвращает хэш:

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

h1 = johns

Конкретный сценарий, который у меня был, состоял в том, что у меня была коллекция хэшей JSON-схемы, в которых некоторые хеши строились на других. Первоначально я определял их как переменные класса и сталкивался с этой проблемой.

Ответ 10

Поскольку у Ruby есть миллион способов сделать это, здесь другой способ использования Enumerable:

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

Ответ 11

Поскольку стандартный метод клонирования сохраняет замороженное состояние, он не подходит для создания новых неизменяемых объектов на основе исходного объекта, если вы хотите, чтобы новые объекты немного отличались от оригинала (если вам нравится программирование без состояния).

Ответ 12

Клон медленный. Для производительности, вероятно, следует начинать с чистого хеша и слияния. Не распространяется на случаи вложенных хешей...

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
  bench  user      system      total        ( real)
  clone  1.960000   0.080000    2.040000    (  2.029604)
  merge  1.690000   0.080000    1.770000    (  1.767828)
  inject 3.120000   0.030000    3.150000    (  3.152627)
  

Ответ 13

Альтернативный способ Deep_Copy, который работал у меня.

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

Это создало deep_copy, поскольку h2 формируется с использованием представления массива h1, а не h1-ссылок.