如何从数组中获得优化选择

时间:2014-09-22 23:15:03

标签: ruby algorithm optimization knapsack-problem

我有几个哈希数组(让我们说我有三个)如下:

a = [{ cost: 10, value: 20},
     { cost: 9, value: 20},
     { cost: 10, value: 22},
     { cost: 2, value: 10}
    ]

b = [{ cost: 4, value: 20},
     { cost: 9, value: 20},
     { cost: 15, value: 22},
     { cost: 12, value: 10}
    ]

c = [{ cost: 10, value: 21},
     { cost: 9, value: 20},
     { cost: 10, value: 22},
     { cost: 3, value: 10}
    ]

我需要找出哪个哈希值,每个数组中的一个,给出了:value的最大总和,将:cost的总和保持在给定值之下(让我们说{ {1}})。

这个案例的答案可能很容易看到它,但这只是示例数据,实际上我有更多的数组。有人可以帮助我或指出我正确的方向吗?

编辑:我还要提一下,我想要处理这个对象数组。我使用哈希作为一个例子,因为它更容易描述,但我计划使用对象。另外,如果从一个数组中使用了一个Object,我就不喜欢从其他数组中使用相同的选项,因此每个选定的Object都应该是唯一的。

3 个答案:

答案 0 :(得分:3)

这是一个单行:

a.product(b,c).select{ |arr| arr.reduce(0) { |sum,h| sum + h[:cost] } < 30 }.max_by{ |arr| arr.reduce(0){ |sum,h| sum + h[:value]} }

分手:

a.product(b,c)
 .select{ |arr| arr.reduce(0) { |sum,h| sum + h[:cost] } < 30 }
 .max_by{ |arr| arr.reduce(0) { |sum,h| sum + h[:value] } } 

答案 1 :(得分:2)

这将是:

def find_max_option(max_cost, *arys)
  a = arys.shift
  a.product(*arys).map do |c| 
   [ c, c.inject({}) {|result, hash| 
       result.merge(hash) {|_,o,n| o + n }
     }
   ]
  end.select {|_,v| v[:cost] < max_cost}.max_by {|_,v| v[:value]}.first
end

find_max_option(30, a, b, c)   #=> [{:cost=>10, :value=>22}, {:cost=>4, :value=>20}, {:cost=>10, :value=>22}]

<小时/> 由sawa编辑为了便于阅读,稍作重写。

def find_max_option(max_cost, *a)
  a.first.product(*a.drop(1))
  .group_by{|hs| hs.inject({}){|acc, h| acc.merge(h){|_, v_acc, v_h| v_acc + v_h}}}
  .select{|k, _| k[:cost] < max_cost}
  .max_by{|k, _| k[:value]}
  .last.first
end

答案 2 :(得分:1)

我们可以使用动态编程来解决这个&#34;背包问题&#34;有效地用于任何数量的数组(散列)。解决方案时间大致与数组的数量和平均数组大小的平方成正比。

我的第一个想法是对此进行编码,但随后决定这将花费太长时间,并且可能不是解释算法如何工作的最佳方式。相反,我决定展示如何针对问题中给出的示例计算最优解。应该明白如何为任意数量的阵列实现该算法。

[编辑:我决定对此进行编码。我在最后添加了 tidE]

首先考虑(&#34;最后&#34;)数组c。这是来自Excel电子表格的相关信息的屏幕截图。

enter image description here

请注意,我未在偏移0{ cost: 10, value: 21}中包含哈希值。这是因为哈希是&#34; domninated&#34;由偏移2{ cost: 10, value: 22}的那个。前者永远不会是首选,因为它们都具有相同的成本,而后者具有更高的价值。

第二个表格显示了给定&#34;剩余预算&#34; (即,在选择ab中的哈希之后)。请注意,可能的剩余预算范围从3(数组c中的哈希值中的最低费用)到24(总预算30减去选择ab中具有最小费用的哈希:30 - 2 - 4 = 24)。

最好用一组哈希值来实现它:

[{budget: 3,  value: 10, hash_offset: 3 }, 
 {budget: 4,  value: 10, hash_offset: 3 },
 ...
 {budget: 8,  value: 10, hash_offset: 3 },
 {budget: 9,  value: 20, hash_offset: 1 },
 {budget: 10, value: 22, hash_offset: 2 },
 ...
 {budget: 10, value: 22, hash_offset: 2 }]

现在让我们转到数组b

enter image description here

这里我只包含数组b中包含的四个哈希中的两个,因为偏移1的哈希,{ cost: 9, value: 20 }由偏移{{0}处的哈希支配。 1}},0和哈希{ cost: 4, value: 20 }{ cost: 12, value: 10}支配。

这里有&#34;可用预算&#34;范围从{ cost: 4, value: 20}727提供了在7中选择一个哈希的最低要求(偏移b,成本为0),4中的一个哈希值c成本3)。范围的高端3是选择27中的哈希值后可以保留的最大值(a)。

假设剩余预算(对于数组30 - 3b)为c。如果我们要在偏移18处选择哈希值,我们会从该哈希值中实现0的值,并且剩余预算为20,用于选择数组{{1}中的哈希值}}。从数组18 - 4 => 14的表中我们看到,我们将选择数组c中偏移量c的哈希值,其值为2。因此,如果可用预算为c,我们将为22的数组bc提供(最大)值,并在偏移20 + 22 => 42处选择哈希值在数组18

我们现在必须考虑选择数组0中偏移量为2的哈希值,这将产生b的值并留下b的剩余预算用于选择数组中的哈希值22。我们从表中看到数组18 - 15 => 3,它将是偏移3处的散列,值为c。因此,如果可用预算为c,我们将为10的数组bc提供(最大)值,并在偏移22 + 10 => 32处选择哈希值在数组18

如果我们要为数组2b提供18的可用预算,那么最佳选择是在偏移b处选择哈希值。数组c和数组0中偏移b的数组3的总(最大)值。我在表格中概述了这一选择。

同样的结果适用于c范围内的任何剩余预算。将对每个其他可用预算范围执行类似的计算。当可用预算为42时,您会发现有一个平局。

我们现在可以转到数组18 - 23。 (我们差不多完成了。)

enter image description here

我没有在偏移24处包含散列,因为它由偏移a处的散列支配。

数组02a的可用预算为b,因此我们不必考虑(即第一个数组)。我们必须考虑三种可能性中的每一种:

  • 选择偏移c处的哈希值,以获得30的值,这会为数组1和{{1}留下20的剩余预算},(来自数组30 - 9 => 21的表)为最后两个数组生成b的最佳值,总值为c

  • 选择偏移b处的哈希值,以获得42的值,这会为数组20 + 42 => 62和{{1}留下2的剩余预算},(来自数组22的表)为最后两个数组生成30 - 10 => 20的最佳值,总值为b

  • 选择偏移c处的哈希值,以获得b的值,这会为数组42和{{1}留下22 + 42 => 64的剩余预算},(来自数组3的表)为最后两个数组生成10的最佳值,总值为30 - 2 => 28

因此,我们得出结论,通过选择数组b中偏移c处的散列,数组{{b处的散列,可以获得44的最大值。{ 1}}和数组10 + 44 => 54中偏移量64的哈希值。

<强>代码

2

a

0

b

示例

我将2的数据结构更改为哈希数组,其中键是成本/值对的标签(例如,c指的是以前是行偏移的哈希{{ 1}},OP的数组中的列偏移def knapsack(all_costs_and_values, total_budget) costs_and_values = remove_dominated_choices(all_costs_and_values) budget = remaining_budget(costs_and_values, total_budget) solution = optimize(costs_and_values, budget) display_solution(costs_and_values, solution) end 。我希望用更有意义的字符串替换标签。

private

def remove_dominated_choices(a)    
  a.map do |f|
    # f.invert.invert ensures that no two keys have the same value
    g = f.invert.invert
    g.each_with_object({}) do |(name,v),h|
      (h[name] = v) unless g.any? { |_,p| 
        (p[:cost] <= v[:cost] && p[:value] >  v[:value]) ||
        (p[:cost] <  v[:cost] && p[:value] >= v[:value]) }
    end
  end
end

def remaining_budget(b, tot_budget)
  mc = min_cost_per_hash(b)
  b.map.with_index do |h,i|
    if i.zero?
      (tot_budget..tot_budget)
    else
      (mc[i..-1].reduce(:+)..tot_budget - mc[0..i-1].reduce(:+))
    end
  end
end

def min_cost_per_hash(arr)
  arr.map { |h| h.values.min_by { |h| h[:cost] }[:cost] }
end

在计算最优值时,构造哈希def optimize(costs_and_values,remaining_budget) solution = Array.new(costs_and_values.size) i = costs_and_values.size-1 g = costs_and_values[i].sort_by { |k,v| -v[:cost] } solution[i] = remaining_budget[i].each_with_object({}) do |rb,h| name, f = g.find { |_,v| v[:cost] <= rb } h[rb] = { name: name, value_onward: f[:value] } end while i > 0 i -= 1 g = costs_and_values[i].sort_by { |k,v| v[:cost] } min_to_leave = remaining_budget[i+1].first solution[i] = remaining_budget[i].each_with_object({}) do |rb,h| best = - Float::INFINITY g.each do |name, v| leave_for_next = rb - v[:cost] break if leave_for_next < min_to_leave candidate = v[:value] + solution[i+1][leave_for_next][:value_onward] if candidate > best best = candidate h[rb] = { name: name, value_onward: candidate } end end end end solution end 数组:

def display_solution(costs_and_values, solution)
  rv = solution.first.keys.first
  puts "Optimal value: #{ solution.first[rv][:value_onward] }\n"
  solution.each_with_index do |h,i|
    name =  h[rv][:name]
    puts "  Best choice for hash #{i}: #{name}"
    rv -= costs_and_values[i][name][:cost]
  end
end