Python 基于ELO的团队匹配算法

Python 基于ELO的团队匹配算法,python,c++,algorithm,math,combinations,Python,C++,Algorithm,Math,Combinations,我正在寻找一种非常简单的方法,用未知(但已知)数量的球员组建2支球队。因此,这实际上不是一个标准的配对,因为它只为特定的比赛在整个注册球员池中创建一场比赛。我几乎只有一个变量,它是每个球员的ELO分数,这意味着它是唯一可以用来计算的选项 我想到的只是简单地检查每一个可能的球员组合(每队6名),球队平均ELO之间的最小差异是最终创建的名册。我已经测试过这个选项,它为18名注册玩家提供了超过1700万次的计算(玩家数量通常不应该超过24名),所以这是可行的,但绝对是最不理想的方法 所以我决定在这里问

我正在寻找一种非常简单的方法,用未知(但已知)数量的球员组建2支球队。因此,这实际上不是一个标准的配对,因为它只为特定的比赛在整个注册球员池中创建一场比赛。我几乎只有一个变量,它是每个球员的ELO分数,这意味着它是唯一可以用来计算的选项

我想到的只是简单地检查每一个可能的球员组合(每队6名),球队平均ELO之间的最小差异是最终创建的名册。我已经测试过这个选项,它为18名注册玩家提供了超过1700万次的计算(玩家数量通常不应该超过24名),所以这是可行的,但绝对是最不理想的方法

所以我决定在这里问一个问题,也许你可以在某种程度上帮助我。我能用什么样的简单算法,或者在所有可能的组合的直接比较中优化某些东西的方法

如果你想提供任何代码示例,我几乎可以阅读任何代码语言,所以这并不重要


UPD。基本上,输入是包含玩家“id”和“elo”的玩家对象列表[],输出应该是包含玩家对象的2个团队列表team1[]和team2[]。两个团队的平均ELO应该尽可能接近。

考虑到你的方法是近似的,付出太多的努力来产生一个完美的答案是失败的。取而代之的是选择一个合理的差异,并从那里开始

我建议你按ELO对球员名单进行排序,然后配对。如果这些人被包括在内,他们将成为对方球队的一员。如果人数是奇数,请将距离其他人最远的人排除在外。按差异对配对进行排序,并以相同的方式将它们配对。这就给了你一个相当均衡的4人小组,在对中间2人的比赛中,两队将是最好的和最差的。这些4人组通常应相对接近平均值。得分为较好组减去较差组。(根据实际得分,任何一半的成绩都会更好。)

现在搜索3组,每组4人,使A尽可能接近B和C的总和。A中较好的组将与B和C中较差的组相匹配

对于24人,这将是一个几乎即时的计算,并将给出合理的结果

我开始使用的重复差分方法是一种众所周知的子集和问题的启发式方法


考虑到这种启发式的速度有多快,我认为有必要扩大对优秀团队的搜索范围,如下所示

  • 对你的球员进行分类。将每个玩家与上面和下面的人配对。对于
    n
    玩家,这将是
    n-1
    对。给每一对选手一个ELO差异的分数,或者更好的选手击败更差选手的可能性。(我会选择哪一个取决于两人的比赛方式。)
  • 把你的一对整理一下。将每一对与上面和下面不相交的最近一对配对。对于
    n-1
    对,这通常会导致
    n-2
    组4
  • 创建一个4组的排序列表。称之为
    list4
    。请注意,此列表具有大小
    n+O(1)
  • 构建一个8人的所有组的列表,该列表可以由不相交的2组4人组成。分类。称之为
    list8
    。这个列表有多大的公式很复杂,但它是
    n^2/2+O(n)
    并且需要时间
    O(n^2 log(n))
    来排序
  • 对于
    list4
    的每个元素,在
    list8
    中查找其上/下最近的元素,这些元素没有共同的参与者。对于
    O(n)
    元素,这是
    O(log(n))
    预期的工作
  • 结果是,您摆脱了偶数/奇数逻辑。是的,您添加了一些额外的工作,但是最大的工作是排序
    list8
    。这是足够快的,即使你有100人投它,你仍然会产生非常快的答案


    结果将是两支势均力敌的队伍,这样,当他们以实力配对时,较弱的队伍至少有合理的机会发布令人信服的失望消息。

    我们可以将此作为一个数学优化问题来写:

    假设我们有球员
    i=1..24
    ,还有球队
    j=1,2
    。引入决策变量:

     x(i,j) = 1 if player i is assigned to team j
              0 otherwise
    
    然后我们可以写:

     Min |avg(2)-avg(1)|
     subject to
         sum(j, x(i,j)) <= 1    for all i  (a player can be assigned only once)
         sum(i, x(i,j)) = 6     for all j  (a team needs 6 players)
         avg(j) = sum(i, rating(i)*x(i,j)) / 6   (calculate the average)
         avg(j) >= 0         
    

    令人惊讶的是,对于这个数据集,我们可以找到一个完美的匹配。

    这里有一个Minizing的解决方案:

    % Selecting Chess Players
    
    include "globals.mzn";
    
    int: noOfTeams = 2;
    int: noOfPlayers = 24;
    int: playersPerTeam = 6;
    
    set of int: Players = 1..noOfPlayers;
    set of int: Teams = 1..noOfTeams;
    
    array[Players] of int: elo = 
      [1275, 1531, 1585,  668, 1107, 1011,
       1242, 1774, 1096, 1400, 1036, 1538,
       1135, 1206, 2153, 1112,  880,  850,
       1528, 1875,  939, 1684, 1807, 1110];
    
    array[Players] of var 0..noOfTeams: team;
    array[Teams] of var int: eloSums;
    
    %  same number of players per team
    constraint forall(t in Teams) (
         playersPerTeam == sum([team[p] == t | p in Players])
      );
    
    %  sum up the ELO numbers per team
    constraint forall(t in Teams) (
         eloSums[t] == sum([if team[p] == t then elo[p] else 0 endif | p in Players])
      );
    
    %  enforce sorted sums to break symmetries
    %  and avoid minimum/maximum predicates          
    constraint forall(t1 in Teams, t2 in Teams where t1 < t2) (
        eloSums[t1] <= eloSums[t2]
    );
    
    solve minimize eloSums[noOfTeams] - eloSums[1];
    
    output ["\n             "] ++ ["team" ++ show(t) ++ "  " | t in Teams] ++
           ["\n"] ++
           [ if fix(team[p]) != 0 then
                 if t == 1 then 
                    "\nplayer" ++ show_int(-2,p) ++ " " 
                 else 
                    "" 
                 endif ++
                 if fix(team[p]) == t then
                    show_int(8, elo[p])
                 else
                    "       "
                 endif
             else 
               "" 
             endif 
             | p in Players, t in Teams ] ++
             ["\nsum     "] ++
             [show_int(8, eloSums[t]) | t in Teams ] ++
             ["\navg        "] ++
             [show_float(8,2,eloSums[t]/playersPerTeam) | t in Teams ];
    
    %选择棋手
    包括“globals.mzn”;
    int:noOfTeams=2;
    int:noOfPlayers=24;
    int:playersPerTeam=6;
    整数集:Players=1..noOfPlayers;
    整数集合:团队=1..nooftams;
    整数的数组[玩家]:elo=
    [1275, 1531, 1585,  668, 1107, 1011,
    1242, 1774, 1096, 1400, 1036, 1538,
    1135, 1206, 2153, 1112,  880,  850,
    1528, 1875,  939, 1684, 1807, 1110];
    变量0..nooftams的数组[玩家]:团队;
    var int的数组[Teams]:eloSums;
    %每个队的球员人数相同
    对所有人的约束(团队中的t)(
    playersPerTeam==sum([团队[p]==t|p球员])
    );
    %总结每个团队的ELO数量
    对所有人的约束(团队中的t)(
    eloSums[t]==sum([如果团队[p]==t,则elo[p]否则0 endif | p在玩家中])
    );
    %强制排序和以打破对称性
    %并避免使用最小/最大谓词
    对所有人的约束(t1在团队中,t2在t1和[t1]我可能会先将球员组织在一个列表中,按照ELO对他们进行排名,然后再从那里开始,而不是随机分配他们,或者检查每个组合,你可以选择谁加入哪支球队……这可能不会在所有情况下都产生完美的结果,但会让你走到一半,然后你可以对此进行平衡,而不是切一千七百万的组合
    
    ----     43 PARAMETER r  ELO rating
    
    player1  1275,    player2  1531,    player3  1585,    player4   668,    player5  1107,    player6  1011
    player7  1242,    player8  1774,    player9  1096,    player10 1400,    player11 1036,    player12 1538
    player13 1135,    player14 1206,    player15 2153,    player16 1112,    player17  880,    player18  850
    player19 1528,    player20 1875,    player21  939,    player22 1684,    player23 1807,    player24 1110
    
    
    ----     43 VARIABLE x.L  assignment
    
                   team1       team2
    
    player1        1.000
    player2                    1.000
    player4        1.000
    player5                    1.000
    player6                    1.000
    player7        1.000
    player8        1.000
    player9        1.000
    player10                   1.000
    player11                   1.000
    player17       1.000
    player18                   1.000
    
    
    ----     43 VARIABLE avg.L  average rating of team
    
    team1 1155.833,    team2 1155.833
    
    
    ----     43 PARAMETER report  solution report
    
                   team1       team2
    
    player1     1275.000
    player2                 1531.000
    player4      668.000
    player5                 1107.000
    player6                 1011.000
    player7     1242.000
    player8     1774.000
    player9     1096.000
    player10                1400.000
    player11                1036.000
    player17     880.000
    player18                 850.000
    sum         6935.000    6935.000
    avg         1155.833    1155.833
    
    % Selecting Chess Players
    
    include "globals.mzn";
    
    int: noOfTeams = 2;
    int: noOfPlayers = 24;
    int: playersPerTeam = 6;
    
    set of int: Players = 1..noOfPlayers;
    set of int: Teams = 1..noOfTeams;
    
    array[Players] of int: elo = 
      [1275, 1531, 1585,  668, 1107, 1011,
       1242, 1774, 1096, 1400, 1036, 1538,
       1135, 1206, 2153, 1112,  880,  850,
       1528, 1875,  939, 1684, 1807, 1110];
    
    array[Players] of var 0..noOfTeams: team;
    array[Teams] of var int: eloSums;
    
    %  same number of players per team
    constraint forall(t in Teams) (
         playersPerTeam == sum([team[p] == t | p in Players])
      );
    
    %  sum up the ELO numbers per team
    constraint forall(t in Teams) (
         eloSums[t] == sum([if team[p] == t then elo[p] else 0 endif | p in Players])
      );
    
    %  enforce sorted sums to break symmetries
    %  and avoid minimum/maximum predicates          
    constraint forall(t1 in Teams, t2 in Teams where t1 < t2) (
        eloSums[t1] <= eloSums[t2]
    );
    
    solve minimize eloSums[noOfTeams] - eloSums[1];
    
    output ["\n             "] ++ ["team" ++ show(t) ++ "  " | t in Teams] ++
           ["\n"] ++
           [ if fix(team[p]) != 0 then
                 if t == 1 then 
                    "\nplayer" ++ show_int(-2,p) ++ " " 
                 else 
                    "" 
                 endif ++
                 if fix(team[p]) == t then
                    show_int(8, elo[p])
                 else
                    "       "
                 endif
             else 
               "" 
             endif 
             | p in Players, t in Teams ] ++
             ["\nsum     "] ++
             [show_int(8, eloSums[t]) | t in Teams ] ++
             ["\navg        "] ++
             [show_float(8,2,eloSums[t]/playersPerTeam) | t in Teams ];