有没有简单的方法在Ruby中复制一个多维数组?

18

我在 Ruby 中有一个二维数组,想要制作一个可以正常工作的副本。显然我不能这样做;

array=[[3,4],[5,9],[10,2],[11,3]]
temp_array=array

因为我只是复制了对象标识符,所以我对temp_array所做的任何修改也会对array产生影响。我原本认为可以通过简单地使用以下代码来避免这个问题:

temp_array=array.dup

但是这并不起作用,因为temp_array只是一个对象标识符数组,会被复制,所以我最终仍然修改了初始数组(如果我理解我所做的错误)。我找到的解决方法如下:

temp_array=[]
array.each{|sub| temp_array << sub.dup}

这样做可以达到我想要的效果,但似乎是解决我的问题的一种笨拙方式。

如果我不知道数组将包含什么(例如,如果数组的某些部分可能是三维的),我会担心它如何工作。我潜在地需要测试数组中每个成员的类别,以查看是否必须迭代它以复制它。这并不是完全不可能的任务,但对我来说似乎有点杂乱无章。这是 Ruby 缺乏内置多维数组支持的简单后果,还是我错过了一个简单的内置函数来执行此操作?

7个回答

35
这里是处理它的“类似于Ruby的”方式: temp_array = Marshal.load(Marshal.dump(要被克隆的数组))

3
没问题。"That's the way." 翻译成中文是 "就这样。","I like sticking that code in Object.deep_copy." 可以翻译为 "我喜欢把那段代码放在 Object.deep_copy 中。" - Wayne Conrad
太好了,谢谢你。这实际上也为我解释了整个封送过程(尽管我需要去读一些更多的内容才能真正理解它)。 - brad

5
Ruby中制作多维数组的精确真实副本的最佳方法是使用Marshalling
以下是marshalling的Ruby语法: Marshal.load(Marshal.dump(您原始数组的名称)) 让我们看一下如何使用上面的示例来使用此语法: array = [[3,4],[5,9],[10,2],[11,3]] temp_array = array 在这个例子中,它只创建一个指向与数组相同的内存位置的对象,它没有真正复制我们的数组。在这里,如果您修改temp_array的值,则会自动反映在我们的示例中的array变量中的原始数组中。那么,如何防止自动更改发生在我们的原始数组中,我们可以通过marshalling来做到这一点。
那么!我们该如何做到这一点,在本示例中,我们需要将array的真正副本制作到temp_array中。
让我们看一下如何做到这一点: array = [[3,4],[5,9],[10,2],[11,3]] temp_array = Marshal.load(Marshal.dump(array)) 现在,我们已经制作了多维数组的真正副本,如果您修改temp_array中的任何值,则更改将不会反映在您的原始array中。

4

正如其他人指出的那样,您可以使用clone。但是,这不起作用,因为它是浅复制,所以子数组(我认为这实际上不是多维数组)将不会被克隆。由于在Ruby中,数组是可变对象,因此子数组将被更改。例如,看一下这个

>> blah = [[3,5],6]
=> [[3, 5], 6]
>> joe = blah.clone
=> [[3, 5], 6]
>> joe[0]
=> [3, 5]
>> joe[0].push "blah"
=> [3, 5, "blah"]
>> blah
=> [[3, 5, "blah"], 6]

正如你所看到的,仅仅克隆是不够的。但是你已经知道了这一点,因此你提出了这个问题。

我刚刚想出了这个方法。在你找到真正的 Ruby 解决方案之前,这个方法可以解决问题(我只是使用 Ruby,不是专家)。

def dup_recursive(new_array, old_array)
  old_array.each do |item|
    if item.class == Array
      new_array << dup_recursive([], item)
    else
      new_item = item.dup rescue new_item = item # in case it's got no dupe, like FixedNum
      new_array << new_item
    end
    new_array
  end
end

array=[[3,[9,12]],[5,9],[10,2],[11,3]]
new_array = Array.new
dup_recursive(new_array, array)
puts array.inspect
puts new_array.inspect

我知道我没有使用鸭子类型,但我很乐意接受教训,以便在不询问相关对象的类的情况下完成此操作。

编辑:我应该在谷歌上搜索“深度克隆 Ruby”,但有时我喜欢编写代码:)... 无论如何,其他提出的解决方案-- Marshal.load(Marshal.dump(array)) --也适用于哈希等,所以它更好。


这实际上是一段不错的代码。这正是我担心要编写来处理这个问题,但比我想象中做得好得多。有趣的是,许多人直接采用克隆解决方案 - 这个问题显然并不为许多 Ruby 程序员所理解。 - brad
也许是对的,Brad,但另一方面,也许你得到了一个糟糕的样本(人员、答案)。可能是时间、问题措辞或其他任何事情。或者可能是许多 Ruby 程序员不理解这个问题。不幸的是,Rails 有点让你免于理解任何东西 :) - Dan Rosenstark

0

你可以使用 DeepEnumerabledeep_dup 来实现:

>> require 'deep_enumerable'

>> array=[[3,4],[5,9],[10,2],[11,3]]

>> temp_array=array.deep_dup
>> array.each{|sub| sub << "XXX"}

>> array
=> [[3, 4, "XXX"], [5, 9, "XXX"], [10, 2, "XXX"], [11, 3, "XXX"]]

>> temp_array
=> [[3, 4], [5, 9], [10, 2], [11, 3]]

-1
尝试在数组中的每个子数组上运行array.dup。
    c = []
    array.each do |row|
      c << row.dup
    end

-8

试试这个:

temp_array = array.clone

-9
你可以使用 array.clone,如 这里 所指定的那样。那会给你原始对象的副本,而不仅仅是指针。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接