ruby:如何在不更改其初始值的情况下使用Array?

时间:2019-11-08 02:29:40

标签: ruby dictionary

我以前有过使用python的经验,并且对ruby还是陌生的。 阵列真的让我感到困惑。 这是我的代码示例:

edges = [[[1, 2], [100, 200]], [[700, 400], [1000, 2000]]]

new_edges = Array.new(edges)
new_edges.map{|edge| edge.map{|point| point.insert(0, 808912)}}

edges
#=> [[[808912, 1, 2], [808912, 100, 200]], [[808912, 700, 400], [808912, 1000, 2000]]]

new_edges
#=> [[[808912, 1, 2], [808912, 100, 200]], [[808912, 700, 400], [808912, 1000, 2000]]]

我相信会发生的是,edges中的值不会被更改,而new_edges中的值将被更改,但是两者都被更改。 如何创建初始数组的副本并进行操作而不更改任何缩写?

3 个答案:

答案 0 :(得分:6)

您的代码具有与Python中相同的问题:

edges = [[[1, 2], [100, 200]], [[700, 400], [1000, 2000]]]
new_edges = list(edges)
for edge in edges:
    for point in edge:
        point.insert(0, 808912)
print(edges)

即您变换了外部数组,但内部数组仅由引用保存,因此更改一个(通过insert)将更改edgesnew_edges的深层内容。通过使用this tool逐步遍历代码,可以很容易地理解这类问题(尽管名称如此,它同时适用于Python和Ruby)。

在Ruby中,您可以使用insert,而不是使用+来修改数组,

edges = [[[1, 2], [100, 200]], [[700, 400], [1000, 2000]]]
new_edges = edges.map { |edge| edge.map { |point| [808912] + point } }
# => [[[808912, 1, 2], [808912, 100, 200]], [[808912, 700, 400], [808912, 1000, 2000]]]
edges
# => [[[1, 2], [100, 200]], [[700, 400], [1000, 2000]]]

答案 1 :(得分:2)

这是一个更一般的情况,它说明了您遇到的问题以及纠正方法。假设我们有以下嵌套数组:

a0 = [1, 2]
a1 = [3, 4]
a = [a0, a1]
  #=> [[1, 2], [3, 4]] 
edges = [a]
  #=> [[[1, 2], [3, 4]]] 

a0a1aedges具有唯一的对象ID:

edges.object_id           #=> 1208140 

a.object_id               #=> 1085620  
edges[0].object_id        #=> 1085620 

a0.object_id              #=> 0977080 
a[0].object_id            #=> 0977080
edges[0][0].object_id     #=> 0977080

a1.object_id              #=> 0995980 
a[1].object_id            #=> 0995980
edges[0][1].object_id     #=> 0995980
edges[0][1][0].object_id  #=> 7

出于可读性考虑,我删除了每个对象ID的前七个数字,在所有情况下均为4833847。请注意edges[0][1][0] #=> 33.object_id #=> 7。出于效率考虑,整数(以及某些其他Ruby对象具有固定的,较小的对象ID。

现在使用方法Array::newedges创建一个新数组:

new_edges = Array.new(edges)
  #=> [[[1, 2], [3, 4]]]

检查对象ID(的最后六位数字):

new_edges.object_id          #=> 2400460 (different than edges.object_id)
new_edges[0].object_id       #=> 1085620 (same as edges[0].object_id)
new_edges[0][0].object_id    #=> 0977080 (same as edges[0][0].object_id)
new_edges[0][1].object_id    #=> 0995980 (same as edges[0][1].object_id)
new_edges[0][1][0].object_id #=> 7       (same as edges[0][1][0].object_id)    

可以看到new_edges是一个新对象,但是其所有嵌套数组和元素都是与edges中相应嵌套数组和元素相同的对象。

现在让我们执行以下操作:

edges[0][1][0] = 5
edges[0][1][0].object_id #=> 11

然后

edges
  #=> [[[1, 2], [5, 4]]] 
new_edges 
  #=> [[[1, 2], [5, 4]]]

new_edgesedges都被更改了,因为edges[0][1]new_edges[0][1]是同一对象(数组),而我们只是更改了该对象的第一个元素。

如何更改new_edges时更改edges

首先,请注意,new_edges = Array.new(edges)可以替换为new_edges = edges.dup。和以前一样,edgesnew_edges将是不同的对象,但是它们所有对应的嵌套数组将是相同的对象。

我们希望通过制作new_edges深拷贝来定义edges,以便对后者的更改不会影响前者,反之亦然:

new_edges = edges.map { |a| a.map { |aa| aa.dup } }  
  #=> [[[1, 2], [3, 4]]]
new_edges.object_id       #=> 2134620 (different than edges.object_id) 
new_edges[0].object_id    #=> 2134600 (different than edges[0].object_id)
new_edges[0][0].object_id #=> 2134580 (different than edges[0][0].object_id)
new_edges[0][1].object_id #=> 2134560 (different than edges[0][1].object_id)

现在更改edges中的嵌套元素,并观察edgesnew_edges的值:

edges[0][1][0] = 5

edges
  #=> [[[1, 2], [5, 4]]] 
new_edges
  #=> [[[1, 2], [3, 4]]]

可以看到new_edges没有被修改。

如果嵌套的级别更高,则使用mapdup进行深层复制可能会变得很繁琐且容易出错。一种更简单的方法是使用Marshal#dumpMarshal#load,它们创建可能包含多个嵌套对象级别的各种Ruby对象的深层副本:

edges
  #=> [[[1, 2], [5, 4]]] 
new_edges = Marshal.load(Marshal.dump(edges))
  #=> [[[1, 2], [5, 4]]]

edges的更改现在将使new_edges不受影响。

edges[0][1][0] = 3

edges
  #=> [[[1, 2], [3, 4]]] 
new_edges
  #=> [[[1, 2], [5, 4]]] 

答案 2 :(得分:0)

您以

开始
edges = [[[1, 2], [100, 200]], [[700, 400], [1000, 2000]]]

由于它是一个嵌套数组,因此Dup在这里不起作用,因此您需要先进行deep_dup,然后再进行操作。 (如果您不想变异原始数组)

这是一个用猴子修补的deep_dup(数组)的示例

  def deep_dup
    new_arr = []
    self.each do |ele|
      if ele.is_a? Array
        new_arr << ele.deep_dup
      else
        new_arr << ele
      end
    end
   new_arr
  end

您可以观察。这样做更清洁,但无论如何

编辑:数组具有内置的deep_dup方法,请使用该方法。我一直以为Ruby将deep_dup留给实施,但是我错了。