2
votes

meilleur algorithme de combinaison de données complexes avec de multiples contraintes

Je suppose que nous pouvons dire que c'est très similaire à une question déjà posée ici ( Algorithme d'optimisation / sac à dos avec de multiples contraintes en JavaScript ), qui n'a pas encore de réponse.

Disons que nous aimons javascript, C, C ++, java. Chacune de ces langues fonctionne pour moi. Quelqu'un connaît des algorithmes pour résoudre le problème?

PROBLÈME: trouver le meilleur sous-ensemble d'articles qui accorde un coût minimum et un nombre maximum d'objets, sachant qu'il y a une limitation de ressource:

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!!!

IDÉES J'AI EU: J'imaginais que je pourrais faire une solution problème de sac à dos pour chaque couple de coûts (appelons-les "KPcost-heures", "KPhours-cost", "KPcost-people" etc.), ce qui me donne la solution pour optimiser les coûts individuels. Ensuite, si j'ai de la chance, prenez les parties communes de ces sous-ensembles et travaillez à partir de là ... mais je ne pense pas que ce soit un bon chemin ...

Si vous pouvez donner un exemple de script, ou un exemple de pseudo-script, de rien! Merci!


1 commentaires

Vous ne pouvez maximiser ou minimiser qu'une seule chose à la fois. Il est probable qu'aucune des solutions à coût minimum n'ait également le minimum d'heures ou le nombre maximum d'objets, vous devez donc en choisir un pour être plus important, ou définir une seule fonction de fitness. Définissez également ce qui détermine le "meilleur sous-ensemble".


4 Réponses :


3
votes

Une approche par force brute en vérifiant toutes les combinaisons.

.as-console-wrapper { max-height: 100% !important; top: 0; }
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);


2 commentaires

il semble qu'il y ait une faille de script ... juste des tests avec 12 éléments ... cela me donne un tableau 55x20, avec beaucoup de copies du même élément dans chacune des 55 lignes


avez-vous pris le dernier code? avec ajouter dans iter ?



2
votes

Solution générale

PROBLÈME: trouvez le meilleur sous-ensemble d'articles qui accorde un coût minimum et nombre maximum d'objets, sachant qu'il y a une limitation de Ressource.

Je vois deux critères d'optimisation ici (je vais également parler du cas où vous souhaitez minimiser les personnes, les heures et les coûts ci-dessous également).
Une approche possible consiste à créer un programme qui retournera un ensemble de solutions Pareto-optimal a >.

Un ensemble de Pareto est un ensemble de solutions non dominantes, ce qui signifie que pour deux solutions quelconques S1 et S2 , S1 code > ne domine pas S2 , et vice versa. Une solution S1 domine une solution S2 si elle est meilleure ou égale à S2 sur tous les critères, et strictement meilleure sur au moins un critère.

Par exemple, dans votre cas, nous pouvons envisager les solutions suivantes:

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

Ensuite, notre ensemble de solutions Pareto-optimal est {S1 , S3, S4} . C'est parce qu'ils ne se dominent pas (par exemple, S1 ne domine pas S4 car S4 est meilleur en ce qui concerne le nombre d'objets). S2 ne fait pas partie de la solution Pareto-optimale car il est dominé à la fois par S1 et S4 .

Dans le cas général, les ensembles de Pareto sont très difficiles à calculer, et peuvent être extrêmement grands. Dans votre cas particulier, 4 critères semblent raisonnables, mais cela peut toujours être surprenant.
Voici un pseudocode sur la façon de calculer un tel ensemble de solutions:

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

Ce petit pseudo-code ici a plus une valeur pour essayer de comprendre le concept d'efficacité de Pareto, je ne des conseils en boucle sur toutes les solutions réalisables d'une variation à un problème de sac à dos (trop coûteux).


0 commentaires

1
votes

En supposant que l'on ne puisse sélectionner que 0 ou 1 de chaque élément, il y a 2 ^ 12 = 4096 combinaisons possibles. Le nombre de solutions réalisables est de 3473. Le nombre de solutions non dominées (ou optimales de Pareto) est de 83.

J'ai utilisé deux approches différentes:

  1. Énumérez toutes les solutions possibles. Ensuite, filtrez toutes les solutions dominées (chaque solution doit être meilleure dans au moins un objectif que toutes les autres solutions).

  2. Ecrire une programmation en nombres entiers mixtes. Il trouve une solution, et ajoute une contrainte qui dit: il devrait être meilleur dans au moins un des objectifs que les solutions précédentes. (Dans le sens de ce modèle ) .

Les deux méthodes trouvent ces 83 solutions. Pour ce problème, l'énumération complète est plus rapide.

Notez que le nombre de solutions optimales Pareto peut augmenter rapidement. Voici quelques photos d'un tel Pareto ensemble optimal d'un problème de conception réel.

Notez qu'il n'y a pas de meilleure solution "unique". Toutes les solutions optimales de Pareto sont optimales. Ce n'est que lorsque vous faites des hypothèses sur les compromis entre les objectifs que vous pouvez réduire davantage le nombre de solutions optimales.


2 commentaires

juste curieux de savoir si j'ai bien compris: vous avez donc trouvé la ou les solution (s)?


Oui, il existe 83 solutions non dominées.



0
votes

J'élabore une solution de travail, mais c'est vraiment bruteforce, mais un peu optimisée. Je ne suis pas allé à travers la solution Pareto qui, je pense, est probablement une meilleure solution. Malheureusement, le script de Nina Sholz n'a pas fonctionné (du moins pour moi), alors j'ai créé celui-ci.

Juste pour laisser ici un exemple de travail (lire: ne pas utiliser pour de BIG data). >

PS - si quelqu'un peut écrire une phrase dans un meilleur anglais, commentez ci-dessous, je corrigerai ma mauvaise écriture.

/**
 * 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]);


0 commentaires