Как мне скопировать хеш в Ruby?

197

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

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

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
обрывистый
источник
Если вы имеете дело с простыми Hashобъектами, предоставленный ответ будет хорошим. Если вы имеете дело с хеш-подобными объектами, которые происходят из мест, которые вы не контролируете, вам следует подумать, хотите ли вы, чтобы синглтон-класс, связанный с хеш-копированием, дублировался или нет. См. Stackoverflow.com/questions/10183370/…
Sim

Ответы:

223

Этот 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методом класса.

Марк Рушаков
источник
Клон это метод объекта, кстати, так что все имеют к нему доступ. Подробнее об API здесь
Дилан Лейси,
29
Добавление более явного комментария здесь для тех, кто не читает другие ответы, что это мелкая копия.
grumpasaurus
Документация #initialize_copy, по-видимому, не существует для Hash, хотя на странице документа Hash есть ссылка на нее ruby-doc.org/core-1.9.3/Hash.html#method-i-initialize_copy
philwhln
14
А для других новичков в Ruby «мелкое копирование» означает, что каждый объект ниже первого уровня все еще является ссылкой.
RobW
9
Обратите внимание, что это не работает для вложенных хэшей для меня (как упоминалось в других ответах). Я использовал Marshal.load(Marshal.dump(h)).
Bheeshmar
178

Как уже отмечали другие, 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 и т. Д.) Можно упорядочить.

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

Уэйн Конрад
источник
Приятно, что вы предоставили информацию о глубоком копировании, но должно появиться предупреждение о том, что это может вызвать непреднамеренные побочные эффекты (например, изменение любого хэша приводит к изменению обоих). Основной целью клонирования хеша является предотвращение модификации оригинала (для неизменяемости и т. Д.).
К. Карпентер
6
@ K.Carpenter Разве это не мелкая копия, которая разделяет части оригинала? Глубокая копия, насколько я понимаю, - это копия, которая не имеет общей части с оригиналом, поэтому изменение одного не приведет к изменению другого.
Уэйн Конрад
1
Как именно Marshal.load(Marshal.dump(o))глубокое копирование? Я не могу понять, что происходит за кулисами
Мунтасир Алам
Что это основные моменты , а также в том , что если вы делаете h1[:a] << 'bar'вы изменяете исходный объект (строка указывает h1 [: а]) , но если вы должны были сделать h1[:a] = "#{h1[:a]}bar"вместо этого, вы можете создать новый строковый объект, и точка h1[:a]в том , что, в то время как h2[:a]IS по-прежнему указывает на старую (неизмененную) строку.
Макс Уильямс
@MuntasirAlam Я добавил несколько слов о том, что делает сортировка. Надеюсь, это поможет.
Уэйн Конрад,
73

Если вы используете Rails, вы можете сделать:

h1 = h0.deep_dup

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

lmanners
источник
2
В Rails 3 есть проблема с массивами deep_duping внутри хэшей. Rails 4 исправляет это.
pdobb
1
Спасибо за указание на это, мой хэш все еще пострадал при использовании dup или клона
Esgi Dendyanri
13

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

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
Обратите внимание, что это та же проблема с глубоким копированием, что и #clone и #dup.
forforf
3
@forforf правильно. Не пытайтесь копировать структуры данных, если вы не понимаете глубокое или мелкое копирование.
Джеймс Мур
5

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

copy_of_original_hash = Hash.new.merge(original_hash)
Капил Аггарвал
источник
3

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

Если вам нужно десериализовать ненадежные данные, используйте 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"}
Wand Maker
источник
1

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

h1 = h0.clone

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

Джош Ли
источник
1

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

kuonirat
источник
1

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

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
  Общее количество пользователей системы (реальное)
  клон 1.960000 0.080000 2.040000 (2.029604)
  объединить 1.690000 0.080000 1.770000 (1.767828)
  ввести 3.120000 0.030000 3.150000 (3.152627)
  
Джастин
источник
1

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

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

h1 = johns

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

grumpasaurus
источник
0

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

deeply_copied_hash = Marshal.load(Marshal.dump(original_hash))
ktsujister
источник
16
Это дубликат ответа Уэйна Конрада.
Эндрю Гримм
0

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

h0 = {  "John"=>"Adams","Thomas"=>"Jefferson","Johny"=>"Appleseed"}
h1 = h0.inject({}) do |new, (name, value)| 
    new[name] = value;
    new 
end
Рохит
источник
-3

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

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

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

user2521734
источник
3
Звучит многообещающе, но не работает, это еще одна мелкая копия
Джинти