Як скопіювати хеш у 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предметами, надана відповідь хороша. Якщо ви маєте справу з об'єктами, схожими на хеш, які надходять з місць, якими ви не керуєте, вам слід розглянути питання про те, чи хочете ви, що дублюється однотонний клас, асоційований з Hash, дублюється чи ні. Дивіться stackoverflow.com/questions/10183370/…
Сім,

Відповіді:


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


Клон - це метод на Object, BTW, тому все має доступ до нього. Деталі 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 та ін.) Можуть бути скасовані.

Маршаллінг - ім'я Рубі для серіалізації . При маршируванні об'єкт - з об'єктами, на які він посилається - перетворюється на ряд байтів; ці байти потім використовуються для створення іншого об'єкта, такого як оригінал.


Приємно, що ви надали інформацію про глибоке копіювання, але слід попередити, що це може спричинити небажані побічні ефекти (наприклад, зміна будь-якого хеша модифікує обидва). Основна мета клонування хешу - запобігання модифікації оригіналу (для незмінності тощо).
К. Карпентер

6
@ K.Carpenter Хіба це не мілка копія, яка ділиться частинами оригіналу? Як я розумію, глибока копія - це копія, яка не поділяє жодної частини оригіналу, тому зміна однієї не змінює іншу.
Уейн Конрад

1
Як саме відбувається Marshal.load(Marshal.dump(o))глибоке копіювання? Я не можу реально зрозуміти, що відбувається за лаштунками
Мунтасір Алам

Це також підкреслює те, що якщо ви h1[:a] << 'bar'змінюєте оригінальний об'єкт (рядок, на який вказує h1 [: a]), але якби ви це робили h1[:a] = "#{h1[:a]}bar"замість цього, ви створили б новий об’єкт рядка і вказували h1[:a]на це, поки h2[:a]є як і раніше вказує на стару (немодифіковану) рядок.
Макс Вільямс

@MuntasirAlam Я додав кілька слів про те, що робить марширування. Я сподіваюся, що це допомагає.
Уейн Конрад

73

Якщо ви використовуєте Rails, ви можете:

h1 = h0.deep_dup

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


2
Rails 3 має проблему з масивами deep_duping в межах хешей. Рейки 4 це виправляють.
пдобб

1
Дякую за те, що це вдалося, мій хеш все-таки постраждав під час використання дупу чи клону
Есгі Денд’янрі

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.
форфор

3
@forforf вірно. Не намагайтеся скопіювати структури даних, якщо ви не розумієте глибоку та неглибоку копію.
Джеймс Мур

5

Я також новачок Рубі, і у мене виникли подібні проблеми при копіюванні хеша. Скористайтеся наступним. Я не маю поняття про швидкість цього методу.

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

1

Використання Object#clone:

h1 = h0.clone

(Конфузно, документація для cloneговорить про те, що initialize_copyце спосіб перекрити це, але посилання на цей метод Hashспрямовує вас на те, щоб replaceзамість цього ...)


1

Оскільки стандартний метод клонування зберігає заморожений стан, він не підходить для створення нових незмінних об'єктів на основі оригінального об'єкта, якщо ви хочете, щоб нові об'єкти були дещо іншими, ніж оригінальні (якщо вам подобається програмування без стану).


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-схем, де одні хеші створювали інші. Я спочатку визначав їх як змінні класу і стикався з цією проблемою копіювання.


0

ви можете використовувати нижче для глибокої копіювання об'єктів Hash.

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

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.


3
Звучить багатообіцяюче, але не працює, це ще одна неглибока копія
Гінті
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.