【问题标题】:Ruby method Array#<< not updating the array in hashRuby 方法 Array#<< 不更新散列中的数组
【发布时间】:2011-02-02 21:36:01
【问题描述】:

灵感来自How can I marshal a hash with arrays? 我想知道Array#&lt;&lt; 在以下代码中无法正常工作的原因是什么:

h = Hash.new{Array.new}
#=> {}
h[0]
#=> []
h[0] << 'a'
#=> ["a"]
h[0]
#=> [] # why?!
h[0] += ['a']
#=> ["a"]
h[0]
#=> ["a"] # as expected

这是否与&lt;&lt; 就地更改数组而Array#+ 创建一个新实例这一事实有关?

【问题讨论】:

标签: ruby arrays hash


【解决方案1】:

如果您使用Hash.new 的块形式创建Hash,则每次您尝试访问实际不存在的元素时都会执行该块。所以,让我们看看会发生什么:

h = Hash.new { [] }
h[0] << 'a'

这里首先要评估的是表达式

h[0]

当它被评估时会发生什么?好了,块开始运行:

[]

这不是很令人兴奋:该块只是创建一个空数组并返回它。它不做任何其他事情。特别是,它不会以任何方式改变hh 仍然是空的。

接下来,带有一个参数'a' 的消息&lt;&lt; 被发送到h[0] 的结果,这是块的结果,它只是一个空数组:

[] << 'a'

这是做什么的?它将元素 'a' 添加到一个空数组中,但由于该数组实际上并未分配给任何变量,因此它会立即被垃圾回收并消失。

现在,如果您再次评估 h[0]

h[0] # => []

h 仍然为空,因为没有分配给它,因此密钥 0 仍然不存在,这意味着该块将再次运行再次,这意味着它再次返回一个空数组(但请注意,它现在是一个全新的、不同空数组)。

h[0] += ['a']

这里发生了什么?首先,运算符 assign 被脱糖为

h[0] = h[0] + ['a']

现在,右侧一侧的h[0] 得到评估。它返回什么?我们已经讨论过这个:h[0] 不存在,因此该块开始运行,该块返回一个空数组。同样,这是一个全新的 third 空数组。这个空数组被发送了带有参数['a'] 的消息+,这导致它返回另一个新数组,即数组['a']。然后将该数组分配给h[0]

最后,在这一点上:

h[0] # => ['a']

现在你终于h[0]中添加了一些东西,所以很明显,你取出了你输入的东西。

所以,要回答您可能提出的问题,为什么不拿出您输入的内容呢?你没有一开始就放任何东西!

如果你真的想分配到块内的哈希,你必须,很好地分配到块内的哈希:

h = Hash.new {|this_hash, nonexistent_key| this_hash[nonexistent_key] = [] }
h[0] << 'a'
h[0] # => ['a']

如果您查看所涉及对象的身份,实际上很容易看出代码示例中发生了什么。然后可以看到每次调用h[0],都会得到一个不同的数组。

【讨论】:

  • 非常感谢您(一如既往)详尽的回答!我现在完全理解了,关键是从未发生过的任务。然而,我发现h[0] &lt;&lt; 'a' 并没有真正影响h 中的值与键0 的事实相当令人困惑和违反直觉。我想这是调用 Ruby 方法的语法糖使事情变得过于甜美的罕见情况之一......:/
  • 什么意思?它确实影响价值!它从h 中获取值,并将'a' 附加到它上面。您可以清楚地看到这种情况正在发生,如果您将其输入irb,实际上,您实际上在问题的代码示例中显示了它。这与 Ruby 的语法无关,甚至与 Ruby 甚至与编程无关。理解一个盒子和盒子里面的东西不是一回事,这是非常普遍的。例如,它同样适用于 Java 中的 final 字段,或 Ruby 中的冻结对象。
  • 混淆可能是因为原始问题中的值是由h的默认proc返回的,即h是一个神奇的盒子,你可以在其中得到一个全新的空数组每次使用在h 中没有值的键对其进行索引时。所以,值 from h 肯定会受到影响,只是它从来没有真正在 in h - 要更改 h 的内容你需要把魔法盒中的值。
  • @Joerg:不,你是对的,它会影响值,但不会影响哈希本身,即值保持未分配状态。在你的“盒子和里面的东西”类比中,h[0]&lt;&lt;'a' 的意思是“将'a'附加到盒子里的东西0”。 a 确实被附加到该值,但该框保持为空。我提到语法是因为我认为将它写成一系列方法调用时会更清晰,实际上是这样 - 然后可以清楚地看到没有赋值。
  • 我爱你,想要拥有你的子类。我是说孩子。
【解决方案2】:

您的代码中的问题是h[0] &lt;&lt; 'a' 创建了一个新数组并在您使用h[0] 进行索引时将其发出,但不会在&lt;&lt; 'a' 之后的任何位置存储修改后的数组,因为没有分配。

同时h[0] += ['a'] 有效,因为它等效于h[0] = h[0] + ['a']。真正与众不同的是分配 ([]=)。

第一种情况可能看起来令人困惑,但是当您只想在找不到键时从哈希中接收一些不变的默认元素时,它很有用。否则,您最终可能仅通过索引就用大量未使用的值填充 Hash。

【讨论】:

    【解决方案3】:
    h = Hash.new{ |a,b| a[b] = Array.new }
    h[0] << "hello world"
    #=> ["hello world"]
    h[0]
    #=> ["hello world"]
    

    【讨论】:

    • 您的代码没有分配到哈希中。此解决方案通过将每个新数组分配到带有a[b] = 的哈希中,使&lt;&lt; 案例工作。 (缺点是你最终会为你索引它的每个键放入一个新的 Array 到 Hash 中。这取决于你的使用是否需要。幸运的是 Ruby 为你提供了选择,如此处所示。)
    猜你喜欢
    • 1970-01-01
    • 2023-03-11
    • 1970-01-01
    • 1970-01-01
    • 2010-10-04
    • 2011-05-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-15
    相关资源
    最近更新 更多