具有多个约束的复杂数据的最佳组合算法

时间:2019-04-04 11:18:13

标签: javascript algorithm optimization combinations linear-programming

我想我们可以说这与此处尚未回答的问题(Optimisation/knapsack algorithm with multiple contraints in JavaScript)非常相似。

假设我们喜欢javascript,C,C ++,java。任何一种语言都可以为我工作。有人知道解决问题的算法吗?

问题: 在知道资源有限的情况下,找到可以提供最低成本和最大数量对象的最佳项目子集:

var items = [
    {name: "Rome", cost: 1000, hours: 5, peoples: 5},
    {name: "Venice", cost: 200, hours:  1, peoples: 10},
    {name: "Torin", cost: 500, hours: 3, peoples: 2},
    {name: "Genova", cost: 700, hours: 7, peoples: 8},
    {name: "Rome2", cost: 1020, hours: 5, peoples: 6},
    {name: "Venice2", cost: 220, hours:  1, peoples: 10},
    {name: "Torin2", cost: 520, hours: 3, peoples: 2},
    {name: "Genova2", cost: 720, hours: 7, peoples: 4},
    {name: "Rome3", cost: 1050, hours: 5, peoples: 5},
    {name: "Venice3", cost: 250, hours:  1, peoples: 8},
    {name: "Torin3", cost: 550, hours: 3, peoples: 8},
    {name: "Genova3", cost: 750, hours: 7, peoples: 8}
];
var maxCost = 10000, maxHours = 100, maxPeoples = 50;
// find subset of items that minimize cost, hours and peoples
// and maximize number of items
// do not exceed max values!!!

我曾想过的想法:我想我可以为每两个成本(分别称为“ KPcost-hours”,“ KPhours-cost”,“ KPcost-peoples”等)解决背包问题。优化单一成本的解决方案。然后,如果幸运的话,请使用此子集的公共部分并从那里开始工作...但是我认为这不是一个好方法...

如果您可以提供脚本示例或伪脚本示例,欢迎您!谢谢!

4 个答案:

答案 0 :(得分:3)

通过检查所有组合来实现暴力破解。

function getItems(array, constraints, [optimum, equal]) {
    function iter(index = 0, right = [], add) {

        function update() {
            if (!result.length || optimum(right, result[0])) return result = [right];
            if (equal(right, result[0])) result.push(right);
        }

        if (index >= array.length || !constraints.every(fn => fn(right))) return;
        if (add && right.length) update();

        var temp = right.find(({ ref }) => ref === array[index]),
            old = JSON.parse(JSON.stringify(right));

        if (temp) {
            temp.count++;
        } else {
            right.push({ count: 1, ref: array[index] });
        }

        iter(index, right, true);
        iter(index + 1, old);
    }

    var result = [];
    iter();
    return result;
}

const
    addBy = k => (s, { count, ref: { [k]: value } }) => s + count * value,
    addCount = (s, { count }) => s + count;

// find subset of items that minimize cost, hours and peoples
// and maximize number of items
// do not exceed max values!!!
var items = [{ name: "Rome", cost: 1000, hours: 5, peoples: 5 }, { name: "Venice", cost: 200, hours: 1, peoples: 10 }, { name: "Torin", cost: 500, hours: 3, peoples: 2 }, { name: "Genova", cost: 700, hours: 7, peoples: 8 }],
    maxCost = 10000,
    maxHours = 100,
    maxPeoples = 50,
    result = getItems(
        items,
        [
            array => array.reduce(addBy('cost'), 0) <= maxCost,
            array => array.reduce(addBy('hours'), 0) <= maxHours,
            array => array.reduce(addBy('peoples'), 0) <= maxPeoples
        ],
        [
            (a, b) => a.reduce(addCount, 0) > b.reduce(addCount, 0),
            (a, b) => a.reduce(addCount, 0) === b.reduce(addCount, 0)
        ]
    );

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

答案 1 :(得分:2)

常规解决方案

  

问题:找到价格最低的最佳项目子集,   知道有一个限制   资源。

我在这里看到两个优化标准(我将在下面讨论您要最大程度地减少人员,时间和成本的情况。)
一种可能的方法是构建一个程序,该程序将返回最大的Pareto-optimal set of solutions

帕累托集是一组非支配解,这意味着对于任何两个解S1S2S1不会支配S2,反之亦然。如果在所有标准上,解决方案S1优于或等于S2,并且在至少一个标准上严格优于解决方案S2

例如,在您的情况下,我们可以考虑以下解决方案:

S1: cost = 10, nb_objects = 4
S2: cost = 10, nb_objects = 7
S3: cost = 0, nb_objects = 0
S4: cost = 14, nb_objects = 6

则我们的帕累托最优解集为{S1, S3, S4}。那是因为它们彼此之间不占优势(例如,S1并不占优势,因为S4在对象数量方面更好)。 S4不是帕累托最优解的一部分,因为它同时受S2S1的支配。

在一般情况下,帕累托集很难计算,并且可能非常大。在您的情况下,似乎有4条标准是合理的,但这总是令人惊讶的。
这是有关如何计算此类解决方案的伪代码:

S4

这里的这个小伪代码对于尝试理解帕累托效率的概念具有更大的价值,我不建议循环利用背包问题变型的所有可行解决方案(过于昂贵)。

答案 2 :(得分:1)

假设每个项只能选择0或1,则可能有2^12=4096个组合。可行解的数量为3473。非支配(或Pareto最优)解的数量为83。

我使用了两种不同的方法:

  1. 枚举所有可行的解决方案。然后过滤掉所有支配的解决方案(每个解决方案在至少一个目标上必须比所有其他解决方案都要好)。

  2. 编写混合整数编程。它找到了一个解决方案,并添加了一个约束条件,即:在至少一个目标中,它应该比以前的解决方案更好。 (沿此model的行)。

这两种方法都能找到这83个解决方案。对于此问题,完整的枚举速度更快。

请注意,帕累托最优解的数量可以快速增长。 Here是现实设计问题中帕累托最优集合的一些图片。

请注意,没有“最佳”解决方案。所有帕累托最优解都是最优的。只有在对目标之间的折衷做出假设时,才能进一步减少最佳解决方案的数量。

答案 3 :(得分:0)

我精心设计了一个可行的解决方案,但这确实是蛮力的,但是有些优化。我没有采用Pareto解决方案,我认为这可能是一个更好的解决方案。不幸的是,尼娜·索尔兹(Nina Sholz)的脚本没有用(至少对我而言),所以我想出了这个脚本。

只需在这里留下一个工作样本(阅读:不要用于BIG数据)。

PS-如果有人可以用更好的英语写任何短语,请在下面评论,我将纠正我的拙劣写作。

/**
 * Brute Force approach
 * Problem: find combination of data objects to minimize sum of object properties and maximize number of objects
 * Costraint: sum of object properties has upper limit (for each property)
 * Solution used: do every combination, starting with the max number of objects, then lower by 1 each time, until a (or more) combination satisfy every criteria.
 */


// combination
// e.g. combination of 3 numbers with value from 0 to 4 -> combination(3,5)
// see https://rosettacode.org/wiki/Combinations#JavaScript
function combination(n, length) {
 
  // n -> [a] -> [[a]]
  function comb(n, lst) {
if (!n) return [[]];
if (!lst.length) return [];
 
var x = lst[0],
  xs = lst.slice(1);
 
return comb(n - 1, xs).map(function (t) {
  return [x].concat(t);
}).concat(comb(n, xs));
  }
 
  // f -> f
  function memoized(fn) {
m = {};
return function (x) {
  var args = [].slice.call(arguments),
    strKey = args.join('-');
 
  v = m[strKey];
  if ('u' === (typeof v)[0])
    m[strKey] = v = fn.apply(null, args);
  return v;
}
  }
 
  // [m..n]
  function range(m, n) {
return Array.apply(null, Array(n - m + 1)).map(function (x, i) {
  return m + i;
});
  }
 
  var fnMemoized = memoized(comb),
lstRange = range(0, length-1);
 
  return fnMemoized(n, lstRange)
}




// just some math calculation ------
// obviously n & r in N; r < n
function _factor(n){
var f = 1;
while (n > 1){ f *= n--; }
return f;
}
function _factor2(n,to){
var f = 1;
while (n > 1 && n >= to){ f *= n--; }
return f;
}
function _factorFraction(sup,inf){
return (sup > inf) ? _factor2(sup,inf+1) : 1/_factor2(inf,sup+1)
}
function _combination(n,r){
return (r > n/2) ? _factorFraction(n,r)/_factor(n-r) : _factorFraction(n,n-r)/_factor(r); // namely _factor(n)/_factor(n-r)/_factor(r)
}
// just some math calculation ------



var minr = 2,			// set inferior limit (r) of combination search. 2 <= minr < datas.length
datas = [],			// to be set. matrix to be filled with array of data
limits = [0],		// to be set. contains limit for each datas column
comboKeep = [],		// will contain all solutions found
columns,
sums,
timer;
function combineCheck(r){
if (r < minr) return;
console.log("Expected number of combination C(",datas.length,",",r,") = ",_combination(datas.length,r));
var metconditions = 0;
var CNR = combination(r,datas.length);
CNR.forEach(combo => {
	sums = new Array(columns).fill(0);
	// calculate sum for each column
	for (var j=0; j<combo.length; j++){
		for (var i=0; i<columns; i++){
			sums[i] += datas[combo[j]][i];
		};
	}
	// check if conditions are met
	for (var i=0; i<columns; i++){
		if (sums[i] > limits[i]){
			//console.log("sum of column",i,"exceeds limit (",sums[i]," > ",limits[i],")");
			return;
		}
	};
	comboKeep.push(combo);
	metconditions++;
});
console.log("Condition met in ",metconditions,"combos.");
if (metconditions == CNR.length){
	console.log("No need to go further, all combo have been checked.");
	return;
}
//------------
// OPTIONAL...
//------------
if (metconditions) return; // remove this line if you want all possible combination, even with less objects

combineCheck(r-1); // for delayed call: setTimeout( () => combineCheck(r-1), 250 );
}

function combineCheckStarter(){
comboKeep = [];
columns = datas[0].length;
timer = Date.now();
combineCheck(datas.length-1);
timer = Date.now() - timer;
}


//-----------------------------------------
var items = [
{name: "Rome", cost: 1000, hours: 5, peoples: 5},
{name: "Venice", cost: 200, hours:  1, peoples: 10},
{name: "Torin", cost: 500, hours: 3, peoples: 2},
{name: "Genova", cost: 700, hours: 7, peoples: 8},
{name: "Rome2", cost: 1020, hours: 5, peoples: 6},
{name: "Venice2", cost: 220, hours:  1, peoples: 10},
{name: "Torin2", cost: 520, hours: 3, peoples: 2},
{name: "Genova2", cost: 720, hours: 7, peoples: 4},
{name: "Rome3", cost: 1050, hours: 5, peoples: 5},
{name: "Venice3", cost: 250, hours:  1, peoples: 8},
{name: "Torin3", cost: 550, hours: 3, peoples: 8},
{name: "Genova3", cost: 750, hours: 7, peoples: 8}
];
var datas = Array.from(items, e => [e.cost, e.hours, e.peoples]);
var limits = [2500, 8, 20];
//-----------------------------------------

// test ;)
combineCheckStarter();
console.log("Combination found in ",timer,"ms:",comboKeep);


// pretty print results
var prettier = new Array(comboKeep.length),
unifier = new Array(columns).fill(0);
comboKeep.forEach( (combo, k) => {
var answer = new Array(combo.length);
sums = new Array(columns).fill(0);
combo.forEach((itm,i) => {
	answer[i] = items[itm].name;
	for (var j=0; j<columns; j++){
		sums[j] += datas[itm][j];
	};
});
prettier[k] = {items: answer.join(","), cost: sums[0], hours: sums[1], peoples: sums[2]};
for (var j=0; j<columns; j++){
	if (unifier[j]<sums[j]) unifier[j] = sums[j];
};
});
// normalize 
prettier.forEach( e => {
e.total = e.cost/unifier[0] + e.hours/unifier[1] + e.peoples/unifier[2];
});

//find the best (sum of all resource is lower)
prettier.sort( (b,a) => b.total-a.total);
console.log("sorted solutions:",prettier);
console.log("Best solution should be ",prettier[0].items,prettier[0]);