2015-04-23 3 views
2

В C# я пытаюсь написать алгоритм для балансировки двух команд с учетом целочисленных оценок игроков для каждого из игроков.Перестановки комбинаций из двух групп

Набор данных выглядит следующим образом:

Player 1: 1330 
Player 2: 1213 
Player 3: 1391 
Player 4: 1192 
Player 5: 1261 
Player 6: 1273 
Player 7: 1178 
Player 8: 1380 
Player 8: 1200 
Player 10: 1252 

Я хотел бы построить два набора из пяти игроков, где общая разница Популярность обеих команд как можно меньше, для справедливого матча ,

Теперь для этого я хотел бы сгенерировать все перестановки команд (каждая перестановка - две команды из 5 игроков). Но все примеры перестановки C# предназначены для объединения таких вещей, как силовые наборы, а не команды.

Каков наиболее эффективный способ сделать это?

ответ

2

Вы хотите combinations , а не перестановок. Используя стандартную формулу, мы знаем, что есть 252 возможных комбинации из 10 игроков, взятых по 5 за раз. Есть очень простой способ генерировать комбинации, которые vib упоминается в его ответе, и я расширяюсь здесь.

Всего 10 игроков. Если вы рассматриваете игроков как 10-битное число, каждый игрок соответствует бит в этом номере. Любое 10-битное число, имеющее ровно 5 бит, является действительной командой. Таким образом, 0101010101 является действительной командой, но 0011000100 не является действительной командой.

Кроме того, любая действительная команда имеет ровно одну противоположную команду. То есть, учитывая 10 игроков и команду из 5 человек, тогда есть только 5 других людей, чтобы выбрать. Поэтому команда 0101010101 в паре с командой 1010101010.

2^10 - 1024. Таким образом, мы должны только проверить 1024 возможных комбинаций. На самом деле нам нужно проверить только 512, потому что мы знаем, что любая команда с номером выше 511 будет иметь на нем наивысший пронумерованный игрок (т. Е. Последний бит установлен), и любое число меньше 512 не будет иметь этого игрока на нем.

Таким образом, идея есть, для каждого номера меньше, чем 512:

  1. если не пяти бит в номере, то перейдите к следующим
  2. инвертировать число.Это даст вам противостоящую команду
  3. Вычислить оценки для команды и команды соперника

Простой C# код, чтобы сделать это:

private readonly int[] _playerRatings = new[] {1330, 1213, 1391, 1192, 1261, 1273, 1178, 1380, 1200, 1252}; 

private int CalculateTeamScore(int team) 
{ 
    var score = 0; 
    for (var i = 0; i < 10; ++i) 
    { 
     if ((team & 1) == 1) 
     { 
      score += _playerRatings[i]; 
     } 
     team >>= 1; 
    } 
    return score; 
} 

private bool IsValidTeam(int team) 
{ 
    // determine how many bits are set, and return true if the result is 5 
    // This is the slow way, but it works. 
    var count = 0; 
    for (var i = 0; i < 10; ++i) 
    { 
     if ((team & 1) == 1) 
     { 
      ++count; 
     } 
     team >>= 1; 
    } 
    return (count == 5); 
} 

public void Test() 
{ 
    // There are 10 players. You want 5-player teams. 

    // Assign each player a bit position in a 10-bit number. 
    // 2^10 is 1024. 
    // Start counting at 0, and whenever you see a number that has 5 bits set, 
    // you have a valid 5-player team. 
    // If you invert the bits, you get the opposing team. 

    // You only have to count up to 511 (2^9 - 1), because any team after that 
    // will already have been found as the opposing team. 

    for (var team = 0; team < 512; ++team) 
    { 
     if (IsValidTeam(team)) 
     { 
      var opposingTeam = ~team; 
      var teamScore = CalculateTeamScore(team); 
      var opposingTeamScore = CalculateTeamScore(opposingTeam); 
      var scoreDiff = Math.Abs(teamScore - opposingTeamScore); 
      Console.WriteLine("{0}:{1} - {2}:{3} - Diff = {4}.", 
       team, teamScore, opposingTeam, opposingTeamScore, scoreDiff); 
     } 
    } 
} 

Вы должны будете предоставить код, который извлекает номера игроков из номера команды. Это просто вопрос вывода номера бит из заданных битов. Вы можете изменить код вычисления баллов для этого.

Обратите внимание, что код, который я использовал для определения количества бит, не является оптимальным. Но это работает. Если вы хотите более быстрый способ, ознакомьтесь с BitHacks page, который имеет много разных способов.

+0

Wow, awesome. Спасибо. –

+0

Джим, я реализовал его здесь: https://github.com/paralin/BalanceAlgorithm/ - у вас были некоторые критические ошибки в вашем коде (используя '-' вместо' ~ 'и целых чисел со знаком), которые вы можете исправить в ваш ответ. Еще раз спасибо! –

+0

@ChristianStewart: Мой код имеет '~', где он принадлежит. Я понимаю, что вы подразумеваете под целыми знаками, хотя алгоритм должен работать. Противоположные номера команд будут отрицательными, что является довольно выигрышным.Это легко зафиксировать, написав 'var opposingTeam = (~ team) & 0x3FF;' В любом случае, я рад, что вы его работали. –

1

Вам не нужно генерировать все перестановки. Посмотрите на целые числа i между 0 и 2^10-1 и посмотрите, сколько битов целого числа установлено на единицу. Всякий раз, когда это 5, это дает вам действительный раздел из ваших 10 команд в две группы из пяти человек.

+0

Является ли «укусы» предполагаемыми «битами» или «байтами»? –

+0

бит, извините. Отредактировано – vib

+2

было бы полезно дать рассуждения по этому методу – Shekhar

1

вы могли бы использовать Linq, чтобы решить вашу проблему

в данном примере это две команды из двух людей

используя мое понимание Jim Mischel answer

.net fiddler run

using System; 
using System.Collections.Generic; 
using System.Linq; 

namespace ConsoleApplication1 
{ 
    class Player 
    { 
     public int PlayerId { get; set; } 
     public int PlayerBit { get; set; } 
     public int PlayerScore { get; set; } 

     public override string ToString() 
     { 
      return string.Format("Player: {0} Score: {1}\n",PlayerId,PlayerScore); 
     } 
    } 

    public class Program 
    { 
     public static void Main(string[] args) 
     { 
      const int maxDiff = 15; 

      var players = new List<Player> { new Player() {PlayerId = 1, PlayerBit = 1<<0, PlayerScore = 1330}, 
              new Player() {PlayerId = 2, PlayerBit = 1<<1, PlayerScore = 1213}, 
              new Player() {PlayerId = 3, PlayerBit = 1<<2, PlayerScore = 1391}, 
              new Player() {PlayerId = 4, PlayerBit = 1<<3, PlayerScore = 1192}, 
              new Player() {PlayerId = 5, PlayerBit = 1<<4, PlayerScore = 1261}, 
              new Player() {PlayerId = 6, PlayerBit = 1<<5, PlayerScore = 1273}, 
              new Player() {PlayerId = 7, PlayerBit = 1<<6, PlayerScore = 1178}, 
              new Player() {PlayerId = 8, PlayerBit = 1<<7, PlayerScore = 1380}, 
              new Player() {PlayerId = 9, PlayerBit = 1<<8, PlayerScore = 1200}, 
              new Player() {PlayerId = 10, PlayerBit = 1<<9, PlayerScore = 1252}}; 

      var maxTeam = players.Max(x => x.PlayerBit); 
      var maxBit = maxTeam * 2 - 1; 

      var team = from t1 in Enumerable.Range(0, maxTeam) where getBitCount(t1) == 5 select t1; 

      var match = team.Select(x => new { t1 = x, t2 = maxBit - x }); 

      foreach (var m in match) 
      { 
       var t1 = players.Where(x => (x.PlayerBit & m.t1) == x.PlayerBit); 
       var t2 = players.Where(x => (x.PlayerBit & m.t2) == x.PlayerBit); 
       var t1Score = t1.Sum(x => x.PlayerScore); 
       var t2Score = t2.Sum(x => x.PlayerScore); 

       if (Math.Abs(t1Score - t2Score) < maxDiff) 
       { 
        Console.WriteLine("Team 1 total score {0} Team 2 total score {1}", t1Score, t2Score); 
        Console.WriteLine("{0} versu \n{1}\n\n", string.Join("", t1.Select(x => x.ToString()).ToArray()), string.Join("", t2.Select(x => x.ToString()).ToArray())); 
       } 
      } 

      Console.Read(); 
     } 

     private static int getBitCount(int bits) 
     { 
      bits = bits - ((bits >> 1) & 0x55555555); 
      bits = (bits & 0x33333333) + ((bits >> 2) & 0x33333333); 
      return ((bits + (bits >> 4) & 0xf0f0f0f) * 0x1010101) >> 24; 
     } 
    } 
} 
+0

Нет, это совсем не так. Как вы создаете команды с 5 игроками? И вы инициализируете 'maxDiff' до 1, что может помешать вам получить какие-либо результаты вообще. Мне нужно будет опубликовать мой код. –

+0

@JimMischel, при diff = 1, я получаю 71 «матч» с командами из 2 игроков, что само по себе. – Fredou

+0

Но если 'maxDiff == 1', как' Math.Abs ​​(t1.totalScore - t2.totalScore) <= maxDiff' когда-либо оценивается как true? 71, конечно, не меньше 1. Вы уверены, что введенный вами код на самом деле является кодом, который вы используете? –

1

Это в основном оптимизированная версия Partition Problem, которая является NP-трудной

Однако при п = 10, который является довольно маленьким, вы все еще можете найти все перестановки и найти ответ, для увеличения п, вы можете использовать быстрый и легко реализуемая жадная аппроксимация, которая также отображается на вики-странице. Ниже я показываю только пример кода с грубой силой n = 10, чтобы найти ответ. Несмотря на то, что написано в C++, ничего особенного внутри и всех операторов/массива являются одинаковыми в C#, вы должны сделать работу перевод самостоятельно, сложность O (2^10 * 10)

#include<bits/stdc++.h> 
 
using namespace std; 
 

 
int a[10] = {1330,1213,1391,1192,1261,1273,1178,1380,1200,1252}; 
 
vector<int> team1, team2; 
 
int ans = 1<<28, T1, T2; 
 

 
int bits(int x){ 
 
\t int cnt = 0; \t 
 
\t while(x){ cnt += x&1; x>>=1;} 
 
\t return cnt; 
 
} 
 

 
int main(){ 
 
\t for(int i=0; i< 1<<10; i++){ 
 
\t \t if(bits(i) == 5){ 
 
\t \t \t int t1 = 0, t2 = 0; 
 
\t \t \t for(int x = i,y=(1<<10)-1-i, j=0; x; x>>=1,y>>=1, j++) { 
 
\t \t \t \t t1 += (x&1)*a[j]; 
 
\t \t \t \t t2 += (y&1)*a[j]; 
 
\t \t \t } 
 
\t \t \t if(ans > abs(t1-t2)){ ans = abs(t1-t2); T1 = i; T2 = (1<<10)-1-i;} 
 
\t \t } 
 
\t } 
 
\t for(int i=1; T1 || T2; T1>>=1, T2>>=1, i++) { 
 
\t \t if(T1&1) team1.push_back(i); 
 
\t \t if(T2&1) team2.push_back(i); 
 
\t } 
 
\t printf("Team 1: "); 
 
\t for(int i=0; i<5;i++) printf("%d ", team1[i]); 
 
\t puts(""); 
 
\t printf("Team 2: "); 
 
\t for(int i=0; i<5;i++) printf("%d ", team2[i]); 
 
\t puts(""); 
 
\t printf("Difference: %d\n", ans); 
 
\t return 0; 
 
}