为什么 a << b 的行为与 a = a + b 不同(使用带字符串的复制哈希数组)
Why does a << b act differently than a = a + b (with copied array of hashes with strings)
我试图在不更改原始数组的情况下修改数组的副本。它是一个散列数组,所以要制作一个 "all new" 我使用的数组副本:
foo = [ { :a => "aaaaaa" } ]
foocopy = foo.map { |h| h.dup }
我想将一些数据附加到副本中散列中的字符串。
如果我使用 =
和 +
:
它工作正常
foocopy.first[:a] = foocopy.first[:a] + "bbbbb"
foo
=> [{:a=>"aaaaaa"}] # original unchanged as expected
foocopy
=> [{:a=>"aaaaaabbbbb"}]
但是,如果我使用 <<
,它会同时修改副本和原件:
foocopy.first[:a] << "cccccc"
foo
=> [{:a=>"aaaaaacccccc"}] # ORIGINAL got changed too
foocopy
=> [{:a=>"aaaaaacccccc"}]
这是 Ruby 中的错误吗?
不,这是因为您复制了数组和散列,但字符串是具有相同 ID 的对象,因为 ruby 以一种奇怪的方式处理字符串。
irb(main):001:0> foo = [ { :a => "aaaaaa" } ]
=> [{:a=>"aaaaaa"}]
irb(main):002:0> foocopy = foo.map { |h| h.dup }
=> [{:a=>"aaaaaa"}]
irb(main):003:0> foo.object_id
=> 70252221980900
irb(main):004:0> foocopy.object_id
=> 70252221915920
irb(main):005:0> foocopy.first.object_id
=> 70252221915880
irb(main):006:0> foo.first.object_id
=> 70252221980940
irb(main):007:0> foocopy.first[:a].object_id
=> 70252221980960
irb(main):008:0> foo.first[:a].object_id
=> 70252221980960
这意味着:a+b
将此对象重新实例化为已更改的对象,并且 a << b
修改对象的实例。这是实际的方法行为。
只要字符串:
irb(main):009:0> a = "test"
=> "test"
irb(main):010:0> b = a.dup
=> "test"
irb(main):011:0> a.object_id
=> 70252221685660
irb(main):012:0> b.object_id
=> 70252221662100
irb(main):013:0> a = a + "1"
=> "test1"
irb(main):014:0> a.object_id
=> 70252221586140
irb(main):015:0> b << "1"
=> "test1"
irb(main):016:0> b.object_id
=> 70252221662100
并且来自文档:
dup
执行对象的 "shallow copy"。因此,您正在创建一个具有 相同 键和值的新哈希!不幸的是 Ruby 没有很好的内置方法来创建散列的 "deep copy",其中所有引用的对象也被复制。那你该怎么办?
我想你已经找到了最好的解决方案,那就是使用+=
。那是因为 +
创建了一个新对象并且 =
覆盖了复制的对象。
但是有一个简单的 hack 可以深度复制 Ruby 中的对象,即使用 Marshal.
serialize/unserialize 它
foo = [ { :a => "aaaaaa" } ]
foocopy = Marshal.load(Marshal.dump(foo))
那么您将不会因为指针在对象之间共享而感到惊讶。您的 <<
代码将按预期工作。
我试图在不更改原始数组的情况下修改数组的副本。它是一个散列数组,所以要制作一个 "all new" 我使用的数组副本:
foo = [ { :a => "aaaaaa" } ]
foocopy = foo.map { |h| h.dup }
我想将一些数据附加到副本中散列中的字符串。
如果我使用 =
和 +
:
foocopy.first[:a] = foocopy.first[:a] + "bbbbb"
foo
=> [{:a=>"aaaaaa"}] # original unchanged as expected
foocopy
=> [{:a=>"aaaaaabbbbb"}]
但是,如果我使用 <<
,它会同时修改副本和原件:
foocopy.first[:a] << "cccccc"
foo
=> [{:a=>"aaaaaacccccc"}] # ORIGINAL got changed too
foocopy
=> [{:a=>"aaaaaacccccc"}]
这是 Ruby 中的错误吗?
不,这是因为您复制了数组和散列,但字符串是具有相同 ID 的对象,因为 ruby 以一种奇怪的方式处理字符串。
irb(main):001:0> foo = [ { :a => "aaaaaa" } ]
=> [{:a=>"aaaaaa"}]
irb(main):002:0> foocopy = foo.map { |h| h.dup }
=> [{:a=>"aaaaaa"}]
irb(main):003:0> foo.object_id
=> 70252221980900
irb(main):004:0> foocopy.object_id
=> 70252221915920
irb(main):005:0> foocopy.first.object_id
=> 70252221915880
irb(main):006:0> foo.first.object_id
=> 70252221980940
irb(main):007:0> foocopy.first[:a].object_id
=> 70252221980960
irb(main):008:0> foo.first[:a].object_id
=> 70252221980960
这意味着:a+b
将此对象重新实例化为已更改的对象,并且 a << b
修改对象的实例。这是实际的方法行为。
只要字符串:
irb(main):009:0> a = "test"
=> "test"
irb(main):010:0> b = a.dup
=> "test"
irb(main):011:0> a.object_id
=> 70252221685660
irb(main):012:0> b.object_id
=> 70252221662100
irb(main):013:0> a = a + "1"
=> "test1"
irb(main):014:0> a.object_id
=> 70252221586140
irb(main):015:0> b << "1"
=> "test1"
irb(main):016:0> b.object_id
=> 70252221662100
并且来自文档:
dup
执行对象的 "shallow copy"。因此,您正在创建一个具有 相同 键和值的新哈希!不幸的是 Ruby 没有很好的内置方法来创建散列的 "deep copy",其中所有引用的对象也被复制。那你该怎么办?
我想你已经找到了最好的解决方案,那就是使用+=
。那是因为 +
创建了一个新对象并且 =
覆盖了复制的对象。
但是有一个简单的 hack 可以深度复制 Ruby 中的对象,即使用 Marshal.
serialize/unserialize 它foo = [ { :a => "aaaaaa" } ]
foocopy = Marshal.load(Marshal.dump(foo))
那么您将不会因为指针在对象之间共享而感到惊讶。您的 <<
代码将按预期工作。