【问题标题】:Ruby transform hash into a running total of valuesRuby 将哈希值转换为运行总和
【发布时间】:2026-01-04 18:05:02
【问题描述】:

我有一个哈希

{:a => 2, :b => 8, :c => 10, :d => 40 }

我想修改散列值,使这些值代表运行总计。

{:a => 2, :b => 10, :c => 20, :d => 60 }

我已经走了几条路,但还没有找到正确的答案。我尝试过的一些事情。

data.inject({}) { |result, (k,v)| result[k] = v+ result.values.compact.sum; result }

=> {:a=>2, :b=>10, :c=>22, :d=>74}

我显然不太了解结果是如何在注入中累积的。由于 k 的第一次迭代为零,我正在使用紧凑型。

我可以像这样将值作为数组获取

data.inject([]) { |result, element| result << result.last.to_i + element.last }

=> [2, 10, 20, 60]

我不知道如何修改它以与哈希类似地工作。

我可以这样做,但这似乎不是正确/最有效的方法。

totals = data.inject([]) { |result, element| result << result.last.to_i + element.last }

=> [2, 10, 20, 60]

data.map.with_index { |(k,v),i| [k,totals[i]] }.to_h

=> {:a=>2, :b=>10, :c=>20, :d=>60}

感谢我在这里得到的任何帮助。

【问题讨论】:

    标签: ruby hash reduce inject


    【解决方案1】:

    鉴于您知道键的顺序,您可以简单地编写

    h= { :a => 2, :b => 8, :c => 10, :d => 40 }
    
    cumulative = 0
    h.transform_values { |v| cumulative += v }
      #=> {:a=>2, :b=>10, :c=>20, :d=>60}
    

    Hash#transform_values

    【讨论】:

    • 巧妙地使用transform_values(我很少记得它存在)。
    【解决方案2】:

    您的“injectmap.with_index”很接近,但您可以将它们组合起来:

    data.inject({}) { |h, (k, v)| h.merge(k => v + h.values.last.to_i) }
    

    或复制较少:

    data.inject({}) { |h, (k, v)| h.merge!(k => v + h.values.last.to_i) }
    

    或许:

    data.each_with_object({}) { |(k, v), h| h[k] = v + h.values.last.to_i }
    

    h 开始时为空,因此 h.values 可以为空,因此 h.values.last.nil? 会发生,因此 #to_i 调用“隐藏”nil 测试并简化块。

    【讨论】:

      【解决方案3】:

      哈希和排序

      不保证哈希是按排序顺序排列的,因此您可以保持运行总计的前提(原则上)是无效的,除非您在将值传递给 #inject 或 #reduce 之前对键进行排序。但是,MRI Ruby 通常会按插入顺序保留哈希,但请自行承担风险。

      解决方案

      抛开 Hash 的排序顺序,您可以使用 #sort 或 #sort_by 来解决,如果您愿意,还可以进行一些重构,我将在 Ruby 3.0.2 中像这样修改原始 Hash:

      hash = {:a => 2, :b => 8, :c => 10, :d => 40 }
      
      # Note that positional argument `_1` is the
      # accumulator, and `_2` is the value.
      hash = hash.keys.zip(
        hash.values.inject([]) { _1 << _2 + _1&.last.to_i }
      ).to_h;
      hash
      #=> {:a=>2, :b=>10, :c=>20, :d=>60}
      

      如果您不想分配回同一个哈希,或者想将结果分配到其他地方,您也可以这样做。但是,据我了解,问题是关于就地修改您的 Hash,将表达式的结果分配回 hash 似乎是最简单的事情。

      【讨论】:

      • “MRI Ruby 经常按插入顺序保留哈希,但请相信您自担风险”并不完全正确。自 1.9.3 ("Hashes enumerate their values in the order that the corresponding keys were inserted.") 以来,Ruby 已经保证了哈希顺序,并且这些年来文档已经慢慢变得更加明确。
      • @muistooshort 另见Entry Order。显然,在某些情况下密钥顺序会发生变化,并且鼓励人们在更改哈希时依赖原始插入顺序,恕我直言,在一般情况下可能存在风险,但不会影响这个特定的解决方案。在 JRuby、TruffleRuby、Rubinius 等中是否保留插入顺序是用户的练习,但重点是插入顺序是 MRI Ruby 的实现选择,而不是哈希的属性一般的。 YMMV。
      • “在 JRuby、TruffleRuby、Rubinius 等中是否保留插入顺序是用户的练习”——这是一个非常简单的练习,因为从 Ruby 1.9.3 开始,插入顺序中的枚举是语言规范的一部分。
      • MRI 与真实的 Ruby 规范非常接近,它记录了哈希的排序方式。任何不保持哈希插入顺序的东西都不是真正的 Ruby。我同意如果顺序很重要,哈希可能不是最佳选择,但它不是实现选择,它是指定的行为。
      • @JörgWMittag 你们都错过了基本的计算机科学要点,以及我在上面链接的非常清晰的 Ruby 文档。考虑h = {a: 1, b: 2}; h.delete :a; h[:a] = 1; h #=&gt; {:b=&gt;2, :a=&gt;1}。在生产应用程序中,依赖预先排序的哈希键是有风险的,尽管当您知道原始插入顺序不会改变时,这对于玩具问题或简短示例通常没问题。