创建用于分发唯一项的算法

时间:2016-08-11 21:21:01

标签: javascript algorithm

我不确定如何为我的代码中的特定问题创建算法,所以我创建了这个难题

  • 有3个cookie怪物。
  • 每个怪物都有一个袋子,随机数量的饼干从0到有限数量(比方说100)。
  • 有许多不同类型的Cookie,但我们不知道有多少(我们可以在生成Cookie后找到)。
  • 每个怪物都会从它自己的包中吃掉1个饼干
    • 其他怪物都不能获得相同类型的cookie。
    • 如果两个或多个怪物的包里没有任何独特的味道,那么较低指数的怪物应该选择cookie而其他怪物不会得到cookie。
  • 我们希望确保每个怪物都有可能获得一个cookie。

// 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.

我以json格式为你制作了list of flavours

你会如何解决这个难题?

修订

我似乎遗漏了至少一个细节,所以如果你已经开始研究它,我会在这里添加任何改变,以免通过突然改变规则来混淆任何人:

  • #1 如果可能的话,怪物应该尝试从行李箱顶部获取一个cookie(较低的数组索引)。

2 个答案:

答案 0 :(得分:3)

我觉得你的问题很有趣,所以我通过创建这个算法给了它一个镜头:

单词

  1. 对于每个怪物,创建一个包含其包中所有cookie类型的数组(删除重复项)。
  2. 对于每个怪物,计算他们的选择(可用的口味)。
  3. 选择数量最少的怪物。
  4. 对于每个选项,请检查有多少其他怪物分享这种风格。
  5. 以最少数量的主人品尝味道,让怪物吃掉它。
  6. 从剩下的怪物袋中取出那种味道。
  7. 从步骤2开始重复,直到[没有剩下其他饥饿的怪物]或[每个剩下的怪物都没有更多的味道]。
  8. 在代码

    第一次曝光

    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>

答案 1 :(得分:1)

这个问题被称为“一个独特的代表系统”,并且有一些关于它们的定理以及至少一些已发布的解决它的算法。您可能会发现Hall's Marriage Theorem很有趣。在上下文中总结一下:

  

如果,并且只有当怪物的每个子集在那些怪物的袋子中有更多不同类型的饼干时,子集中都有怪物,每个怪物都可以吃一个饼干。

@blex写了一个算法,这是一个很好的贪心近似算法。当可能的cookie类型的数量远远大于怪物的数量时,它应该始终有效,但是当这些数字彼此非常接近且非常大时通常会失败。

下面是一个人为的例子,其中@blex的算法失败,有八个怪物和八个不同的cookie类型。 Blubite做出了错误的选择,他必须选择香草,但是他选择choco并且选择choco并没有可能的解决方案,因为Fluffy,Fred,Jub和Pillowpants是四个怪物的子集,它们之间只有三个选择(燕麦片,蓝莓和花生酱。

初始状态

Blubite的包包含:choco,vanilla
Fluffy的包包含:巧克力,蓝莓,花生酱
弗雷德的包包括:燕麦片,蓝莓,花生酱
Jub的包包括:燕麦片,蓝莓,花生酱
枕头袋包含:燕麦片,蓝莓,花生酱
Pinky的包包含:香草,燕麦片,糖,朗姆酒,绿色 Scuzzy的包包含:香草,燕麦片,糖,朗姆酒,绿色 Ziffu的包包含:香草,燕麦片,糖,朗姆酒,绿色

@blex算法

Blubite吃了:choco
蓬松吃:蓝莓
弗雷德吃了:燕麦片 Jub吃:花生酱
枕头吃了:没什么
小吃吃了:香草
Scuzzy吃了:糖
Ziffu吃了:朗姆酒

真正的解决方案

Blubite吃了:香草香 蓬松吃:巧克力
弗雷德吃了:燕麦片 Jub吃了:蓝莓
枕头吃:花生酱
小吃吃:糖
Scuzzy吃了:朗姆酒 Ziffu吃了:绿色

在一般情况下有a book containing two algorithms正确解决它,这里是an academic paper,从1956年开始,原始描述了这两种算法之一。我不打算在JavaScript中实现该算法,但如果我需要休息,我可能会在稍后重新审视。