安排一组比赛,以便每个参与者的比赛不会太接近

Dar*_*ark 3 javascript algorithm combinatorics

主要目标:

创建一个 JavaScript 函数,帮助每个战士有更好的战斗顺序。一些拳手在整个比赛中有七次参赛。在fighter1fighter2列中,在给定的数据中,您可以看到每个战士的战斗编号。每个同名战士的差距太近或太远。我们的目标是:

  1. 每个同名战士的差距应该有10到30个战斗编号差异。10 是最小间隙,30 是最大间隙。
  2. `fightNumber 应该是唯一的,范围仅在 1 到 162 之间。(战斗编号不能重复)

对象数据:

  1. id=战斗id
  2. 战斗机1战斗机2 =这些是将要匹配的战斗机。战斗机 1 与战斗机 2 - 战斗机有不同的名称。每个可能的名称都在战斗机 1 或战斗机 2 中
  3. FightNumber - 这是每场战斗的唯一序列号。一旦产生新的序列就可以更新(范围是从1到条目数据的总长度(本次测试我打了162场))

创建该函数的目的:

这将帮助每个战士不要等待太久或太短,我们需要给予间隙 10 - 30 战斗编号差异。

我的目标例如是:

战斗机 1:“V”是 FightNumber 1。它的下一场战斗应该是10(最小) 或 30(最大)。但在我当前的函数中,它在 FightNumber 6 处再次匹配 (仅等于 5 FightNumber 差异)

这意味着我当前的函数不满足我的条件,即等于(请参阅上述条件)。

我认为我的身体状况有问题。有没有办法可以实现我的目标?

  const data = [
    { id: "1", fighter1: "V", fighter2: "DD", fightNumber: 1 },
    { id: "2", fighter1: "R", fighter2: "V", fightNumber: 2 },
    { id: "3", fighter1: "J", fighter2: "X", fightNumber: 3 },
    { id: "4", fighter1: "H", fighter2: "KK", fightNumber: 4 },
    { id: "5", fighter1: "DD", fighter2: "MM", fightNumber: 5 },
    { id: "6", fighter1: "V", fighter2: "Z", fightNumber: 6 },
    { id: "7", fighter1: "V", fighter2: "SS", fightNumber: 7 },
    { id: "8", fighter1: "C", fighter2: "V", fightNumber: 8 },
    { id: "9", fighter1: "P", fighter2: "joker", fightNumber: 9 },
    { id: "10", fighter1: "P", fighter2: "LL", fightNumber: 10 },
    { id: "11", fighter1: "Y", fighter2: "QQ", fightNumber: 11 },
    { id: "12", fighter1: "R", fighter2: "OO", fightNumber: 12 },
    { id: "13", fighter1: "D", fighter2: "R", fightNumber: 13 },
    { id: "14", fighter1: "X", fighter2: "DD", fightNumber: 14 },
    { id: "15", fighter1: "P", fighter2: "W", fightNumber: 15 },
    { id: "16", fighter1: "Y", fighter2: "BB", fightNumber: 16 },
    { id: "17", fighter1: "D", fighter2: "O", fightNumber: 17 },
    { id: "18", fighter1: "W", fighter2: "CC", fightNumber: 18 },
    { id: "19", fighter1: "JJ", fighter2: "KK", fightNumber: 19 },
    { id: "20", fighter1: "I", fighter2: "T", fightNumber: 20 },
    { id: "21", fighter1: "T", fighter2: "MM", fightNumber: 21 },
    { id: "22", fighter1: "R", fighter2: "RR", fightNumber: 22 },
    { id: "23", fighter1: "T", fighter2: "FF", fightNumber: 23 },
    { id: "24", fighter1: "G", fighter2: "DD", fightNumber: 24 },
    { id: "25", fighter1: "L", fighter2: "FF", fightNumber: 25 },
    { id: "26", fighter1: "S", fighter2: "EE", fightNumber: 26 },
    { id: "27", fighter1: "BB", fighter2: "GG", fightNumber: 27 },
    { id: "28", fighter1: "E", fighter2: "MM", fightNumber: 28 },
    { id: "29", fighter1: "J", fighter2: "T", fightNumber: 29 },
    { id: "30", fighter1: "V", fighter2: "X", fightNumber: 30 },
    { id: "31", fighter1: "CC", fighter2: "DD", fightNumber: 31 },
    { id: "32", fighter1: "Q", fighter2: "EE", fightNumber: 32 },
    { id: "33", fighter1: "C", fighter2: "T", fightNumber: 33 },
    { id: "34", fighter1: "H", fighter2: "U", fightNumber: 34 },
    { id: "35", fighter1: "Z", fighter2: "II", fightNumber: 35 },
    { id: "36", fighter1: "A", fighter2: "JJ", fightNumber: 36 },
    { id: "37", fighter1: "H", fighter2: "T", fightNumber: 37 },
    { id: "38", fighter1: "D", fighter2: "OO", fightNumber: 38 },
    { id: "39", fighter1: "L", fighter2: "O", fightNumber: 39 },
    { id: "40", fighter1: "T", fighter2: "DD", fightNumber: 40 },
    { id: "41", fighter1: "F", fighter2: "MM", fightNumber: 41 },
    { id: "42", fighter1: "N", fighter2: "V", fightNumber: 42 },
    { id: "43", fighter1: "X", fighter2: "EE", fightNumber: 43 },
    { id: "44", fighter1: "G", fighter2: "PP", fightNumber: 44 },
    { id: "45", fighter1: "I", fighter2: "Q", fightNumber: 45 },
    { id: "46", fighter1: "K", fighter2: "CC", fightNumber: 46 },
    { id: "47", fighter1: "F", fighter2: "DD", fightNumber: 47 },
    { id: "48", fighter1: "Q", fighter2: "AA", fightNumber: 48 },
    { id: "49", fighter1: "AA", fighter2: "PP", fightNumber: 49 },
    { id: "50", fighter1: "LL", fighter2: "TT", fightNumber: 50 },
    { id: "51", fighter1: "P", fighter2: "Z", fightNumber: 51 },
    { id: "52", fighter1: "W", fighter2: "FF", fightNumber: 52 },
    { id: "53", fighter1: "MM", fighter2: "RR", fightNumber: 53 },
    { id: "54", fighter1: "FF", fighter2: "II", fightNumber: 54 },
    { id: "55", fighter1: "F", fighter2: "P", fightNumber: 55 },
    { id: "56", fighter1: "G", fighter2: "MM", fightNumber: 56 },
    { id: "57", fighter1: "O", fighter2: "BB", fightNumber: 57 },
    { id: "58", fighter1: "H", fighter2: "PP", fightNumber: 58 },
    { id: "59", fighter1: "K", fighter2: "O", fightNumber: 59 },
    { id: "60", fighter1: "P", fighter2: "BB", fightNumber: 60 },
    { id: "61", fighter1: "HH", fighter2: "KK", fightNumber: 61 },
    { id: "62", fighter1: "B", fighter2: "K", fightNumber: 62 },
    { id: "63", fighter1: "Y", fighter2: "KK", fightNumber: 63 },
    { id: "64", fighter1: "LL", fighter2: "OO", fightNumber: 64 },
    { id: "65", fighter1: "PP", fighter2: "QQ", fightNumber: 65 },
    { id: "66", fighter1: "M", fighter2: "II", fightNumber: 66 },
    { id: "67", fighter1: "E", fighter2: "KK", fightNumber: 67 },
    { id: "68", fighter1: "Q", fighter2: "LL", fightNumber: 68 },
    { id: "69", fighter1: "AA", fighter2: "CC", fightNumber: 69 },
    { id: "70", fighter1: "NN", fighter2: "OO", fightNumber: 70 },
    { id: "71", fighter1: "E", fighter2: "FF", fightNumber: 71 },
    { id: "72", fighter1: "G", fighter2: "K", fightNumber: 72 },
    { id: "73", fighter1: "C", fighter2: "Q", fightNumber: 73 },
    { id: "74", fighter1: "D", fighter2: "U", fightNumber: 74 },
    { id: "75", fighter1: "O", fighter2: "OO", fightNumber: 75 },
    { id: "76", fighter1: "U", fighter2: "Z", fightNumber: 76 },
    { id: "77", fighter1: "X", fighter2: "JJ", fightNumber: 77 },
    { id: "78", fighter1: "G", fighter2: "QQ", fightNumber: 78 },
    { id: "79", fighter1: "Q", fighter2: "Z", fightNumber: 79 },
    { id: "80", fighter1: "JJ", fighter2: "NN", fightNumber: 80 },
    { id: "81", fighter1: "F", fighter2: "QQ", fightNumber: 81 },
    { id: "82", fighter1: "QQ", fighter2: "SS", fightNumber: 82 },
    { id: "83", fighter1: "EE", fighter2: "QQ", fightNumber: 83 },
    { id: "84", fighter1: "KK", fighter2: "PP", fightNumber: 84 },
    { id: "85", fighter1: "G", fighter2: "J", fightNumber: 85 },
    { id: "86", fighter1: "EE", fighter2: "FF", fightNumber: 86 },
    { id: "87", fighter1: "D", fighter2: "HH", fightNumber: 87 },
    { id: "88", fighter1: "H", fighter2: "OO", fightNumber: 88 },
    { id: "89", fighter1: "O", fighter2: "R", fightNumber: 89 },
    { id: "90", fighter1: "K", fighter2: "HH", fightNumber: 90 },
    { id: "91", fighter1: "AA", fighter2: "TT", fightNumber: 91 },
    { id: "92", fighter1: "M", fighter2: "CC", fightNumber: 92 },
    { id: "93", fighter1: "U", fighter2: "EE", fightNumber: 93 },
    { id: "94", fighter1: "Z", fighter2: "FF", fightNumber: 94 },
    { id: "95", fighter1: "HH", fighter2: "JJ", fightNumber: 95 },
    { id: "96", fighter1: "C", fighter2: "E", fightNumber: 96 },
    { id: "97", fighter1: "H", fighter2: "I", fightNumber: 97 },
    { id: "98", fighter1: "C", fighter2: "U", fightNumber: 98 },
    { id: "99", fighter1: "F", fighter2: "X", fightNumber: 99 },
    { id: "100", fighter1: "G", fighter2: "SS", fightNumber: 100 },
    { id: "101", fighter1: "W", fighter2: "JJ", fightNumber: 101 },
    { id: "102", fighter1: "C", fighter2: "P", fightNumber: 102 },
    { id: "103", fighter1: "K", fighter2: "W", fightNumber: 103 },
    { id: "104", fighter1: "CC", fighter2: "TT", fightNumber: 104 },
    { id: "105", fighter1: "L", fighter2: "TT", fightNumber: 105 },
    { id: "106", fighter1: "J", fighter2: "EE", fightNumber: 106 },
    { id: "107", fighter1: "M", fighter2: "Y", fightNumber: 107 },
    { id: "108", fighter1: "Z", fighter2: "AA", fightNumber: 108 },
    { id: "109", fighter1: "E", fighter2: "BB", fightNumber: 109 },
    { id: "110", fighter1: "F", fighter2: "I", fightNumber: 110 },
    { id: "111", fighter1: "N", fighter2: "RR", fightNumber: 111 },
    { id: "112", fighter1: "D", fighter2: "NN", fightNumber: 112 },
    { id: "113", fighter1: "L", fighter2: "HH", fightNumber: 113 },
    { id: "114", fighter1: "J", fighter2: "L", fightNumber: 114 },
    { id: "115", fighter1: "L", fighter2: "U", fightNumber: 115 },
    { id: "116", fighter1: "BB", fighter2: "TT", fightNumber: 116 },
    { id: "117", fighter1: "J", fighter2: "U", fightNumber: 117 },
    { id: "118", fighter1: "A", fighter2: "RR", fightNumber: 118 },
    { id: "119", fighter1: "I", fighter2: "SS", fightNumber: 119 },
    { id: "120", fighter1: "J", fighter2: "SS", fightNumber: 120 },
    { id: "121", fighter1: "B", fighter2: "NN", fightNumber: 121 },
    { id: "122", fighter1: "OO", fighter2: "PP", fightNumber: 122 },
    { id: "123", fighter1: "S", fighter2: "X", fightNumber: 123 },
    { id: "124", fighter1: "S", fighter2: "BB", fightNumber: 124 },
    { id: "125", fighter1: "N", fighter2: "II", fightNumber: 125 },
    { id: "126", fighter1: "R", fighter2: "II", fightNumber: 126 },
    { id: "127", fighter1: "S", fighter2: "W", fightNumber: 127 },
    { id: "128", fighter1: "II", fighter2: "NN", fightNumber: 128 },
    { id: "129", fighter1: "Q", fighter2: "Y", fightNumber: 129 },
    { id: "130", fighter1: "B", fighter2: "W", fightNumber: 130 },
    { id: "131", fighter1: "E", fighter2: "M", fightNumber: 131 },
    { id: "132", fighter1: "GG", fighter2: "QQ", fightNumber: 132 },
    { id: "133", fighter1: "S", fighter2: "GG", fightNumber: 133 },
    { id: "134", fighter1: "S", fighter2: "PP", fightNumber: 134 },
    { id: "135", fighter1: "B", fighter2: "GG", fightNumber: 135 },
    { id: "136", fighter1: "M", fighter2: "NN", fightNumber: 136 },
    { id: "137", fighter1: "F", fighter2: "Y", fightNumber: 137 },
    { id: "138", fighter1: "I", fighter2: "R", fightNumber: 138 },
    { id: "139", fighter1: "KK", fighter2: "SS", fightNumber: 139 },
    { id: "140", fighter1: "D", fighter2: "GG", fightNumber: 140 },
    { id: "141", fighter1: "H", fighter2: "AA", fightNumber: 141 },
    { id: "142", fighter1: "A", fighter2: "MM", fightNumber: 142 },
    { id: "143", fighter1: "NN", fighter2: "TT", fightNumber: 143 },
    { id: "144", fighter1: "L", fighter2: "LL", fightNumber: 144 },
    { id: "145", fighter1: "S", fighter2: "LL", fightNumber: 145 },
    { id: "146", fighter1: "O", fighter2: "CC", fightNumber: 146 },
    { id: "147", fighter1: "GG", fighter2: "SS", fightNumber: 147 },
    { id: "148", fighter1: "N", fighter2: "HH", fightNumber: 148 },
    { id: "149", fighter1: "A", fighter2: "II", fightNumber: 149 },
    { id: "150", fighter1: "B", fighter2: "LL", fightNumber: 150 },
    { id: "151", fighter1: "K", fighter2: "M", fightNumber: 151 },
    { id: "152", fighter1: "A", fighter2: "N", fightNumber: 152 },
    { id: "153", fighter1: "M", fighter2: "HH", fightNumber: 153 },
    { id: "154", fighter1: "A", fighter2: "E", fightNumber: 154 },
    { id: "155", fighter1: "N", fighter2: "GG", fightNumber: 155 },
    { id: "156", fighter1: "AA", fighter2: "RR", fightNumber: 156 },
    { id: "157", fighter1: "B", fighter2: "I", fightNumber: 157 },
    { id: "158", fighter1: "C", fighter2: "Y", fightNumber: 158 },
    { id: "159", fighter1: "RR", fighter2: "TT", fightNumber: 159 },
    { id: "160", fighter1: "N", fighter2: "joker", fightNumber: 160 },
    { id: "161", fighter1: "JJ", fighter2: "RR", fightNumber: 161 },
    { id: "162", fighter1: "A", fighter2: "B", fightNumber: 162 },
  ];
  function rearrangeFightNumbers(data) {
    const totaldata = data.length;

    // Helper function to find the last fight number for a fighter
    function findLastFightNumber(fighterName) {
      const fights = data.filter(
        (fight) =>
          fight.fighter1 === fighterName || fight.fighter2 === fighterName
      );
      return Math.max(...fights.map((fight) => fight.fightNumber));
    }

    // Calculate new fight numbers for each fighter with the desired gap
    const uniqueFighters = Array.from(
      new Set(
        data
          .map((fight) => fight.fighter1)
          .concat(data.map((fight) => fight.fighter2))
      )
    );

    for (const fighter of uniqueFighters) {
      const fighterFights = data.filter(
        (fight) => fight.fighter1 === fighter || fight.fighter2 === fighter
      );
      let totalFights = fighterFights.length;
      let minGap = 10;
      let maxGap = 30;

      // Adjust the gap if necessary to fit the desired criteria
      while ((totalFights - 1) * minGap > totaldata) {
        minGap--;
      }
      while ((totalFights - 1) * maxGap < totaldata) {
        maxGap++;
      }

      let currentFightNumber = findLastFightNumber(fighter) + minGap;

      for (const fight of fighterFights) {
        fight.fightNumber = Math.min(currentFightNumber, data.length);
        currentFightNumber +=
          minGap + Math.floor(Math.random() * (maxGap - minGap + 1));
      }
    }

    // Sort the fights based on their new fight numbers
    data.sort((a, b) => a.fightNumber - b.fightNumber);

    // Ensure fight numbers are unique and within the range of 1 to 162
    for (let i = 0; i < data.length; i++) {
      data[i].fightNumber = Math.min(i + 1, totaldata);
    }

    return data;
  }
  // Usage:
  const updatedData = rearrangeFightNumbers(data);
  console.log(updatedData);
Run Code Online (Sandbox Code Playgroud)

vas*_*vas 5

你不能天真地重新排列比赛顺序。

\n

我没有计算机科学或组合数学学位(也没有任何其他学位),也没有任何理论计算机科学背景。尽管如此,我知道这是一个不平凡的组合数学问题。

\n

你的数据有47名战士。有 1081 种可能的匹配 ( f*(f-1)/2)。每位拳手都会参加 46 场比赛。因此,对于任何一名战士来说,都有1081 - 46不包括该战士的对决。这是在该战士的战斗之间创建间隔的可用战斗数量。因此,理论上所有战斗机战斗的最大可能间隔是1081 - 46)/47, 或 22。如果这不明显,请写出 6 名战士的所有对决。那么数学应该是有意义的。

\n
\n

\xe2\x80\xbc\xef\xb8\x8f 我做了一些蒙特卡罗测试,看起来真正的上限是非线性的,值进一步下降\n低于我计算的上限。我对 46 架战斗机进行蒙特卡洛测试,所有迭代的最大分离度仅为 15,而我上面的公式得出的分离度为 21。

\n
\n

您问题中的场景仅包含 1081 种可能的匹配中的 162 种。您可能会认为这使问题变得更容易,但事实并非如此。搜索的解决方案空间仍然巨大!这 162 场比赛有 1.2296942187394488e+289 ( 162!) 种可能的赛程。那是:

\n
12,296,942,187,394,488,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000  possible schedules.\n
Run Code Online (Sandbox Code Playgroud)\n

不仅如此,根据所选择的匹配,最大可能的分离可能小于理论最大值。您可以在极端情况下看到这一点:为战斗机设置 20 场比赛A,并且只设置 18 场不包括 的比赛A。您将被迫至少安排一场背靠背的比赛A。在你的情况下,除了 之外,战斗是均匀分布的joker,所以我认为 22 上限仍然是可能的,但不能再多了,因为相同的上限公式仍然适用。

\n
\n

\xe2\x80\xbc\xef\xb8\x8f 换句话说,不可能找到间隔为 23 的时间表。甚至不要考虑 30。真正的最大间隔可能更低,因为我的蒙特卡洛测试似乎表明了这一点。

\n
\n

更重要的是:

\n
\n

\xe2\x80\xbc\xef\xb8\x8f 间隔为 22 的计划数量只是我上面显示的巨大数字的一小部分。您正在大海捞针中寻找原子。没有哪个 CPU 足够快来尝试所有可能性。

\n
\n

你需要一个智能算法

\n

这意味着您必须:

\n
    \n
  • 如果可能的话,提出一种“知道”确切如何找到最佳时间表的算法
  • \n
  • 提出一种启发式算法,该算法结合了对问题的某种理解,知道哪些选择更有可能产生更好的结果,最重要的是,避免死胡同。它不会找到最佳答案,但会在合理的时间内找到一个相当好的答案。
  • \n
\n

我没有时间弄清楚你想用你的代码做什么,但我可以看出它不是上述任何一个。

\n

我想出了一个启发式算法。

\n

下面是您的输入数据,去除了无关的内容。我的解决方案就是以此为基础的。如有必要,您可以修改它以对您的数据结构进行操作,但正如其他人指出的那样,如果首先将问题分解为MRE形式,则问题最好得到解决。

\n
12,296,942,187,394,488,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000  possible schedules.\n
Run Code Online (Sandbox Code Playgroud)\n

下面是算法的核心。本质上,当它迭代地安排比赛时,它会不断地重新计算哪些比赛更“需要”安排接下来的比赛(“安排压力”)。

\n

例如,假设一场比赛中,每位拳手都只剩下一场\n计划外比赛。稍后很容易找到minSep适合他们的位置,因此该比赛的调度压力很小。但在另一场比赛中,一名选手还剩 6 场计划外比赛,而另一名选手还剩 4 场。在维持 的同时将所有剩余的\n战斗放入剩余的位置将会更加困难minSep,因此现在安排它们的压力要大得多。

\n
\n

该算法是一种贪婪算法,总是尽快调度具有较高调度压力的比赛来做出调度决策。

\n
\n
const matchups = [\n    [\'V\', \'DD\'],\n    [\'R\', \'V\'],\n    [\'J\', \'X\'],\n    [\'H\', \'KK\'],\n    [\'DD\', \'MM\'],\n    [\'V\', \'Z\'],\n    [\'V\', \'SS\'],\n    [\'C\', \'V\'],\n    [\'P\', \'joker\'],\n    [\'P\', \'LL\'],\n    [\'Y\', \'QQ\'],\n    [\'R\', \'OO\'],\n    [\'D\', \'R\'],\n    [\'X\', \'DD\'],\n    [\'P\', \'W\'],\n    [\'Y\', \'BB\'],\n    [\'D\', \'O\'],\n    [\'W\', \'CC\'],\n    [\'JJ\', \'KK\'],\n    [\'I\', \'T\'],\n    [\'T\', \'MM\'],\n    [\'R\', \'RR\'],\n    [\'T\', \'FF\'],\n    [\'G\', \'DD\'],\n    [\'L\', \'FF\'],\n    [\'S\', \'EE\'],\n    [\'BB\', \'GG\'],\n    [\'E\', \'MM\'],\n    [\'J\', \'T\'],\n    [\'V\', \'X\'],\n    [\'CC\', \'DD\'],\n    [\'Q\', \'EE\'],\n    [\'C\', \'T\'],\n    [\'H\', \'U\'],\n    [\'Z\', \'II\'],\n    [\'A\', \'JJ\'],\n    [\'H\', \'T\'],\n    [\'D\', \'OO\'],\n    [\'L\', \'O\'],\n    [\'T\', \'DD\'],\n    [\'F\', \'MM\'],\n    [\'N\', \'V\'],\n    [\'X\', \'EE\'],\n    [\'G\', \'PP\'],\n    [\'I\', \'Q\'],\n    [\'K\', \'CC\'],\n    [\'F\', \'DD\'],\n    [\'Q\', \'AA\'],\n    [\'AA\', \'PP\'],\n    [\'LL\', \'TT\'],\n    [\'P\', \'Z\'],\n    [\'W\', \'FF\'],\n    [\'MM\', \'RR\'],\n    [\'FF\', \'II\'],\n    [\'F\', \'P\'],\n    [\'G\', \'MM\'],\n    [\'O\', \'BB\'],\n    [\'H\', \'PP\'],\n    [\'K\', \'O\'],\n    [\'P\', \'BB\'],\n    [\'HH\', \'KK\'],\n    [\'B\', \'K\'],\n    [\'Y\', \'KK\'],\n    [\'LL\', \'OO\'],\n    [\'PP\', \'QQ\'],\n    [\'M\', \'II\'],\n    [\'E\', \'KK\'],\n    [\'Q\', \'LL\'],\n    [\'AA\', \'CC\'],\n    [\'NN\', \'OO\'],\n    [\'E\', \'FF\'],\n    [\'G\', \'K\'],\n    [\'C\', \'Q\'],\n    [\'D\', \'U\'],\n    [\'O\', \'OO\'],\n    [\'U\', \'Z\'],\n    [\'X\', \'JJ\'],\n    [\'G\', \'QQ\'],\n    [\'Q\', \'Z\'],\n    [\'JJ\', \'NN\'],\n    [\'F\', \'QQ\'],\n    [\'QQ\', \'SS\'],\n    [\'EE\', \'QQ\'],\n    [\'KK\', \'PP\'],\n    [\'G\', \'J\'],\n    [\'EE\', \'FF\'],\n    [\'D\', \'HH\'],\n    [\'H\', \'OO\'],\n    [\'O\', \'R\'],\n    [\'K\', \'HH\'],\n    [\'AA\', \'TT\'],\n    [\'M\', \'CC\'],\n    [\'U\', \'EE\'],\n    [\'Z\', \'FF\'],\n    [\'HH\', \'JJ\'],\n    [\'C\', \'E\'],\n    [\'H\', \'I\'],\n    [\'C\', \'U\'],\n    [\'F\', \'X\'],\n    [\'G\', \'SS\'],\n    [\'W\', \'JJ\'],\n    [\'C\', \'P\'],\n    [\'K\', \'W\'],\n    [\'CC\', \'TT\'],\n    [\'L\', \'TT\'],\n    [\'J\', \'EE\'],\n    [\'M\', \'Y\'],\n    [\'Z\', \'AA\'],\n    [\'E\', \'BB\'],\n    [\'F\', \'I\'],\n    [\'N\', \'RR\'],\n    [\'D\', \'NN\'],\n    [\'L\', \'HH\'],\n    [\'J\', \'L\'],\n    [\'L\', \'U\'],\n    [\'BB\', \'TT\'],\n    [\'J\', \'U\'],\n    [\'A\', \'RR\'],\n    [\'I\', \'SS\'],\n    [\'J\', \'SS\'],\n    [\'B\', \'NN\'],\n    [\'OO\', \'PP\'],\n    [\'S\', \'X\'],\n    [\'S\', \'BB\'],\n    [\'N\', \'II\'],\n    [\'R\', \'II\'],\n    [\'S\', \'W\'],\n    [\'II\', \'NN\'],\n    [\'Q\', \'Y\'],\n    [\'B\', \'W\'],\n    [\'E\', \'M\'],\n    [\'GG\', \'QQ\'],\n    [\'S\', \'GG\'],\n    [\'S\', \'PP\'],\n    [\'B\', \'GG\'],\n    [\'M\', \'NN\'],\n    [\'F\', \'Y\'],\n    [\'I\', \'R\'],\n    [\'KK\', \'SS\'],\n    [\'D\', \'GG\'],\n    [\'H\', \'AA\'],\n    [\'A\', \'MM\'],\n    [\'NN\', \'TT\'],\n    [\'L\', \'LL\'],\n    [\'S\', \'LL\'],\n    [\'O\', \'CC\'],\n    [\'GG\', \'SS\'],\n    [\'N\', \'HH\'],\n    [\'A\', \'II\'],\n    [\'B\', \'LL\'],\n    [\'K\', \'M\'],\n    [\'A\', \'N\'],\n    [\'M\', \'HH\'],\n    [\'A\', \'E\'],\n    [\'N\', \'GG\'],\n    [\'AA\', \'RR\'],\n    [\'B\', \'I\'],\n    [\'C\', \'Y\'],\n    [\'RR\', \'TT\'],\n    [\'N\', \'joker\'],\n    [\'JJ\', \'RR\'],\n    [\'A\', \'B\']\n]\n
Run Code Online (Sandbox Code Playgroud)\n

该函数获取比赛和目标间隔值,并调用上面的代码来尝试制定一个在每场战斗之间间隔如此大的时间表。如果失败,它会以越来越小的目标间隔再次尝试,直到成功:

\n
function scheduleMatchups(matchups, minSep) {\n    const unscheduledMatchups = [...matchups];\n    const schedule = [];\n    const fighterLastMatchup = {};\n    let fightNum = 0;\n    let complete = true;\n    outer: while (unscheduledMatchups.length > 0) {\n        fightNum++;\n        sortBySchedulingPressure(unscheduledMatchups);\n        for (let i = 0; i < unscheduledMatchups.length; i++) {\n            const mu = unscheduledMatchups[i];\n            const f1Last = fighterLastMatchup[mu[0]] ?? -minSep;\n            const f2Last = fighterLastMatchup[mu[1]] ?? -minSep;\n            if (fightNum - f1Last >= minSep &&\n                fightNum - f2Last >= minSep) {\n                const smu = { fightNum: fightNum, matchup: mu };\n                schedule.push(smu);\n                fighterLastMatchup[mu[0]] = fightNum;\n                fighterLastMatchup[mu[1]] = fightNum;\n                unscheduledMatchups.splice(i, 1);\n                continue outer;\n            }\n        }\n        complete = false;\n        break;\n    }\n    return { schedule, complete };\n}\n\nfunction sortBySchedulingPressure(matchups) {\n    const fToMu = mapFightersToMatchups(matchups);\n    // sort by pressure, desc\n    matchups.sort((mu1, mu2) => {\n        const m1f1 = fToMu[mu1[0]].length;\n        const m1f2 = fToMu[mu1[1]].length;\n        const m1Min = Math.min(m1f1, m1f2);\n        const m1Max = Math.max(m1f1, m1f2);\n        const m2f1 = fToMu[mu2[0]].length;\n        const m2f2 = fToMu[mu2[1]].length;\n        const m2Min = Math.min(m2f1, m2f2);\n        const m2Max = Math.max(m2f1, m2f2);\n        if (m1Min != m2Min)\n            return m2Min - m1Min;\n        if (m1Max != m2Max)\n            return m2Max - m1Max;\n        return 0;\n    });\n}\n\nfunction mapFightersToMatchups(matchups) {\n    return matchups\n        .reduce(function (f2m, mu) {\n        if (mu[0] in f2m) {\n            f2m[mu[0]].push(mu);\n        }\n        else {\n            f2m[mu[0]] = [mu];\n        }\n        if (mu[1] in f2m) {\n            f2m[mu[1]].push(mu);\n        }\n        else {\n            f2m[mu[1]] = [mu];\n        }\n        return f2m;\n    }, {});\n}\n
Run Code Online (Sandbox Code Playgroud)\n

您可以像这样调用/测试它:

\n
function findBestSchedule(matchups, targetSep) {\n    console.group(`\\nScheduling ${matchups.length} matchups...`);\n    for (let sep = targetSep; sep >= 0; sep--) {\n        const { schedule, complete } = scheduleMatchups(matchups, sep);\n        if (complete) {\n            console.log(`\xe2\x9c\x85 found schedule with minimum separation of ${sep}.`);\n            console.groupEnd();\n            return { schedule, sep };\n        }\n        else {\n            console.log(`\xe2\x9d\x8c failed using minimum separation of ${sep}. Hit dead end with ${matchups.length - schedule.length} matchups remaining to schedule}.`);\n        }\n    }\n    console.groupEnd();\n    throw new Error(\'this should never happen\');\n}\n
Run Code Online (Sandbox Code Playgroud)\n

产生:

\n
Scheduling 162 matchups...\n  \xe2\x9d\x8c failed using minimum separation of 22. Hit dead end with 141 matchups remaining to schedule}.\n  \xe2\x9d\x8c failed using minimum separation of 21. Hit dead end with 141 matchups remaining to schedule}.\n  \xe2\x9d\x8c failed using minimum separation of 20. Hit dead end with 128 matchups remaining to schedule}.\n  \xe2\x9d\x8c failed using minimum separation of 19. Hit dead end with 127 matchups remaining to schedule}.\n  \xe2\x9d\x8c failed using minimum separation of 18. Hit dead end with 42 matchups remaining to schedule}.\n  \xe2\x9d\x8c failed using minimum separation of 17. Hit dead end with 18 matchups remaining to schedule}.\n  \xe2\x9c\x85 found schedule with minimum separation of 16.\n
Run Code Online (Sandbox Code Playgroud)\n

我没有包含它找到的时间表,因为这篇文章已经太长了。

\n

更进一步,前往蒙特卡洛

\n

给定相同的输入,该算法每次都会找到完全相同的答案。如果你打乱输入,它会得出不同的答案,也许更好,也许更糟。所以我使用了蒙特卡罗方法,对输入进行洗牌并重新运行算法。

\n

以下是 10,000 次迭代的结果分布:

\n
12 | 5\n13 | -- 137\n14 | --------------------------------- 1554\n15 | -------------------------------------------------------------------------------------------------- 4497\n16 | -------------------------------------------------------------------------- 3386\n17 | -------- 417\n18 | 4\n10000 values plotted with an average of 15.2389 \n
Run Code Online (Sandbox Code Playgroud)\n

这说明了两件事:

\n
    \n
  • 更优化的解决方案显然更难找到。困难得多。我怀疑如果我运行它一百万次,它可能会找到一个最小间隔为 19 的时间表。也许是 20,但我对此表示怀疑。
  • \n
  • 直方图在另一端也很快缩小,导致时间表的间隔少于 14 个,少于 1.5% 的时间,并且没有一个低于 12 个。这非常好!
  • \n
\n

如果您接受这样的范围,那么您可以只调用我的算法一次。如果您确实需要\n找到更佳的答案,您可以打乱输入并重新运行尽可能多的次数,\n并保留它找到的最佳答案。

\n

无论如何,我现在必须回到现实生活了!希望这可以帮助!

\n

  • 完美的。这就是我正在寻找的。我是一个初学者,所以这个一步一步的解释对我的最终非常有帮助。非常感谢你做的这些。超级赞赏。 (2认同)