查找总和为给定数字的元素

时间:2015-03-21 18:42:58

标签: ruby-on-rails ruby arrays subset-sum

我必须找到订单金额等于或大于给定数量的订单列表。例如,

order #  amount
o1        100
o2         50
o3         90
o4        150
o5        20
o6        30
o7        50

如果我需要找到订单金额等于300或大于300的订单,那么我应该得到o5,o6,o2,o7,o3,o1或o1,o4,o3。如果订单是最小到最大或最大到最小,则无关紧要。我怎么能以最小的方式做到这一点?我知道第一步是排序。我可以使用数组求和来获得所有元素的总和但是如何获得加起来或者只是大于给定数字的元素?

我使用Ruby on Rails和Oracle作为db。

1 个答案:

答案 0 :(得分:1)

你的问题其实很简单。首先,通过减少数量来订购订单:

orders = [["o1", 100], ["o2", 50], ["o3", 90], ["o4", 150],
          ["o5",  20], ["o6", 30], ["o7", 50]] 

sorted_orders = orders.sort_by(&:last).reverse
  #=> [["o4", 150], ["o1", 100], ["o3", 90], ["o7", 50],
  #    ["o2", 50],  ["o6", 30],  ["o5", 20]]

假设:

min_req = 300

首先看看是否可以通过使用所有项目来实现min_req

orders.reduce(0) { |tot,(_,qty)| tot+qty } < min_req
  #=> false

如果返回true我们就完成了:因为数量都是非负数,我们就会计算出数量子集之和的最大可能值。

然后只需按排序顺序获取项目,直到数量总和至少为min_req

tot = 0
sorted_orders.take_while { |_,qty| tot < min_req && tot += qty }
  #=> [["o4", 150], ["o1", 100], ["o3", 90]]

我们可以将它包装在一个方法中:

def smallest_combination(orders, min_req)
  return nil if orders.reduce(0) { |tot,(_,qty)| tot+qty } < min_req
  tot = 0
  orders.sort_by(&:last)
        .reverse
        .take_while { |_,qty| tot < min_req && tot += qty }
end

smallest_combination(orders, 300)
  #=> [["o4", 150], ["o1", 100], ["o3", 90]] 
smallest_combination(orders, 400)
  #=> [["o4", 150], ["o1", 100], ["o3", 90], ["o7", 50], ["o2", 50]] 
smallest_combination(orders, 500)
  #=> nil