[英]Creating an algorithm for distributing unique items
我不確定如何在代碼中針對特定問題創建算法,因此我創建了這個難題
。
// A cookie maker for good measure:
function cookieMaker(quant) {
// Fill in your own flavours...
const flavours = [...]
const cookies = []
for(let i = 0; i < quant; i++) {
cookies.push(flavours[Math.floor(Math.random() * flavours.length)])
}
return cookies
}
// This is our function
function pickCookies(monsters) {
...
}
pickCookies([
{
name: 'Fluffy',
cookies: ['choco', 'vanilla', 'blueberry']
},
{
name: 'Pillowpants',
cookies: ['choco', 'vanilla']
},
{
name: 'Pinky',
cookies: ['choco']
}
])
// Should return:
[
{
name: 'Fluffy',
eat: 'blueberry'
},
{
name: 'Pillowpants',
eat: 'vanilla'
},
{
name: 'Pinky',
eat: 'choco'
}
]
// Note, that it should also work if you shuffle the list.
您將如何解決這個難題?
我似乎至少遺漏了一個細節,因此,如果您已經開始研究它,我將在此處添加任何更改,以免突然更改規則而不會混淆任何人:
我發現您的問題很有趣,因此我通過創建此算法試了一下:
第一槍
var monsters = [{name:'Fluffy'}, {name:'Pillowpants'}, {name:'Pinky'}], flavors = ['choco', 'vanilla', 'blueberry', 'peanut butter'], maxNumberOfCookies = 6; $('#generateBtn').click(generateExample); generateExample(); function generateExample() { // Fill each monster's bag for(var i=0; i<monsters.length; i++) monsters[i].cookies = cookieMaker(); // 3, 2, 1, bon appetit! var res = pickCookies(monsters); displayResults(monsters, res); } function cookieMaker() { var cookies = [], // The quantity is random for each monster quant = Math.floor(Math.random() * maxNumberOfCookies); for(var i=0; i<quant; i++) { cookies.push(flavors[Math.floor(Math.random() * flavors.length)]) } return cookies; } function pickCookies(monsters) { var res = []; // List flavors available for each monster for(var i=0; i<monsters.length; i++) { var m = monsters[i], flavorsInBag = []; for(var j=0; j<m.cookies.length; j++) { if(flavorsInBag.indexOf(m.cookies[j]) < 0) { flavorsInBag.push(m.cookies[j]); } } res.push({name: m.name, flavors: flavorsInBag, eat: false}); } while(!allMonstersAte(res) && !noMoreFlavors(res)) { // Take the monster with the smallest number of options var monsterWithLeastFlavors = false; for(var i=0; i<res.length; i++) { if(!res[i].flavors.length) continue; if(!monsterWithLeastFlavors || res[i].flavors.length < monsterWithLeastFlavors.flavors.length) { monsterWithLeastFlavors = res[i]; } } // Select the flavor owned by the fewest monsters var flavorWithLeastOwners = monsterWithLeastFlavors.flavors[0], fewestNbOfOwners = res.length; for(var i=0; i<monsterWithLeastFlavors.flavors.length; i++) { var nbOfOwners = getNbOfOwners(monsterWithLeastFlavors.flavors[i], res); if(nbOfOwners < fewestNbOfOwners) { flavorWithLeastOwners = monsterWithLeastFlavors.flavors[i]; fewestNbOfOwners = nbOfOwners; } } makeMonsterEat(monsterWithLeastFlavors, flavorWithLeastOwners, res); } return res; } // Returns true if all monsters have a property "eat" != false function allMonstersAte(res) { return !res.some(function(monster){ return !monster.eat; }); } // Returns true if all monsters have no flavor left to choose from function noMoreFlavors(res) { return !res.some(function(monster){ return monster.flavors.length; }); } // Returns the number of monsters who have that flavor function getNbOfOwners(flavor, monsters) { return monsters.filter(function(monster){ monster.flavors.indexOf(flavor)>-1; }).length; } function makeMonsterEat(monster, flavor, res) { monster.flavors = []; monster.eat = flavor; for(var i=0; i<res.length; i++) { res[i].flavors = res[i].flavors.filter(function(fl){ return fl != flavor; }); } } function displayResults(monsters, res) { var initial = ""; for(var i=0; i<monsters.length; i++) { initial += '<b>' + monsters[i].name + '\\'s bag contains:</b> ' + (monsters[i].cookies.length ? monsters[i].cookies.join(', ') : '<span style="color:red">NOTHING</span>') + '<br>'; } var result = ""; for(var i=0; i<res.length; i++) { result += '<b>' + res[i].name + ' ate:</b> ' + (res[i].eat ? res[i].eat : '<span style="color:red">NOTHING</span>') + '<br>'; } $('#result').html('<h2>Initial state</h2>' + initial + '<h2>Result</h2>' + result ); }
*{margin: 0; padding: 0} body{font-family: Arial, Helvetica, sans-serif; padding: 1em; font-size: 14px} h2{font-size: 18px; margin: .3em 0}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <button id="generateBtn">Generate new example</button> <div id="result"></div>
更實用的方法
我不知道那不是你想要的,但是我嘗試了...
var monsters = [{name:'Fluffy'}, {name:'Pillowpants'}, {name:'Pinky'}], flavors = ['choco', 'vanilla', 'blueberry', 'peanut butter'], maxNumberOfCookies = 6; $.getJSON('https://cdn.rawgit.com/demux/ad32c612b303aa12d1cdf043225fa1d2/raw/3a037fb7ad30d8a383526ebc62b3671a1656c06d/flavours.json') .done(init); function init(data) { flavors = data; $('body').html('<button id="generateBtn">Generate new example</button><div id="result"></div>'); $('#generateBtn').click(generateExample); generateExample(); } function generateExample() { // Fill each monster's bag for (var i = 0; i < monsters.length; i++) monsters[i].cookies = cookieMaker(Math.floor(Math.random() * maxNumberOfCookies)); // 3, 2, 1, bon appetit! var res = pickCookies(monsters); displayResults(monsters, res); } function cookieMaker(quant) { var cookies = []; for (var i = 0; i < quant; i++) { cookies.push(flavors[Math.floor(Math.random() * flavors.length)]) } return cookies; } /* * Returns a new Array of monsters who ate unique cookies */ function pickCookies(monsters) { var res = getMonstersWithFlavors(monsters); while (!allMonstersAte(res) && !noMoreFlavors(res)) { var monsterIndex = indexOfMonsterWithLeastFlavors(res), flavor = flavorWithLeastOwners(res[monsterIndex].flavors, res); for (var i = 0; i < res.length; i++) { if (i == monsterIndex) res[i] = makeMonsterEat(res[i], flavor); else res[i] = removeFlavor(res[i], flavor); } } return res; } /* * Returns a new Array of monsters with their flavors */ function getMonstersWithFlavors(monsters) { return monsters.map(function(monster) { return { name: monster.name, flavors: removeDuplicates(monster.cookies), eat: false }; }); } /* * Returns a new Array without duplicates */ function removeDuplicates(arr) { return Array.from(new Set(arr)); } /* * Returns the index of the monster with least flavors */ function indexOfMonsterWithLeastFlavors(monsters) { var tmp = { monsterIndex: -1, count: false }; for (var i = 0; i < monsters.length; i++) { if (!monsters[i].flavors.length) continue; if (!tmp.count || monsters[i].flavors.length < tmp.count) { tmp = { monsterIndex: i, count: monsters[i].flavors.length }; } } return tmp.monsterIndex; } /* * Returns the flavor owned by least monsters */ function flavorWithLeastOwners(flavors, monsters) { var tmp = { flavor: '', count: false }; for (var i = 0; i < flavors.length; i++) { var nbOfOwners = getNbOfOwners(flavors[i], monsters); if (!tmp.count || nbOfOwners < tmp.count) { tmp = { flavor: flavors[i], count: nbOfOwners }; } } return tmp.flavor; } /* * Checks if all monsters have a property "eat" != false */ function allMonstersAte(res) { return !res.some(function(monster) { return !monster.eat; }); } /* * Checks if all monsters have no flavor left to choose from */ function noMoreFlavors(res) { return !res.some(function(monster) { return monster.flavors.length; }); } /* * Returns the number of monsters who have that flavor */ function getNbOfOwners(flavor, monsters) { return monsters.filter(function(monster) { monster.flavors.indexOf(flavor) > -1; }).length; } /* * Returns a new monster Object with the cookie they ate */ function makeMonsterEat(monster, flavor) { return { name: monster.name, flavors: [], eat: flavor }; } /* * Returns a new monster Object without the cookie flavor */ function removeFlavor(monster, flavor) { return { name: monster.name, flavors: monster.flavors.filter(function(fl) { return fl != flavor }), eat: monster.eat }; } function displayResults(monsters, res) { var initial = ""; for (var i = 0; i < monsters.length; i++) { initial += '<b>' + monsters[i].name + '\\'s bag contains:</b> ' + (monsters[i].cookies.length ? monsters[i].cookies.join(', ') : '<span style="color:red">NOTHING</span>') + '<br>'; } var result = ""; for (var i = 0; i < res.length; i++) { result += '<b>' + res[i].name + ' ate:</b> ' + (res[i].eat ? res[i].eat : '<span style="color:red">NOTHING</span>') + '<br>'; } $('#result').html('<h2>Initial state</h2>' + initial + '<h2>Result</h2>' + result ); }
*{margin: 0; padding: 0} body{font-family: Arial, Helvetica, sans-serif; padding: 1em; font-size: 14px} h2{font-size: 18px; margin: .3em 0}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <h2>Loading...</h2>
這個問題稱為“不同代表系統”,關於它們有一些定理,並且至少有一些公開的算法可以解決該問題。 您可能會發現霍爾的婚姻定理很有趣。 在上下文中進行總結:
當且僅當對於每個怪物子集,在那些怪物的袋子中存在更多不同類型的餅干,然后在子集中存在怪物時,每個怪物才有可能吃餅干。
@blex編寫了一個算法,它是一個很好的貪婪近似算法。 當可能的Cookie類型數量遠大於怪獸數量時,它應該總是可以工作,但是當這些數量彼此非常接近並且也非常大時,通常會失敗。
以下是一個人為設計的示例,其中@blex的算法失敗,出現了8個怪物和8個不同的Cookie類型。 馬上,Blubite做出了錯誤的選擇,他必須選擇香草,但他選擇了choco,而且這種選擇沒有可能的解決方法,因為Fluffy,Fred,Jub和Pillowpants是四個怪物的子集,它們之間只有三個選擇(燕麥片,藍莓和花生醬)。
Blubite的手提袋包含:巧克力,香草
蓬松的袋子包含:巧克力,藍莓,花生醬
弗雷德(Fred)的書包包含:燕麥片,藍莓,花生醬
奶壺的袋子包含:燕麥片,藍莓,花生醬
枕頭袋包含:燕麥粥,藍莓,花生醬
小指袋包含:香草,燕麥片,糖,朗姆酒,綠色
Scuzzy的袋子包含:香草,燕麥片,糖,朗姆酒,綠色
Ziffu的手提袋內含:香草,燕麥片,糖,朗姆酒,綠色
Blubite吃:巧克力
蓬松吃:藍莓
弗雷德(Fred)吃:燕麥片
吃過的食物:花生醬
Pillowpants吃:NOTHING
吃了小指:香草
吃不清:糖
Ziffu ate:朗姆酒
吃了Blubite:香草
蓬松吃:巧克力
弗雷德(Fred)吃:燕麥片
吃過的食物:藍莓
吃了枕頭:花生醬
吃了小指:糖
吃不清:朗姆酒
Ziffu吃:綠色
這里有一本書,其中包含兩種可以在一般情況下正確解決問題的算法 ,這是一篇 1956年的學術論文 ,上面對這兩種算法之一進行了原始描述。 我不打算在JavaScript中實現該算法,但是如果需要休息,可以稍后再訪問。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.