Ruby:选择具有多个条件的分组数组

时间:2016-06-29 12:49:51

标签: ruby

我有一系列交易。我需要按名称对交易进行分组,然后选择金额最高的组。超过1个实例。

例如,如果我有一个名为" car"金额为3000美元,与" boat"共计$ 1800,与" house"总计500美元,该方法将选择船,因为它是具有多个交易的最高金额组。

@transactions =
 [{"amount"=>-3000, "name"=>"CAR"},
  {"amount"=>-600, "name"=>"BOAT"},
  {"amount"=>-600, "name"=>"BOAT"},
  {"amount"=>-600, "name"=>"BOAT"},
  {"amount"=>-125, "name"=>"HOUSE" },
  {"amount"=>-125, "name"=>"HOUSE" },
  {"amount"=>-125, "name"=>"HOUSE" },
  {"amount"=>-125, "name"=>"HOUSE" }]

现在我有这个,但它根据名字的长度选择。

@transactions.group_by {|h| h['name'] }.max_by {|k, v| v.length }.first

如何分组,然后求和,然后在具有多个交易的组中选择最高金额。

5 个答案:

答案 0 :(得分:2)

@transactions.group_by { |h| h['name'] }
             .map { |k, v| [k, v.inject(0) { |acc, cur| acc + cur['amount'] }] }
             .max_by(&:last).first

如果您需要Enumerable中的单个值,则可能需要reduceinject

有关详细信息,请参阅Enumerable#inject上的文档。

答案 1 :(得分:2)

出于好奇:

00:00:00.001 BRT

现在可以在结果上调用$from = "$month/1/$year"; $to = $month+1."/1/$year"; $fromDate = date_create("$from"); $toDate = date_create("$to"); $fromDate->setTime(0,0,0); $toDate->setTime(0,0,0); $from = strtotime($fromDate); $to = strtotime($toDate); ,或使用[*transactions.each_with_object( Hash.new { |h, k| h[k] = {count: 0, total: 0} } ) do |h, memo| memo[h['name']].tap do |ct| ct[:count] += 1 ct[:total] -= h['amount'] end end.reject { |_, v| v[:count] == 1 } .sort_by { |_, v| v[:total] }].to_h #⇒ { # "BOAT" => { # :count => 3, # :total => 1800 # }, # "HOUSE" => { # :count => 4, # :total => 500 # } # } 代替first来检索一个最大元素。

答案 2 :(得分:2)

这是一种创建哈希数组而不是从给定数组中选择哈希值的方法。

<强>代码

def doit(transactions)
  name, arr = transactions.each_with_object(Hash.new { |h,k| h[k]=[] }) { |g,h|
    h[g["name"]] << g["amount"] }.
    reject { |_,v| v.size == 1 }.
    min_by { |_,v| v.reduce(:+) }
    name ? arr.map { |v| { "amount"=>v, "name"=>name } } : []
end

<强>实施例

doit(@transactions)
  #=> [{"amount"=>-600, "name"=>"BOAT"},
  #    {"amount"=>-600, "name"=>"BOAT"},
  #    {"amount"=>-600, "name"=>"BOAT"}] 

doit([{"amount"=>-3000, "name"=>"CAR"}, {"amount"=>-600, "name"=>"BOAT"},
      {"amount"=>-125, "name"=>"HOUSE"}])  
      #=> []

<强>解释

The steps for the first example are as follows.

a = @transactions.each_with_object(Hash.new { |h,k| h[k]=[] }) { |g,h|
  h[g["name"]] << g["amount"] }
  #=> {"CAR"=>[-3000], "BOAT"=>[-600, -600, -600], "HOUSE"=>[-125, -125, -125, -125]} 
b = a.reject { |_,v| v.size == 1 }
  #=> {"BOAT"=>[-600, -600, -600], "HOUSE"=>[-125, -125, -125, -125]} 
name, arr = b.min_by { |_,v| v.reduce(:+) }
  #=> ["BOAT", [-600, -600, -600]] 
arr
  #=> [-600, -600, -600] 
name
  #=> "BOAT" 
arr.map { |v| { "amount"=>v, "name"=>name } }
  #=> [{"amount"=>-600, "name"=>"BOAT"},
  #    {"amount"=>-600, "name"=>"BOAT"},
  #    {"amount"=>-600, "name"=>"BOAT"}] 

表达式

h = Hash.new { |h,k| h[k]=[] }
  #=> {}

使用块给定的默认值创建一个空哈希。假设我们写了

h[:dogs] += ["Saffi"]
  #=> ["Saffi"] 

Ruby首先将其扩展为

h[:dogs] = h[:dogs] + ["Saffi"]

由于h没有键:dogsh为空),因此在相等的右侧为h[:dogs]调用默认值,因此表达式变

h[:dogs] = [] + ["Saffi"]
  #=> ["Saffi"]

现在

h #=> {:dogs=>["Saffi"]} 

表达式

h[:dogs] << "Saffi"
  #=> ["Saffi"] 
h #=> {:dogs=>["Saffi"]}

类似,因为h[:dogs][]附加到空数组"Saffi"之前设置为h[:dogs]。现在,如果我们写

h[:dogs] << "Nina"
  #=> ["Saffi", "Nina"]
h #=> {:dogs=>["Saffi", "Nina"]}

由于h现在有一个键:dog,因此不会调用默认块。

另一种写作方式如下:

def doit(transactions)
  name, arr = transactions.each_with_object({}) { |g,h|
    (h[g["name"]] ||= []) << g["amount"] }.
    reject { |_,v| v.size == 1 }.
    min_by { |_,v| v.reduce(:+) }
    name ? arr.map { |v| { "amount"=>v, "name"=>name } } : []
end

如果h没有密钥g["name"](在这种情况下为h[g["name"]] #=> nil),则会在h[g["name"]]之前将[]设置为g["amount"]追加。

答案 3 :(得分:1)

第一步。选择“重复”交易:

google.load("elements", "1", {
    packages: "keyboard"
});
function onLoad() {
    console.log(lang);
    var kbd = new google.elements.keyboard.Keyboard(
        [google.elements.keyboard.LayoutCode[lang]],
        ['input1']);
}
google.setOnLoadCallback(onLoad);

第二步。查找具有最大金额的产品名称(在这种情况下,由于负数而最小):

selected = @transactions.group_by { |el| el['name'] }
                        .select{ |k, v| v.size > 1 }

更新

selected.each_with_object({}) { |(k, v), obj| obj[k] = v.map { |a| a['amount'] }.sum }
        .min_by { |k, v| v }.first

答案 4 :(得分:1)

这里有很多好的答案。我想补充一点,你可以通过组合操作来消除大量的迭代。

例如,您可以在group_by块中执行此操作,而不是在第二步中计算每个组的总和:

sums = Hash.new(0)

groups = transactions.group_by do |t|
  sums[t["name"]] += t["amount"]
  t["name"]
end

p groups
# => { "CAR" => [ { "amount" => -3000, "name" => "CAR" } ],
#      "BOAT" => [ ... ],
#      "HOUSE" => [ ... ] }

p sums
# => { "CAR" => -3000, "BOAT" => -1800, "HOUSE" => -500 }

接下来而不是执行groups.select来消除只有一个成员的群组,然后min_by来获得最终结果,将前者合并到后者中:

result = groups.min_by do |k,g|
  g.size > 1 ? sums[k] : Float::INFINITY
end

p result[1]
# => [ { "amount" => -600, "name" => "BOAT" },
#      { "amount" => -600, "name" => "BOAT" },
#      { "amount" => -600, "name" => "BOAT" } ]

因为所有小于Float::INFINITY,所以永远不会选择只有一个成员的组(除非每个组只有一个成员)。

所以......

解决方案1 ​​

全部放在一起:

sums = Hash.new(0)

result =
  transactions.group_by {|t|
    sums[t["name"]] += t["amount"]
    t["name"]
  }.min_by {|k,g| g.size > 1 ? sums[k] : Float::INFINITY }[1]

p result
# => [ { "amount" => -600, "name" => "BOAT" },
#      { "amount" => -600, "name" => "BOAT" },
#      { "amount" => -600, "name" => "BOAT" } ]

解决方案2

您还可以将所有这些组合成一个reduce并仅对数据进行一次迭代,但它不是非常Rubyish:

sums = Hash.new(0)
groups = Hash.new {|h,k| h[k] = [] }
min_sum = Float::INFINITY

result = transactions.reduce do |min_group, t|
  name = t["name"]
  sum = sums[name] += t["amount"]
  (group = groups[name]) << t

  if group.size > 1 && sum < min_sum
    min_sum, min_group = sum, group
  end
  min_group
end

请注意,您可以将变量声明之外的所有内容移动到传递给reduce(而不是nil)的数组中,但这会影响可读性。