алгоритм наилучшей комбинации сложных данных с несколькими ограничениями - PullRequest
2 голосов
/ 04 апреля 2019

Полагаю, мы можем сказать, что это очень похоже на уже заданный вопрос ( Алгоритм оптимизации / ранца с несколькими ограничениями в 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 -oples» и т. Д.), Что дает мне решение для оптимизации отдельных затрат. Тогда, если мне повезет, возьмите общие части этих подмножеств и поработайте оттуда ... но я не думаю, что это хороший путь ...

Если вы можете привести образец сценария или образец псевдоскрипта, добро пожаловать! Спасибо!

Ответы [ 4 ]

3 голосов
/ 04 апреля 2019

Метод грубой силы путем проверки всех комбинаций.

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; }
2 голосов
/ 04 апреля 2019

Общее решение

ПРОБЛЕМА: найти лучшее подмножество предметов, которое обеспечивает минимальную стоимость и максимальное количество объектов, зная, что есть ограничение ресурс.

Здесь я вижу два критерия оптимизации (ниже я расскажу о случае, когда вы хотите минимизировать количество людей, часы и стоимость).
Возможный подход - построить программу, которая будет возвращать максимум Парето-оптимальный набор решений .

Набор Парето - это набор недоминирующих решений, что означает, что для любых двух решений S1 и S2, S1 не доминирует 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 лучше в отношении количества объектов). S2 не является частью оптимального по Парето решения, поскольку в нем преобладают S1 и S4.

В общем случае набор Парето очень сложен для вычисления и может быть очень большим. В вашем конкретном случае 4 критерия кажутся как-то разумными, но это всегда может удивлять.
Вот псевдокод о том, как вычислить такой набор решений:

Result = array of size nb_objects, initialized with empty sets
for i from 0 to total_nb_of_objects:
    for each feasible solution 'new_solution' to the problem with fixed number of objects:
        for each solution of Result[i]:
            if hours(new_solution) >= hours(solution) and  \
               cost(new_solution) >= cost(solution) and    \
               people(new_solution) >= people(solution):
                dominated = true
                break
        if not dominated:
            add new_solution to Result[i]
return Result

Этот маленький псевдокод здесь имеет больше смысла, чтобы попытаться понять концепцию эффективности Парето, я бы не советовал зацикливаться на всех возможных решениях вариации проблемы рюкзака (слишком дорогой).

1 голос
/ 05 апреля 2019

При условии, что можно выбрать только 0 или 1 каждого элемента, возможны комбинации 2^12=4096. Число возможных решений - 3473. Число недоминируемых (или оптимальных по Парето) решений - 83.

Я использовал два разных подхода:

  1. Перечислите все возможные решения. Затем отфильтруйте все доминирующие решения (каждое решение должно быть лучше по крайней мере в одной цели, чем все другие решения).

  2. Написать смешанное целочисленное программирование. Он находит решение и добавляет ограничение, которое гласит: оно должно быть лучше по крайней мере в одной из целей, чем предыдущие решения. (По линии этой модели ).

Оба метода находят эти 83 решения. Для этой задачи полное перечисление выполняется быстрее.

Обратите внимание, что число оптимальных по Парето решений может быстро возрасти. Здесь - несколько изображений такого оптимального по Парето множества реальных задач проектирования.

Обратите внимание, что не существует "единого" лучшего решения. Все оптимальные по Парето решения являются оптимальными. Только когда вы делаете предположения о компромиссах между целями, вы можете еще больше сократить количество оптимальных решений.

0 голосов
/ 07 апреля 2019

Я разработал рабочее решение, но оно действительно грубое, хотя и немного оптимизированное. Я не использовал решение Парето, которое, я считаю, возможно, является лучшим решением. К сожалению, сценарий от Нины Шольц не сработал (по крайней мере, для меня), поэтому я придумал этот.

Просто чтобы оставить здесь рабочий образец (читай: не использовать для БОЛЬШИХ данных).

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]);
...