对 Ruby 哈希值求和

8
我将尝试从一个Ruby哈希表中求和,但使用inject或reduce方法都不能返回正确的答案。似乎这些方法在累加值时覆盖了当前存储的值,而不是对它们进行求和。
我的哈希表长这样:
@test = [
  {"total"=>18, "type"=>"buy", "date"=>Thu, 21 Nov 2013, "instrument_code"=>"food"},
  {"total"=>92, "type"=>"buy", "date"=>Thu, 14 Nov 2013, "instrument_code"=>"food"},
  {"total"=>12, "type"=>"buy", "date"=>Wed, 20 Nov 2013, "instrument_code"=>"drink"},
  {"total"=>1, "type"=>"buy", "date"=>Mon, 11 Nov 2013, "instrument_code"=>"food"}
]

这是我的注入代码,但它失败了:

def additions_per_security
  @test.group_by { |i| i.type }.each do |key, value|
    if key == "buy"
      value.group_by{ |i| i.date }.each do |key, value|
        @sortable_additions[key] = value
      end
      @sorted_additions = @sortable_additions.sort_by { |key,value| key }
      @sorted_additions.shift
      @additions_per_security = Hash[@sorted_additions.map { |key, value| 
       [key, value]
      }]
      @additions_per_security.each do |key, value|
        value.group_by { |i| i.instrument_code }.each do |key, value|
          @additions_security[key] = value.inject(0){ |result, transaction| 
            (result += transaction.total)
          }
        end
      end
    end
  end
  return @additions_security
end

这是我的reduce代码,但它失败了:

def additions_per_security
  @@test.group_by { |i| i.type }.each do |key, value|
    if key == "buy"
      value.group_by { |i| i.date }.each do |key, value|
        @sortable_additions[key] = value
      end
      @sorted_additions = @sortable_additions.sort_by { |key,value| key }
      @sorted_additions.shift
      @additions_per_security = Hash[@sorted_additions.map { |key, value| 
        [key, value]
      }]
      @additions_per_security.each do |key, value|
        value.group_by { |i| i.instrument_code }.each do |key, value|
          @additions_security[key] = value.map { |p| p.total }.reduce(0,:+)
        end
      end
    end
  end
  return @additions_security
end

我有一个哈希表,我想对除第一个日期以外的所有键的总数进行求和。
目前我得到的结果如下:
{"drink"=>12.0, "food"=>92}

我的期望结果会像这样:

{"drink"=>12.0, "food"=>110}

提前感谢任何建议。


2
你的testhash出了问题或者不是一个哈希表;在你的reduce代码中,你使用了一个类变量来存储testhash。你的方法过于复杂,没有人能够告诉你为什么“reduce”失败了。我认为通过良好的重构,你将能够解决这些错误。 - Beat Richartz
1
Jordan,你的第一个问题是@testhash不是一个散列。如果你将外部的{}更改为[],它将成为一组散列;现在它不是一个Ruby对象。另外,reduceinject是同义词。最好简化并修复@testhash,通过删除不必要的哈希元素来实现。特别是日期不相关,但可能会引起复杂情况。 - Cary Swoveland
Beat - 你是正确的,我漏掉了[]。@testhash是我正在处理的哈希表的简化版本。它只显示了与此问题相关的键值对。Cary日期字段存在是因为我需要对除第一个日期之外的所有值进行求和。我已经更新了哈希表。 - JordanC
从字里行间看,你提到的“第一次约会”应该被解释为“不要计算具有最早日期值的哈希中的值(这里是@testhash中的最后一个哈希)”。你还应该从名称@testhash中删除“哈希”。请编辑以澄清。 - Cary Swoveland
是的,没错。我只想要第一个日期之后的任何日期的值,这就是为什么我进行了按“日期”分组、排序和删除(.shift)最早的日期的原因。 - JordanC
3个回答

44

如果您有简单的键/值哈希表

{1 => 42, 2 => 42}.values.sum
 => 84 

{1 => 42, 2 => 42}.values.sum NoMethodError: undefined method `sum' for [42, 42]:Array - Pikachu
5
{1 => 42, 2 => 30}.values.inject(:+) - Pikachu

7
我对你的inject代码提出以下观察意见:
  • 没有必要使用实例变量,使用本地变量(无@)即可;
  • test.group_by {|i| i.type}... 应改为 test.group_by {|i| i["type"]}...
  • @sortable_additions[key]=value 应该引发异常,因为尚未创建哈希表;
  • @sorted_additions.shift 移除哈希表的第一个元素并返回该元素,但是没有变量接收它(例如,h = @sorted_additions.shift);
  • @additions_per_security = Hash[@sorted_additions.map { |key, value|[key, value]}] 看起来将 @sorted_additions 转换为数组,然后再转回相同的哈希表。
下面是一种实现方式。
首先,您将传递日期对象。为了使用它们,我们将从制作与示例中所示的日期相对应的日期对象开始:
require 'date'
date1 = Date.parse("Thu, 21 Nov 2013") # => #<Date: 2013-11-21 ((2456618j,0s,0n),+0s,2299161j)>
date2 = Date.parse("Thu, 14 Nov 2013") # => #<Date: 2013-11-14 ((2456611j,0s,0n),+0s,2299161j)>
date3 = Date.parse("Thu, 20 Nov 2013") # => #<Date: 2013-11-20 ((2456617j,0s,0n),+0s,2299161j)>
date4 = Date.parse("Thu, 11 Nov 2013") # => #<Date: 2013-11-11 ((2456608j,0s,0n),+0s,2299161j)>

测试用:

test = [{"total"=>18, "type"=>"buy", "date"=>date1, "instrument_code"=>"food"},
        {"total"=>92, "type"=>"buy", "date"=>date2, "instrument_code"=>"food"},
        {"total"=>12, "type"=>"buy", "date"=>date3, "instrument_code"=>"drink"},
        {"total"=> 1, "type"=>"buy", "date"=>date4, "instrument_code"=>"food"}]

现在我们计算所需内容。
test_buy = test.select {|h| h["type"] == "buy"}

earliest = test_buy.min_by {|h| h["date"]}["date"]
  # => #<Date: 2013-11-11 ((2456608j,0s,0n),+0s,2299161j)>

all_but_last = test.reject {|h| h["date"] == earliest}
 # =>  [{"total"=>18, "type"=>"buy", "date"=>date1, "instrument_code"=>"food"},
        {"total"=>92, "type"=>"buy", "date"=>date2, "instrument_code"=>"food"},
        {"total"=>12, "type"=>"buy", "date"=>date3, "instrument_code"=>"drink"}] 

或者我们可以使用 Enumerable#select

all_but_last = test.select {|h| h["date"] != earliest}

请注意,以下内容中以及后续的内容中,将显示date1date2date3的值(例如,对于date1,将显示#<Date: 2013-11-21 ((2456618j,0s,0n),+0s,2299161j)>);我在这里使用变量名作为占位符,以使此内容更易读。此外,将拒绝所有哈希h,其中h["date"] = earliest (如果有超过一个)。
grouped = all_but_last.group_by {|h| h["instrument_code"]}
 # => {"food" =>[{"total"=>18, "type"=>"buy", "date"=>date1, "instrument_code"=>"food"},
                  {"total"=>92, "type"=>"buy", "date"=>date2, "instrument_code"=>"food"}],
       "drink"=>[{"total"=>12, "type"=>"buy", "date"=>date3, "instrument_code"=>"drink"}]}

keys = grouped.keys # => ["food", "drink"]

arr = keys.map {|k| [k, grouped[k].reduce(0) {|t,h| t + h["total"]}]}
  # => [["food", 110], ["drink", 12]]

Hash[arr] # => {"food"=>110, "drink"=>12} 

我使用了几个临时变量,包括test_buyearliestall_but_lastgroupedkeysarr。您可以通过“链式调用”来消除其中一些。在这里,我将向您展示如何摆脱其中的一些:

test_buy = test.select {|h| h["type"] == "buy"}
earliest = test_buy.min_by {|h| h["date"]}["date"]
grouped = test_buy.reject {|h| h["date"] == earliest}.group_by \
  {|h| h["instrument_code"]}
Hash[grouped.keys.map {|k| [k, grouped[k].reduce(0) \
  {|t,h| t + h["total"]}]}] # => {"food"=>110, "drink"=>12}

你可能觉得这看起来很复杂,但是在掌握Ruby后,它会变得非常自然和易于阅读。不过,使用链式调用的程度取决于个人风格偏好。


这个运行得很好!!非常感谢。我会继续研究您是如何处理它的,直到我更好地理解它。我在那里加入了“类型”字段,因为还有“销售”类型需要排除。但是从您给我的信息中,我相信我能够解决那部分。非常感谢您的帮助!!! - JordanC
我很高兴你觉得有帮助。你会发现我把“类型”字段放回去了。 - Cary Swoveland

0

尝试:

test = [
    {"total"=>18, "type"=>"buy", "date"=>Thu, 21 Nov 2013, "instrument_code"=>"food"},
    {"total"=>92, "type"=>"buy", "date"=>Thu, 14 Nov 2013, "instrument_code"=>"food"},
    {"total"=>12, "type"=>"buy", "date"=>Wed, 20 Nov 2013, "instrument_code"=>"drink"},
    {"total"=>1, "type"=>"buy", "date"=>Mon, 11 Nov 2013, "instrument_code"=>"food"}
]

except_first_date = test.sort_by {|i| i['date'] }
except_first_date.shift

result = except_first_date.inject({}) {|m,i| m[i["instrument_code"]] = m[i["instrument_code"]].to_f + i['total'] ; m }
# => {"food"=>110.0, "drink"=>12.0}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接