Rubyでハッシュをコピーするにはどうすればよいですか?


197

私は少しルビーの初心者であることを認めます(今はrakeスクリプトを書いています)。ほとんどの言語では、コピーコンストラクターを簡単に見つけることができます。半時間の検索でルビは見つかりませんでした。元のインスタンスに影響を与えずに変更できるように、ハッシュのコピーを作成したいと思います。

意図したとおりに機能しないいくつかの予想されるメソッド:

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クラスのメソッドの下に文書化されます。


クローンはオブジェクト、BTWのメソッドなので、すべてがそれにアクセスできます。APIの詳細はこちら
Dylan Lacey 2012

29
他の答えを読んでいない人のために、ここにもっと明確なコメントを追加すると、これは浅いコピーになります。
grumpasaurus

ハッシュのドキュメントページruby-doc.org/core-1.9.3/Hash.html#method-i-initialize_copy
philwhln

14
そして、他のRuby初心者にとって、「浅いコピー」とは、最初のレベルより下のすべてのオブジェクトがまだ参照であることを意味します。
RobW 2013

9
これは、(他の回答で述べたように)ネストされたハッシュでは機能しなかったことに注意してください。使用しましたMarshal.load(Marshal.dump(h))
bheeshmar 2013

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マーシャリングできるすべてのオブジェクトに対して機能します。ほとんどの組み込みデータ型(配列、ハッシュ、文字列など)はマーシャリングできます。

マーシャリングは、シリアル化の Rubyの名前です。マーシャリングでは、オブジェクト(オブジェクトが参照するオブジェクト)が一連のバイトに変換されます。これらのバイトは、オリジナルのような別のオブジェクトを作成するために使用されます。


ディープコピーに関する情報を提供できてうれしいですが、意図しない副作用が発生する可能性があるという警告が表示されるはずです(たとえば、いずれかのハッシュを変更すると両方が変更されます)。ハッシュを複製する主な目的は、(不変性などのために)オリジナルの変更を防ぐことです。
K.カーペンター、

6
@ K.Carpenter オリジナルの一部を共有する浅いコピーではありませんか?ディープコピーは、私が理解しているように、オリジナルの一部を共有しないコピーであるため、一方を変更しても他方は変更されません。
ウェインコンラッド

1
Marshal.load(Marshal.dump(o))ディープコピーはどの程度正確ですか?私は本当に舞台裏で何が起こるのか理解できない
Muntasirアラム

これが強調していることはh1[:a] << 'bar'、元のオブジェクト(h1 [:a]が指す文字列)を変更した場合h1[:a] = "#{h1[:a]}bar"、代わりに新しい文字列オブジェクトを作成h1[:a]してそれをポイントすることh2[:a]です。まだ古い(変更されていない)文字列を指しています。
Max Williams

@MuntasirAlam私はマーシャリングが何をするかについていくつかの単語を追加しました。お役に立てば幸いです。
ウェインコンラッド

73

Railsを使用している場合は、次のことができます。

h1 = h0.deep_dup

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


2
Rails 3には、ハッシュ内のdeep_duping配列に関する問題があります。Rails 4はこれを修正します。
pdobb 2016

1
これを指摘してくれてありがとう、dupまたはcloneを使用すると、私のハッシュはまだ影響を受けました
Esgi Dendyanri 2017年

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 2012

3
@forforfは正しいです。深いコピーと浅いコピーを理解していない場合は、データ構造をコピーしないでください。
James Moore

5

私はRubyの初心者でもあり、ハッシュの複製で同様の問題に直面しました。以下を使用してください。この方法の速さはわかりません。

copy_of_original_hash = Hash.new.merge(original_hash)

3

マーシャルのドキュメントのセキュリティに関する考慮事項セクションで述べたように、

信頼できないデータを逆シリアル化する必要がある場合は、JSON、または文字列、配列、ハッシュなどの単純な「プリミティブ」タイプのみをロードできる別のシリアル化形式を使用してください。

RubyでJSONを使用してクローンを作成する方法の例を次に示します。

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

以下を使用して、ハッシュオブジェクトをディープコピーできます。

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

16
これはウェイン・コンラッドの答えの複製です。
Andrew Grimm、

0

Rubyにはその方法が100万通りあるため、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]

これにより、h2はh1の参照ではなくh1の配列表現を使用して形成されるため、deep_copyが生成されました。


3
有望に聞こえますが機能しません。これは別の浅いコピーです
Ginty
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.