如何对这个哈希数组进行分组和过滤。红宝石

时间:2018-01-29 03:32:24

标签: ruby

我有这些数据:

@complete_transactions = [{"timestamp"=>"2018-01-18T02:57:34.959Z", "toAddress"=>"Alice", "amount"=>"50"},
 {"timestamp"=>"2018-01-18T02:57:35.016Z", "fromAddress"=>"Alice", "toAddress"=>"Bob", "amount"=>"12.5"},
 {"timestamp"=>"2018-01-18T16:53:16.747Z", "toAddress"=>"50", "amount"=>"50"},
 {"timestamp"=>"2018-01-18T16:53:27.572Z", "fromAddress"=>"50", "toAddress"=>"alice", "amount"=>"50"},
 {"timestamp"=>"2018-01-18T16:53:38.853Z", "fromAddress"=>"alice", "toAddress"=>"Alice", "amount"=>"50"},
 {"timestamp"=>"2018-01-23T23:35:03.792Z", "fromAddress"=>"Alice", "toAddress"=>"alice", "amount"=>"10"},
 {"timestamp"=>"2018-01-23T23:35:25.464Z", "fromAddress"=>"Alice", "toAddress"=>"Bob", "amount"=>"1"},
 {"timestamp"=>"2018-01-23T23:35:48.835Z", "fromAddress"=>"Alice", "toAddress"=>"Bob", "amount"=>"1"},
 {"timestamp"=>"2018-01-23T23:47:46.485Z", "fromAddress"=>"Alice", "toAddress"=>"Bob", "amount"=>"1"},
 {"timestamp"=>"2018-01-23T23:48:31.953Z", "fromAddress"=>"Alice", "toAddress"=>"Bobz", "amount"=>"1"},
 {"timestamp"=>"2018-01-24T18:11:04.444Z", "fromAddress"=>"Alice", "toAddress"=>"Bobz", "amount"=>"1"},
 {"timestamp"=>"2018-01-24T18:28:40.680Z", "fromAddress"=>"Alice", "toAddress"=>"bob", "amount"=>"3"},
 {"timestamp"=>"2018-01-24T18:36:53.179Z", "fromAddress"=>"Alice", "toAddress"=>"Sally", "amount"=>"3"},
 {"timestamp"=>"2018-01-24T18:39:14.926Z", "fromAddress"=>"Alice", "toAddress"=>"Sally", "amount"=>"3"},
 {"timestamp"=>"2018-01-24T18:39:40.937Z", "fromAddress"=>"Alice", "toAddress"=>"Sally", "amount"=>"3"},
 {"timestamp"=>"2018-01-24T18:40:11.686Z", "fromAddress"=>"Alice", "toAddress"=>"bob", "amount"=>"2"},
 {"timestamp"=>"2018-01-24T18:40:13.720Z", "fromAddress"=>"Alice", "toAddress"=>"bob", "amount"=>"2"},
 {"timestamp"=>"2018-01-24T18:41:32.832Z", "fromAddress"=>"Alice", "toAddress"=>"bob", "amount"=>"2"},
 {"timestamp"=>"2018-01-24T18:49:10.052Z", "fromAddress"=>"Alice", "toAddress"=>"bob", "amount"=>"2"},
 {"timestamp"=>"2018-01-24T18:49:11.729Z", "fromAddress"=>"Alice", "toAddress"=>"bob", "amount"=>"2"},
 {"timestamp"=>"2018-01-24T18:50:09.862Z", "fromAddress"=>"Alice", "toAddress"=>"bob", "amount"=>"2"},
 {"timestamp"=>"2018-01-24T19:14:46.326Z", "fromAddress"=>"Alice", "toAddress"=>"bob", "amount"=>"2"},
 {"timestamp"=>"2018-01-24T20:29:30.973Z", "fromAddress"=>"Alice", "toAddress"=>"Bob", "amount"=>"3"},
 {"timestamp"=>"2018-01-24T20:31:37.491Z", "fromAddress"=>"Alice", "toAddress"=>"bob", "amount"=>"2"},
 {"timestamp"=>"2018-01-24T20:35:06.693Z", "fromAddress"=>"Alice", "toAddress"=>"bob", "amount"=>"2"},
 {"timestamp"=>"2018-01-24T21:29:21.465Z", "fromAddress"=>"Alice", "toAddress"=>"bob", "amount"=>"2"},
 {"timestamp"=>"2018-01-24T21:29:27.637Z", "fromAddress"=>"Alice", "toAddress"=>"bozb", "amount"=>"2"},
 {"timestamp"=>"2018-01-26T19:04:56.197Z", "fromAddress"=>"Alice", "toAddress"=>"bob", "amount"=>"2"},
 {"timestamp"=>"2018-01-28T19:53:12.502Z", "toAddress"=>"1444", "amount"=>"50"},
 {"timestamp"=>"2018-01-28T19:53:21.318Z", "fromAddress"=>"1444", "toAddress"=>"0297bc77", "amount"=>"1"},
 {"timestamp"=>"2018-01-28T19:53:30.947Z", "fromAddress"=>"1444", "toAddress"=>"0297bc77", "amount"=>"1"},
 {"timestamp"=>"2018-01-28T19:54:11.119Z", "fromAddress"=>"1444", "toAddress"=>"0297bc77", "amount"=>"1"},
 {"timestamp"=>"2018-01-28T21:26:58.551Z", "fromAddress"=>"1444", "toAddress"=>"Alice", "amount"=>"1"},
 {"timestamp"=>"2018-01-28T21:28:13.499Z", "fromAddress"=>"1444", "toAddress"=>"Alice", "amount"=>"2"},
 {"timestamp"=>"2018-01-28T21:31:47.454Z", "fromAddress"=>"1444", "toAddress"=>"something else", "amount"=>"2"},
 {"timestamp"=>"2018-01-28T21:39:16.644Z", "fromAddress"=>"1444", "toAddress"=>"Alice", "amount"=>"3"},
 {"timestamp"=>"2018-01-28T21:39:41.527Z", "fromAddress"=>"1444", "toAddress"=>"Alice", "amount"=>"2"},

我想要的是按toAddress字段对数据进行分组,并总计金额。

我喜欢这样的事情(通知时间戳无关紧要):

[
   {
    'toAddress' => 'bob', amount: '20', fromAddress: ['Alice', '1444']
   },
   {
    'toAddress' => 'Sally', amount: '9', fromAddress: ['Alice'],
   },
   ...
]

有关如何执行此操作的任何建议吗?

这会产生一个我认为正确的数据......但感觉很糟糕:

 complete_transactions.group_by {|trans| trans['toAddress']}.map do |group, transactions|
  transactions.reduce do |h1, h2|
    h1.merge(h2) do |key, old, new|
      if key == 'amount'
        old.to_i + new.to_i
      elsif key == 'fromAddress'
        [old, new].flatten.uniq
      else
        old
      end
    end
  end
end

导致正确的结果,但感觉很糟糕。还有更好的方法吗?

这是一小部分数据样本和预期结果:

 transactions
=> [{"timestamp"=>"2018-01-23T23:48:31.953Z", "fromAddress"=>"Alice", "toAddress"=>"Bobz", "amount"=>"1"}, {"timestamp"=>"2018-01-24T18:11:04.444Z", "fromAddress"=>"Alice", "toAddress"=>"Bobz", "amount"=>"1"}, {"timestamp"=>"2018-01-24T18:11:04.444Z", "fromAddress"=>"Another", "toAddress"=>"Bobz", "amount"=>"1"}]


transactions.reduce do |h1, h2|
irb(main):007:1* h1.merge(h2) do |key, old, new|
irb(main):008:2*   if key == 'amount'
irb(main):009:3>      old.to_i + new.to_i
irb(main):010:3>   elsif key == 'fromAddress'
irb(main):011:3>      [old, new].flatten.uniq
irb(main):012:3>   else
irb(main):013:3*      old
irb(main):014:3>   end
irb(main):015:2>   end
irb(main):016:1> end
=> {"timestamp"=>"2018-01-23T23:48:31.953Z", "fromAddress"=>["Alice", "Another"], "toAddress"=>"Bobz", "amount"=>3}

2 个答案:

答案 0 :(得分:4)

我相信这就是你需要的。

result = @complete_transactions
         .group_by{|hash| hash['toAddress']}
         .map do |key, value|
           {
             'toAddress' => key,
             'amount' => value.sum {|val| val['amount'].to_i},
             'fromAddress' => value.map {|val| val['fromAddress']}.uniq
           }
         end

p result

这适用于2.4.0之后的Ruby版本。对于先前版本,请使用此计算金额总和。

'amount' => value.map {|val| val['amount'].to_i}.inject(:+)

以下语句按其' toAddress'对所有哈希值进行分组。值。

.group_by{|hash| hash['toAddress']}

followins语句遍历每个结果哈希并创建一个新哈希。

.map do |key, value|

在下面的块中,我们通过操作每个散列的值来创建所需的散列。

{
  'toAddress' => key,
  'amount' => value.sum {|val| val['amount'].to_i},
  'fromAddress' => value.map {|val| val['fromAddress']}.uniq
}

对于2.4.0之前的Ruby版本,代码变为。

{
  'toAddress' => key,
  'amount' => value.map {|val| val['amount'].to_i}.inject(:+),
  'fromAddress' => value.map {|val| val['fromAddress']}.uniq
}

希望这有帮助。

答案 1 :(得分:1)

我编写了一个提供您要求的方法但另外我提供了一些您没有要求的方法:每个“fromAddress”对每个“toAddress”的总“金额”的贡献。 (我误读了这个问题。¯\ _(ツ)_ /¯)

<强>代码

def doit(transactions)
  transactions.each_with_object(Hash.new(0)) do |g,h|
    a = [g["fromAddress"], g["toAddress"]]
    h.update(a => h[a] += g["amount"].to_f)
  end.
  group_by { |(_from, to), _amt| to }.
  map { |k,v| { toAddress: k, amount: v.map(&:last).sum.to_s,
                from_address: v.map { |(from, _to), amt| [from, amt.to_s] }.to_h } }
end

示例

让我们将示例简化为更易于管理的内容。我已经删除了@transactions的一些元素(哈希),并从每个哈希中删除了我已经删除了密钥"timestamp",因为它与问题无关。

transactions = [
  { "toAddress"=>"Alice", "amount"=>"50" },
  { "fromAddress"=>"Alice", "toAddress"=>"Bob", "amount"=>"12.5" },
  { "toAddress"=>"50", "amount"=>"50" },
  { "fromAddress"=>"50", "toAddress"=>"Alice", "amount"=>"50" },
  { "fromAddress"=>"Alice", "toAddress"=>"Alice", "amount"=>"50" },
  { "fromAddress"=>"Alice", "toAddress"=>"Bob", "amount"=>"1" },
  { "fromAddress"=>"1444", "toAddress"=>"0297bc77", "amount"=>"1"},
  { "fromAddress"=>"1444", "toAddress"=>"0297bc77", "amount"=>"1"},
  { "fromAddress"=>"1444", "toAddress"=>"Alice", "amount"=>"1"},
  { "fromAddress"=>"1444", "toAddress"=>"Alice", "amount"=>"2"}
]

doit(transactions)
    #=> [
    #     {:toAddress=>"Alice", :amount=>"153.0",
    #      :from_address=>{nil=>"50.0", "50"=>"50.0", "Alice"=>"50.0", "1444"=>3.0}},
    #     {:toAddress=>"Bob", :amount=>"13.5",
    #      :from_address=>{"Alice"=>"13.5"}},
    #     {:toAddress=>"50", :amount=>"50.0",
    #      :from_address=>{nil=>"50.0"}},
    #     {:toAddress=>"0297bc77", :amount=>"2.0",
    #      :from_address=>{"1444"=>"2.0"}}
    #   ]

如果您不希望返回值包含某些元素,则需要从transactions中删除相应的哈希值作为第一步(例如transactions.reject { |h| .... }.each_with_object...)。

<强>解释

步骤如下。

h = @transactions.each_with_object(Hash.new(0)) do |g,h|
  a = [g["fromAddress"], g["toAddress"]]
  h.update(a => h[a] += g["amount"].to_f)
end
  #=> {[nil, "Alice"]=>50.0, ["Alice", "Bob"]=>13.5, [nil, "50"]=>50.0,
  #    ["50", "Alice"]=>50.0, ["Alice", "Alice"]=>50.0, ["1444", "0297bc77"]=>2.0,
  #    ["1444", "Alice"]=>3.0}
g = h.group_by { |(_from, to), _amt| to }
  #=> { "Alice"=>[[[nil, "Alice"], 50.0], [["50", "Alice"], 50.0],
  #              [["Alice", "Alice"], 50.0], [["1444", "Alice"], 3.0]],
  #     "Bob"=>[[["Alice", "Bob"], 13.5]], "50"=>[[[nil, "50"], 50.0]],
  #     "0297bc77"=>[[["1444", "0297bc77"], 2.0]]}
g.map do |k,v|
  { toAddress: k, amount: v.map(&:last).sum.to_s,
    from_address: v.map { |(from, _to), amt| [from, amt.to_s] }.to_h }
end   
  #=> <return value shown in example>