【问题标题】:Ruby array in hash assignment odd behavior哈希分配中的Ruby数组奇怪的行为
【发布时间】:2017-01-05 02:21:43
【问题描述】:

我试图理解为什么以下代码的行为(据我所知)奇怪。

[1] pry(main)> a = {}
=> {}
[2] pry(main)> a[1] = [[0,0]] * 7
=> [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]
[3] pry(main)> a[2] = [[0,0]] * 7
=> [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]
[4] pry(main)> a[1][2][0] = 3 # Should be one value changed, right? 
=> 3
[5] pry(main)> a
=> {1=>[[3, 0], [3, 0], [3, 0], [3, 0], [3, 0], [3, 0], [3, 0]],
 2=>[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]}

我认为应该发生的是,在索引 2 处的键 1 处的哈希 a 中的数组的一个值应该更改为 3,但是整个数组的所有第一个值都更改为3。这里发生了什么,我错过了什么?这是我的 Ruby 版本。

$ ruby -v
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-linux]

编辑:

我也尝试了以下

[1] pry(main)> a = {}
=> {}
[2] pry(main)> a[1] = ([[0,0].dup].dup * 7).dup
=> [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]
[3] pry(main)> a[2] = ([[0,0].dup].dup * 7).dup
=> [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]
[4] pry(main)> a[1][2][0] = 3
=> 3
[5] pry(main)> a
=> {1=>[[3, 0], [3, 0], [3, 0], [3, 0], [3, 0], [3, 0], [3, 0]],
 2=>[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]}
[6] pry(main)> a = {}
=> {}
[7] pry(main)> a[1] = ([[0,0].clone].clone * 7).clone
=> [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]
[8] pry(main)> a[2] = ([[0,0].clone].clone * 7).clone
=> [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]
[9] pry(main)> a
=> {1=>[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]],
 2=>[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]}
[10] pry(main)> a[1][2][0] = 3
=> 3
[11] pry(main)> a
=> {1=>[[3, 0], [3, 0], [3, 0], [3, 0], [3, 0], [3, 0], [3, 0]],
 2=>[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]}

这些值肯定应该是副本吗?

【问题讨论】:

    标签: ruby


    【解决方案1】:

    a[1] 中的所有元素都指向同一个数组。

    [0,0] 在您执行[[0,0]] * 7 时不会被深度复制。

    解决方案:a[1] = Array.new(7) { [0,0] }(感谢@Stefan!)

    【讨论】:

    • 解决这个问题:a[1] = Array.new(7) { [0,0] }
    • 非常感谢你们两位,现在我明白了。
    • 这里的关键是你使用初始化器block而不是初始化器element。查看原始问题:a.map(&:object_id) 表明数组中的每个对象都是相同的引用。
    【解决方案2】:

    以下是将来诊断此问题的方法:

    object_id 会告诉你 Ruby 保存对象的“槽”。对象应该有唯一的 ID:

    foo = []
    bar = []
    foo.object_id # => 70322472940660
    bar.object_id # => 70322472940640
    

    如果不是,尽管它们可能是不同的变量名,但它们仍然指向同一个对象。这在更高级的用途中可能是可取的,但通常这不是你想要的,因为改变一个会改变另一个导致混乱、咬牙切齿并可能返回“不应该被命名的人”。例如,当我们看到:

    [[]] * 2 # => [[], []]
    

    因为我们似乎返回了两个数组,它们可以使用并行赋值进行赋值,例如:

    foo, bar = [[]] * 2
    

    但是,当我们查看 foobar 以查看它们的存储位置时,我们可以发现问题所在,它们都指向同一个插槽,因此更改一个插槽会更改另一个插槽:

    foo.object_id # => 70323794190700
    bar.object_id # => 70323794190700
    foo << 1
    bar # => [1]
    

    这是编写代码时的老问题;您必须了解“按值传递”与“按引用传递”,并且当一种语言执行其中一种或另一种时,就会出现此问题。然后你必须学习如何告诉语言以避免问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-18
      • 2017-01-09
      • 2016-05-22
      • 2011-06-18
      • 1970-01-01
      相关资源
      最近更新 更多