【问题标题】:Why does a << b act differently than a = a + b (with copied array of hashes with strings)为什么 a << b 的行为与 a = a + b 不同(复制的哈希数组和字符串)
【发布时间】:2015-07-02 19:11:08
【问题描述】:

我正在尝试修改数组的副本而不更改原始数组。这是一个哈希数组,所以要制作我使用的数组的“全新”副本:

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

但是,如果我使用&lt;&lt;,它会同时修改副本和原件:

foocopy.first[:a] << "cccccc"
foo
=> [{:a=>"aaaaaacccccc"}]   # ORIGINAL got changed too
foocopy
=> [{:a=>"aaaaaacccccc"}]

这是 Ruby 中的错误吗?

【问题讨论】:

  • 在使用数组时,您非常不太可能在 &lt;&lt;+ 这样的老牌事物中发现错误。
  • 然而……它发生了……stackoverflow.com/questions/29224421/… 据任何人称,这似乎是一个持续到 4 的 rails 错误。在这种情况下,由于特殊情况(修改复制数组内的哈希内的字符串)这似乎是一个错误。但是@jorge 给出的解释很有启发性。我的观点是,虽然不太可能,但这并非不可能,AFAIK SO 是一个很好的询问和发现的地方。
  • Rails 与 Ruby 几乎不是同一层,&lt;&lt;+ 的使用次数超过了 Rails 方法 any Rails 方法的使用次数。因此,在 Rails 中发现 bug 比在 Ruby 中发现核心方法的可能性要大得多。

标签: ruby dup


【解决方案1】:

dup 执行对象的“浅拷贝”。因此,您正在创建一个具有 same 键和值的新哈希!不幸的是,Ruby 没有一个很好的内置方法来创建哈希的“深度副本”,其中所有引用的对象也被复制。那你该怎么办?

我想你已经找到了最好的解决方案,那就是使用+=。这是因为+ 创建了一个新对象,而= 覆盖了复制的对象。

但是有一个简单的技巧可以在 Ruby 中深度复制对象,即使用 Marshal 对其进行序列化/反序列化。

foo = [ { :a => "aaaaaa" } ]
foocopy = Marshal.load(Marshal.dump(foo))

那么由于指针在对象之间共享,您不会有任何意外。您的&lt;&lt; 代码将按您的预期工作。

【讨论】:

  • Marshal 是处理这个问题的好方法。
【解决方案2】:

不,这是因为你复制了数组和哈希,但字符串是一个具有相同 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 &lt;&lt; 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

从文档中:

http://ruby-doc.org/core-2.2.0/String.html#method-i-2B

http://ruby-doc.org/core-2.2.0/String.html#method-i-3C-3C

【讨论】:

    猜你喜欢
    • 2021-10-06
    • 2020-04-13
    • 2014-03-29
    • 2015-09-14
    • 2019-10-27
    • 2019-04-30
    • 2011-10-20
    • 2011-05-30
    • 2011-08-10
    相关资源
    最近更新 更多