【问题标题】:how to work with Array without changing its initial values?如何在不更改其初始值的情况下使用 Array?
【发布时间】:2019-11-08 02:29:40
【问题描述】:

我是新手。到目前为止,我接触过两种语言,ruby 和 python。我还阅读了几篇关于 C++ 指针的文章,我对数组对象以及如何操作其中的值有些困惑。我发现在方法中包含一个感叹号,可以在不赋值的情况下更改数组。但有时它不会;如果有人能解释数组的性质,我很感激,它们是什么,它们是存储值的位置,还是值的集合?更具体地说,如果我将 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 会改变。非常感谢您的帮助。

【问题讨论】:

  • 看起来您正在创建 new_edges 数组两次。一次使用 Array.new,两次使用 new_edges.map。你为什么这样做?当你在终端咆哮这段代码时发生了什么?
  • 有更好的方法吗?可以给我一些建议吗,谢谢。
  • 嗯 Array.new(edges) 似乎还可以...但是有什么问题?您需要在运行此代码时包含结果,即。错误消息或输出。
  • 最初是什么:[[[1, 2], [100, 200]], [[700, 400], [1000, 2000]]],我想要什么:[[[ 808912, 1, 2], [808912, 100, 200]], [[808912, 700, 400], [808912, 1000, 2000]]],后面需要用到初始值

标签: ruby dictionary


【解决方案1】:

您的代码存在与 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]]]

【讨论】:

  • 谢谢。我还找到了另一种方法: 'new_edges = Marshal.load(Marshal.dump(edges))' 'new_edges.map{|point| point.insert(0, 808912)}'
【解决方案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::new 方法从 edges 创建一个新数组:

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] 是同一个对象(数组),我们只是更改了该对象的第一个元素。

edges 改变时,我们如何避免改变new_edges

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

我们希望通过制作edges深拷贝来定义new_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]]] 

【讨论】:

    【解决方案3】:

    你从

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

    Dup 在这里不起作用,因为它是一个嵌套数组,所以你需要在做你想做的事情之前对其进行 deep_dup。 (如果你不想改变原始数组)

    这是一个猴子补丁 deep_dup (Array) 的示例

      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 留给实现,但我错了。

    【讨论】:

    • Ruby 没有deep_dup。 ActiveSupport 可以(Rails 也可以)。
    • 哦,我刚用谷歌搜索了一下,一定是看错了文档。我的错,我刚刚看到#deep_dup,但它一定是 ActiveSupport 而不是 Ruby。此外,您的解决方案更节省空间和时间,哈哈。仅使用不改变原始数组的方法比 deep_dupping 原始数组然后使用发生变异的方法要好得多。
    • 在不使用 Rails 的情况下深度克隆数组的一种有点骇人听闻的方法是 Marshal.load Marshal.dump(my_array)Marshal.dump 将数组打包成字符串格式,Marshal.load 将其解包成数组。
    猜你喜欢
    • 2020-02-01
    • 2022-10-17
    • 2020-03-02
    • 1970-01-01
    • 1970-01-01
    • 2022-11-22
    • 1970-01-01
    • 2016-05-08
    • 2013-12-02
    相关资源
    最近更新 更多