【问题标题】:Use [].replace to make a copy of an array使用 [].replace 制作数组的副本
【发布时间】:2017-05-30 03:39:32
【问题描述】:

我有一个类,我在实例变量上使用Array#shift 实例方法。我以为我制作了实例变量的“副本”,但实际上我没有,shift 实际上是在更改实例变量。

例如,在我预期两次都得到["foo", "bar", "baz"] 之前:

class Foo
  attr_reader :arr
  def initialize arr
    @arr = arr
  end

  def some_method
    foo = arr
    foo.shift
  end
end

foo = Foo.new %w(foo bar baz)
p foo.arr #=> ["foo", "bar", "baz"]
foo.some_method
p foo.arr #=> ["bar", "baz"]

结果:

["foo", "bar", "baz"]
["bar", "baz"]

但如图所示,我的“副本”根本不是真正的副本。现在,我不确定我是否应该将我想要的东西称为“副本”、“克隆”、“dup”、“深度克隆”、“深度复制”、“冷冻克隆”等......

我真的很困惑要搜索什么,并发现了一堆疯狂的尝试来做看似“制作数组的副本”的事情。

然后我发现another answer 有一行字就解决了我的问题:

class Foo
  attr_reader :arr
  def initialize arr
    @arr = arr
  end

  def some_method
    foo = [].replace arr
    foo.shift
  end
end

foo = Foo.new %w(foo bar baz)
p foo.arr #=> ["foo", "bar", "baz"]
foo.some_method
p foo.arr #=> ["foo", "bar", "baz"]

输出:

["foo", "bar", "baz"]
["foo", "bar", "baz"]

我知道Array#replace 是一个在Array 的实例上调用的实例方法,该实例恰好是一个空数组(例如foo = ["cats", "and", "dogs"].replace arr 仍然可以工作),我得到一个“副本”是有道理的"实例变量@arr

但这与以下有何不同:

foo = arr
foo = arr.clone
foo = arr.dup
foo = arr.deep_clone
Marshal.load # something something
# etc...

或者我在 SO 上看到的 dupmapinject 的任何其他疯狂组合?

【问题讨论】:

  • 这些方法之间的很多区别在于新数组中的对象是原始对象的副本还是两个数组都指向相同的对象。
  • 需要注意的一点:在 ruby​​ 中分配一个变量永远不会复制它,它只是创建一个指向该值的指针。因此,如果 arr = [1,3,4] 并且您分配了 x = arr,您只会为原始数组创建另一个“名称”。

标签: ruby


【解决方案1】:

这是 ruby​​ 中可变性的棘手概念。就核心对象而言,这通常会出现数组和散列。字符串也是可变的,但这可以通过脚本顶部的标志来禁用。见What does the comment "frozen_string_literal: true" do?

在这种情况下,您可以轻松调用dupdeep_dupclone,达到与replace相同的效果:

['some', 'array'].dup
['some', 'array'].deep_dup
['some', 'array'].clone
Marshal.load Marshal::dump(['some', 'array'])

在差异方面,dupclone 除了一些细微差别之外是相同的 - 请参阅 What's the difference between Ruby's dup and clone methods?

这些和deep_dup 的区别在于deep_dup 是递归工作的。例如,如果你复制一个嵌套数组,内部数组将不会被克隆:

  a = [[1]]
  b = a.clone
  b[0][0] = 2
  a # => [[2]]

哈希也会发生同样的事情。

Marshal.load Marshal::dump <object> 是深度克隆对象的通用方法,与deep_dup 不同,它位于 ruby​​ 核心中。 Marshal::dump 返回一个字符串,因此可以方便地将对象序列化到文件中。

如果您想避免此类意外错误,请记住哪些方法具有副作用,并且仅在有意义时才调用这些方法。方法名称末尾的解释点表明它具有副作用,但其他包括 unshift、push、concat、delete 和 pop。函数式编程的很大一部分是避免副作用。可以看https://www.sitepoint.com/functional-programming-techniques-with-ruby-part-i/

【讨论】:

  • +1。另请注意,明智的做法是密切关注相关类如何实现方法。正如Ruby docs 所说,“一般来说,clonedup 在后代类中可能具有不同的语义。”我认为数组或哈希与 Object 的文档没有什么不同,但 ActiveRecord 记录可以,例如。
  • 感谢@maxple 的回复,我收到无方法错误:['some', 'array'].deep_dup #NoMethodError: undefined method deep_dup'`
  • @mbigras 是的,我在回答中提到了这一点。 deep_dup 不在 Ruby 核心中。如果你require 'active_support/all' 你会得到那个方法。
  • Marshal.loadMarshal::dump 都是类方法时,为什么你选择以不同的方式运行它们?
  • @mbigras 我自己只是在测试它。似乎:: 可以在任何地方使用而不是.
【解决方案2】:

首选方法是dup

  • 在需要复制数组时使用array.dup
  • 在需要复制二维数组时使用array.map(&:dup)

除非您真的想深度复制整个对象图,否则不要使用编组技巧。通常你只想复制数组而不是包含的元素。

【讨论】:

    猜你喜欢
    • 2015-06-26
    • 1970-01-01
    • 2015-03-04
    • 1970-01-01
    • 1970-01-01
    • 2012-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多