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!
4 Réponses :
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);
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
?
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).
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:
É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).
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.
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.
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]);
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".