【问题标题】:How to get the optimized choice from arrays如何从数组中获得优化的选择
【发布时间】:2014-11-17 00:48:01
【问题描述】:

我有几个哈希数组(假设我有三个)如下:

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 的总和保持在给定值以下(比如说30)。

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

编辑:我还应该提到我想处理这个对象数组。我以哈希为例,因为它更容易描述,但我打算使用对象。另外,如果从其中一个数组中使用了一个对象,我希望它与其他数组中不使用相同的选项,因此每个选定的对象都应该是唯一的。

【问题讨论】:

  • “更多的数组”是多少?有一些简单的方法可以解决这个问题,但在大型数据集上使用时性能可能会很差。
  • 8 个数组,每个数组最多 50 个哈希。性能不是一个大问题,因为它只需要每天运行一次。
  • 这是包装问题的变体。你应该把它当作算法问题来问,或者在数学网站上问,你会得到更多的答案。
  • MM,我正在准备一个采用动态编程的解决方案,它可以有效地用于任意数量的哈希数组(即,不仅仅是三个)。不过,我今晚没有时间完成它。
  • 卡里,那太好了。是否也可以说,如果一个对象被使用,从所有哈希的结果集中删除所有相同的对象,这样每个使用的对象都是唯一的?

标签: ruby algorithm optimization knapsack-problem


【解决方案1】:

应该是:

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

【讨论】:

  • 感谢您的回复,也许您可​​以进一步帮助我...我的数组实际上是对象数组,我只是以哈希为例,以使其更易于理解。当我尝试运行上述代码时,我收到错误“没有将 ObjectName 隐式转换为 Hash”。这可以修改为适用于对象数组吗?很抱歉第一次遗漏了这个细节。
  • 呃,变化很大。我试图优化迭代,所以所有的总和都是通过合并一次性计算出来的,它不能用对象来完成。看看@MarkThomas 的答案,它应该更接近您正在寻找的内容。
  • @BroiSatse 到目前为止给出的两个答案都是蛮力解决方案。有可能做到非蛮力吗? NP不完全吗?
【解决方案2】:

这是一个单行:

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] } } 

【讨论】:

  • 这个和BroiSatse的区别是先排序还是先过滤,我认为和BroiSatse一样先过滤效率更高。在您的回答中,您正在对将被丢弃的元素进行排序。
  • 就个人而言,我认为存在可读性差异。我试图让业务规则更加清晰。顺序可以更改;我没有考虑效率。
  • 另外,这没有 @MajorMajor 不想要的散列依赖。
  • 我唯一想避免的是迭代两次来计算总和。不幸的是,我同意它的可读性要差得多。您仍然可以通过更改sort_byselect 的顺序来稍微加快您的解决方案。
  • 我更改了顺序,还注意到另一个改进:我将sort_by .. .last 替换为max_by
【解决方案3】:

对于任意数量的(散列)数组,我们可以使用动态规划有效地解决这个“背包问题”。求解时间大致与数组数量和平均数组大小的平方成正比。

我的第一个想法是编写代码,但后来决定这将花费太长时间,并且可能不是解释算法如何工作的最佳方式。相反,我决定展示如何为问题中给出的示例计算最佳解决方案。对于任意数量的数组,如何实现该算法应该是显而易见的。

[编辑:毕竟我决定编写这个代码。我在末尾添加了 tidE]

首先考虑(“最后一个”)数组c。这是 Excel 电子表格中相关信息的屏幕截图。

请注意,我没有在偏移量0{ cost: 10, value: 21} 处包含哈希值。那是因为该哈希被偏移量2{ cost: 10, value: 22} 处的那个“支配”。前者永远不会被首选,因为它们的成本相同,而后者的价值更高。

第二个表显示了对于给定的“剩余预算”(即,在选择了 ab 中的哈希之后),哪个哈希是首选的。请注意,可能的剩余预算范围从3(数组c 中的最低成本)到24(总预算,30 减去在a 和@987654336 中选择哈希的成本)成本最低的@: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

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

这里的“可用预算”范围从7277 提供了在b 中选择一个哈希(在偏移量0,成本为4)和在c 中选择一个哈希值(在偏移量3,成本为3)的最低要求。范围的上限 27 是选择 a 中的哈希后可能剩余的最大值 (30 - 3)。

假设剩余预算(数组bc)为18。如果我们要在偏移量0 处选择散列,我们将从该散列中实现20 的值,并且有18 - 4 =&gt; 14 的剩余预算用于在数组c 中选择散列。从数组c 的表中,我们看到我们将选择数组c 中偏移量2 处的散列,其值为22。因此,如果可用预算为 18 并且我们在数组 b 中选择偏移量 0 处的哈希,我们将获得数组 bc 的(最大值)20 + 22 =&gt; 42

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

因此,如果我们要为数组bc 提供18 的可用预算,则最佳选择是在数组b 中的偏移量0 和偏移量@ 处选择散列数组c 中的987654393@,总(最大值)值为42。我已在表格中概述了该选择。

同样的结果适用于18 - 23 范围内的任何剩余预算。将对每个其他可用预算范围执行类似的计算。当可用预算为24 时,您会看到平局。

我们现在可以转到数组a。 (我们快完成了。)

我没有包含偏移量 0 处的哈希,因为它由偏移量 2 处的哈希支配。

数组abc 的可用预算是30,因此我们不必以此为条件(即对于第一个数组)。我们必须考虑三种可能性:

  • 选择偏移量1 处的散列以获得20 的值,从而为数组bc 留下剩余预算30 - 9 =&gt; 21,其中(来自数组@ 的表987654410@) 为最后两个数组生成最佳值42,总值为20 + 42 =&gt; 62

  • 选择偏移量2 处的哈希值以获取22 的值,这使得数组bc 的剩余预算为30 - 10 =&gt; 20,其中(来自数组@ 的表987654418@) 为最后两个数组生成最佳值 42,总值为 22 + 42 =&gt; 64

  • 选择偏移量3 处的哈希以获得10 的值,从而为数组bc 留下剩余预算30 - 2 =&gt; 28,其中(来自数组@ 的表987654426@) 为最后两个数组生成最佳值44,总值为10 + 44 =&gt; 54

因此,我们得出结论,通过选择数组a 中偏移量2 处的哈希、数组b 中偏移量0 处的哈希和偏移量@987654434 处的哈希来实现64 的最大值数组中的@c

代码

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

示例

我将all_costs_and_values 的数据结构更改为哈希数组,其中键是成本/值对的标签(例如,'12' 指的是以前在行偏移量1 处的哈希值,列偏移量2 在 OP 的数组中)。我希望标签被替换为更有意义的字符串。

all_costs_and_values =
  [{ '00' => { cost: 10, value: 20 }, '01' => { cost:  8, value: 17 },
     '02' => { cost: 10, value: 20 }, '03' => { cost: 12, value: 24 } },
   { '10' => { cost:  6, value: 16 }, '11' => { cost:  4, value: 14},
     '12' => { cost:  6, value: 17 }, '13' => { cost:  5, value: 13 } },
   { '20' => { cost:  8, value: 16 }, '21' => { cost: 14, value: 30 },
     '22' => { cost: 16, value: 32 }, '23' => { cost: 14, value: 32 } },
   { '30' => { cost:  2, value:  4 }, '31' => { cost:  5, value:  9 },
     '32' => { cost: 10, value: 16 }, '33' => { cost:  6, value:  8 } }]

total_budget = 30

knapsack(all_costs_and_values, total_budget)
  #=> Optimal value: 70
  #     Best choice for hash 0: 01
  #     Best choice for hash 1: 12
  #     Best choice for hash 2: 23
  #     Best choice for hash 3: 30

在计算最优值时,构造散列数组solution

solution
  #=> [{  30=>{:name=>"01", :value_onward=>70}},
  #                   "01" => { cost:  8, value: 17 } leave 30-8 = 22
  #=> {14-15=>{:name=>"11", :value_onward=>34},
  #=>     16=>{:name=>"12", :value_onward=>37},
  #=>  17-18=>{:name=>"11", :value_onward=>39},
  #=>     19=>{:name=>"12", :value_onward=>42},
  #=>  20-21=>{:name=>"11", :value_onward=>50},
  #=>     22=>{:name=>"12", :value_onward=>53}},
  #                   "12" => { cost:  6, value: 17 } leave 22-6 = 16
  #=> {10-12=>{:name=>"20", :value_onward=>20},
  #=>  13-15=>{:name=>"20", :value_onward=>25},
  #=>  16-18=>{:name=>"23", :value_onward=>36},
  #                   "23" => { cost: 14, value: 32 } leave 16-14 = 2
  #=>   {2-4=>{:name=>"30", :value_onward=>4},
  #                   "30" => { cost:  2, value:  4 } leave 2-2 = 0
  #=>    5-9=>{:name=>"31", :value_onward=>9},
  #=>     10=>{:name=>"32", :value_onward=>16}}]

【讨论】:

    猜你喜欢
    • 2012-12-05
    • 1970-01-01
    • 2013-12-16
    • 2016-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多