【问题标题】:Why do these two methods yield different results?为什么这两种方法会产生不同的结果?
【发布时间】:2019-07-30 10:44:23
【问题描述】:

根据所有文档,您可以使用<<.push+= 将元素附加到数组,结果应该是相同的。我发现不是。谁能向我解释我做错了什么? (我使用的是 Ruby 2.3.1。)

我有许多哈希值。它们都包含相同的键。我想将它们组合成一个散列,其中包含一个数组中的所有收集值。这很简单,您遍历所有散列并创建一个新散列,收集所有值,如下所示:

    # arg is array of Hashes - keys must be identical
    return {} unless arg
    keys = (arg[0] ? arg[0].keys : [])

    result = keys.product([[]]).to_h # value for each key is empty array.

    arg.each do |h|
      h.each { |k,v| result[k] += [v] }
    end

    result
  end

如果而不是使用+= 我使用.push<<,我会得到完全奇怪的结果。

使用以下测试数组:

a_of_h = [{"1"=>10, "2"=>10, "3"=>10, "4"=>10, "5"=>10, "6"=>10, "7"=>10, "8"=>10, "9"=>10, "10"=>10}, {"1"=>100, "2"=>100, "3"=>100, "4"=>100, "5"=>100, "6"=>100, "7"=>100, "8"=>100, "9"=>100, "10"=>100}, {"1"=>1000, "2"=>1000, "3"=>1000, "4"=>1000, "5"=>1000, "6"=>1000, "7"=>1000, "8"=>1000, "9"=>1000, "10"=>1000}, {"1"=>10000, "2"=>10000, "3"=>10000, "4"=>10000, "5"=>10000, "6"=>10000, "7"=>10000, "8"=>10000, "9"=>10000, "10"=>10000}] 

我明白了

merge_hashes(a_of_h)
 => {"1"=>[10, 100, 1000, 10000], "2"=>[10, 100, 1000, 10000], "3"=>[10, 100, 1000, 10000], "4"=>[10, 100, 1000, 10000], "5"=>[10, 100, 1000, 10000], "6"=>[10, 100, 1000, 10000], "7"=>[10, 100, 1000, 10000], "8"=>[10, 100, 1000, 10000], "9"=>[10, 100, 1000, 10000], "10"=>[10, 100, 1000, 10000]} 

正如我所料,但如果我改用h.each { |k,v| result[k] << v },我会得到

buggy_merge_hashes(a_of_h)
 => {"1"=>[10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000], "2"=>[10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000], "3"=>[10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000], "4"=>[10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000], "5"=>[10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000], ...}

(我把剩下的剪掉了。)

这里有什么我不知道的?

【问题讨论】:

  • "根据所有文档,您可以使用<<.push+= 将元素附加到数组,结果应该相同 。” ——请告诉我们你在哪里找到了这个“所有文件”,这样我们就可以带着极端的偏见将它从这个宇宙中根除,因为它完全是错误的。正如你所发现的,很明显是这样。另外,请使用官方文档,它非常清楚地没有说明您声称“所有文档”状态。
  • @Jörg W Mittag:在这里,例如:teamtreehouse.com/library/adding-items-to-arrays-2。或在这里:ruby-forum.com/t/add-element-at-end-of-array/106609。只是一个开始。
  • ...坦率地说,我仍然不明白为什么这是完全错误的;所有三个方法都修改数组并在末尾附加新元素。正如 Amadan 写得如此优美,我的错误完全在别处。
  • 是和不是。 array += element 修改数组。它创建一个新数组,然后丢弃旧数组。想象一下,如果您的冰箱里需要更多啤酒。你可以买更多的啤酒,然后把它放进冰箱(fridge << beerfridge.push(beer)),或者你可以买一个新冰箱,把旧冰箱里的所有东西和你买的啤酒一起移到新冰箱,然后扔掉旧冰箱走了(fridge = fridge + beerfridge += beer)。
  • 我想指出您提供的两个链接都不是文档。第二个只是邮件列表上一群随机人之间的讨论,第一个是互联网上某个随机人的课程。此外,第二个链接中的 nowhere 有人声称这三个表达式是等价的,实际上 += 甚至没有被提及一次。我无法评论第一个链接,因为它需要我付费才能观看视频。但是,如果视频没有解释突变和重新绑定之间的区别,我会要求您退款。

标签: arrays ruby append


【解决方案1】:

<<#push 是破坏性操作(它们会改变接收器)。

+(因此还有+=)是一种非破坏性操作(它返回一个新对象,而接收者保持不变)。

虽然他们似乎在做同样的事情,但这个看似很小的差异却是至关重要的。

这由于另一个错误而起作用:result 中的所有子数组都以同一个对象开始。如果你添加到其中一个,你添加到所有的。

如果您使用+=,为什么这不是问题?因为result[k] += [v]result[k] = result[k] += [v] 相同(我在这里说谎,有细微的差别,但在这里不相关,只是接受它们现在相同,以免更加困惑:D);由于+ 是非破坏性的,result[k] + [v]result[k] 是不同的对象;当您使用此赋值更新数组中的值时,您不再使用起始 [] 对象,并且引用共享错误不能再咬您了。

创建result 数组的更好方法是以下之一:

result = Array.new(keys.size) { [] }
result = keys.map { [] }

这将为每个元素创建一个新的数组对象。

但是,我会以完全不同的方式写它:

a_of_h.each_with_object(Hash.new { |h, k| h[k] = [] }) { |h, r|
  h.each { |k, v| r[k] << v }
}

each_with_hash 会将传递的对象作为附加参数提供给块(此处为r,用于结果),并在方法完成时返回它。参数 - 将在 r 中的对象 - 将是带有 default_proc 的散列:每次我们尝试获取尚未在其中的键时,它将在那里插入一个新数组(即,而不是尝试预先-填充我们的结果对象,按需执行)。然后我们只需遍历数组中的每个哈希,并将值插入结果哈希中,而不用担心键是否存在。

【讨论】:

  • 我有你同样的建议,直到我尝试:h = { a: [], b: [] }; 2.times { h[:a] += [1] }; 2.times { h[:b] &lt;&lt; 1 }。还是我尝试重现该问题时遗漏了什么?
  • 您的 h[:a]h[:b] 是不同的对象。试试这个最简单的方法来说明差异:a = [[], []]; a[0]&lt;&lt;1; p aa = [[]] * 2; a[0]&lt;&lt;1; p a
  • 另外,跳here - 我所知道的这个概念的最佳教学工具。选择 Ruby,输入一些代码(例如我刚刚发布的两个 sn-ps,或者您的原始代码),使用“可视化执行”按钮提交,然后在观察内存中发生的情况的同时单步执行您的代码。
  • 还是一头雾水,看b = [[]] * 2; b[0]+=[1]; p b,和你的第一个a = [[], []]; a[0]&lt;&lt;1; p a一样。顺便说一句,很好的链接。谢谢!
  • result = Hash.new { |h,k| h[k] = [] } 两种方法没有区别。当result = Hash.new([]) 时,buggy 方法返回{}。我正在路上... :)
【解决方案2】:

第一个是hash[key] += value。第二个是hash[key] &lt;&lt; value

【讨论】:

  • 你说的很明显。您的回答应该解释为什么这两个运算符的行为不同,以及为什么这种差异会导致 OP 出现问题。
猜你喜欢
  • 2023-04-11
  • 1970-01-01
  • 2014-02-28
  • 2020-02-09
  • 1970-01-01
  • 2021-11-06
  • 2022-08-03
  • 2019-04-19
  • 1970-01-01
相关资源
最近更新 更多