Pair-Tournament design algorithm

|▌冷眼眸甩不掉的悲伤 提交于 2021-01-28 17:16:24

问题


I'm trying to build a Javascript function that takes as an input:

  • array of players e.g. ["player1","player2","player3","player4"] (probably only equal number of players)

and dynamically creates an array for a tournament design based on the following rules:

  • each player partners no more than once with the same player
  • every player has an equal amount of total matches

the outputs would be an array containing arrays of matches with four entries each e.g. [player1, player2, player3, player4] stands for player1 and player2 vs. player3 and player4.

[["player1","player2","player3","player4"], ["player1","player3","player2","player4"], ...]

Currently I use something like the below example to do this hard-coded but unfortunately only for a pre-defined number of players.

const m = [];

const A = players[0];
const B = players[1];
const C = players[2];
const D = players[3];
const E = players[4];
const F = players[5];
const G = players[6];
const H = players[7];
const I = players[8];
const J = players[9];
const K = players[10];
const L = players[11];
const M = players[12];
const N = players[13];
const O = players[14];
const P = players[15];

m.push(A, B, C, P);
m.push(A, C, E, O);
m.push(B, C, D, A);
m.push(B, D, F, P);
m.push(C, D, E, B);
m.push(C, E, G, A);
m.push(D, E, F, C);
m.push(D, F, H, B);
m.push(E, F, G, D);
m.push(E, G, I, C);
m.push(F, G, H, E);
m.push(F, H, J, D);
m.push(G, H, I, F);
m.push(G, I, K, E);
m.push(H, I, J, G);
m.push(H, J, L, F);
m.push(I, J, K, H);
m.push(I, K, M, G);
m.push(J, K, L, I);
m.push(J, L, N, H);
m.push(K, L, M, J);
m.push(K, M, O, I);
m.push(L, M, N, K);
m.push(L, N, P, J);
m.push(M, N, O, L);
m.push(M, O, A, K);
m.push(N, O, P, M);
m.push(N, P, B, L);
m.push(O, P, A, N);
m.push(O, A, C, M);
m.push(P, A, B, O);
m.push(P, B, D, N);

return m;

Would be thankful for every hint!

Cheers


回答1:


You could use the round-robin tournament mechanism to pair players. At each iteration all players, except one, take the place of the next player. If the number of players is odd, there will be one player excluded from matching, but it will be a different one in each iteration. As a game needs 2 pairs, there might be a pair that is not participating either. Again, this will be a different pair in each iteration.

This method will make each player play as many games as the other players, except for when the number of players is 2 modulo 4 (i.e. 6, 10, 14, ...). In that case all players except one will play the same number of games. The exceptional player will play 2 more games.

The number of games found for n players, and the number of games per player, will follow this formula:

#players(n) modulo 4  |   #games             |  #games per player
----------------------+----------------------+--------------------
        0             | n(n-1)/4             |       n-1
        1             | n(n-1)/4             |       n-1
        2             | (n-1)(n-2)/4         |       n-3 (one: n-1)    
        3             | floor((n-1)(n-2)/4)  |       n-3

Example: given 16 players, the algorithm will find 60 games, where each player gets to play in 15 games.

Here is an implementation:

function assignToGames(players) {
    // Round the number of players up to nearest multiple of 2.
    // The potential extra player is a dummy, and the games they play 
    //   will not be included.
    const numPlayers = players.length + players.length % 2, // potential dummy added
        pairsPerRound = numPlayers / 2,
        rotatingPlayers = numPlayers - 1,
        firstRound = players.length % 2, // will make the dummy game being ignored 
        games = [];
    
    for (let round = 0; round < rotatingPlayers; round++) {
        for (let i = firstRound; i < pairsPerRound-1; i+=2) {
            // The following formulas reflect a roundrobin scheme, where
            //   the last player (possibly a dummy) does not move.
            games.push([
                players[i ? (i+round-1) % rotatingPlayers : numPlayers - 1],
                players[(numPlayers-i-2+round) % rotatingPlayers],
                players[(i+round) % rotatingPlayers],
                players[(numPlayers-i-3+round) % rotatingPlayers],
            ]);
        }
    }
    return games;
}

// Optional function to test the correctness of the result, 
//    and count the number of games per player:
function getStatistics(players, games) {
    const usedPairs = new Set(),
        stats = Object.assign(...players.map( player => ({ [player]: 0 }) ));
    
    for (let game of games) {
        // verify uniqueness of pairs
        for (let pairIndex = 0; pairIndex < 4; pairIndex += 2) {
            let pair = JSON.stringify(game.slice(pairIndex,pairIndex+2).sort());
            if (usedPairs.has(pair)) throw "Duplicate pair " + pair;
            usedPairs.add(pair);
        }
    }
    // Count the number of games each player plays:
    for (let i = 0; i < games.length; i++) {
        for (let j = 0; j < 4; j++) {
            stats[games[i][j]]++;
        }
    }
    return stats;
}

// Demo
// Create 16 players. Their values are the letters of the alphabet up to "p". 
const players = Array.from("abcdefghijklmnop");
const games = assignToGames(players);
// Display results
console.log(JSON.stringify(games));
console.log("--- statistics ---");
console.log('#games: ', games.length);
const stats = getStatistics(players, games);
console.log(stats);



回答2:


You could take a combination algorithm with checking the length of four for the wanted result.

function getTournament(array, size) {

    function fork(i, t) {
        if (t.length === size) {
            result.push(t);
            return;
        }
        if (i === array.length) {
            return;
        }
        fork(i + 1, t.concat([array[i]]));
        fork(i + 1, t);
    }

    var result = [];
    fork(0, []);
    return result;
}

console.log(getTournament([1, 2, 3, 4, 5, 6], 4).map(function (a) { return JSON.stringify(a); }));
.as-console-wrapper { max-height: 100% !important; top: 0; }



回答3:


There exists rather simple round-robin tournament algorithm

Set players in two rows. Fix the first player position. Write current pairs (between rows). If number of players is odd, one of them, has a rest in every round.

For next round shift players in circular way. Write pairs again. Repeat

A B
D C 
-----      pairs A-D, B-C
A D
C B
----
A C
B D



回答4:


premetting that I understood the problem correctly, this has no solution in case you will have an odd number of players.

  1. Note: each couple appears just once (means that if you have a and b in the first two positions or in the 3rd and fourth that will be the only case, even if could happen to have a in the first two and b in the 3rd/4th or viceversa)
  2. we have n-1 + n-2 + ... + 2 + 1 couples that is n(n-1) / 2
  3. we want 2 couples for each match => number of matches will be n(n-1) / 4
  4. once we built the couples (that is pretty easy to do) we want to mix them; to do this we create a function that given a couple and an array of couples, returns the first couple from the array that matches the requirements (just different ppl).
  5. Note that we know already the number of the matches so that we can iterate that fixed number of times.
  6. As last step we want to merge the couples into 4-entries arrays.

here's the code,

const players = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L'];
let couples = [];
for (let i = 0; i < players.length; i++) {
    for (let j = i + 1; j < players.length; j++) {
        couples.push([players[i], players[j]]);
    }
}
console.log(couples.length === (players.length * (players.length - 1)) / 2); // true

function findFirstAvailableCouple(couple, arr) {
    for (let i = 0; i < arr.length; i++) {
        if (couple.indexOf(arr[i][0]) === -1 && couple.indexOf(arr[i][1]) === -1) {
            const ret = arr.splice(i, 1)[0];
            return ret;
        }
    }
}
let matches = [];
const n = couples.length / 2;
for (let i = 0; i < n; i++) { const current = couples.splice(0, 1)[0];
    matches.push([current, findFirstAvailableCouple(current, couples)]);
}
matches = matches.map(e => [...e[0], ...e[1]]);
console.log(matches);

you can check the console for the results.

This algorithm can be adapted to any number of entries for couples / players (with increase of cost)




回答5:


Hi you can try with following code.

function getUniquePairArray(_arr) {
    var _tArr = [];
    for(var i = 0; i < _arr.length; i++) {
        for(var j = i+1; j < _arr.length; j++) {
            _tArr.push([_arr[i],_arr[j]]);
        }   
    } 
    return _tArr;
}

above code will create unique pairs from array. e.g. input

[1,2,3,4]

output

[[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]

var findOne = function (haystack, arr) {
    return arr.some(function (v) {
        return haystack.indexOf(v) >= 0;
    });
};

above function checks each element present in target array from given array.

function finalResult(_arr) {
    var _tArr = [];
    for(var i = 0; i < _arr.length; i++) {
        for(var j = i+1; j < _arr.length; j++) {
            if(!findOne(_arr[i],_arr[j]))
            {
                _tArr.push([_arr[i],_arr[j]]);
                break;
            }
        }   
    } 
    return _tArr;
}

this function will give you your desired output.


INPUT == finalFun(getUniquePairArray(["p1","p2","p3","p4","p5"]))

OUTPUT = "[p1,p2,p3,p4][p1,p3,p2,p4][p1,p4,p2,p3][p1,p5,p2,p3][p2,p3,p4,p5][p2,p4,p3,p5][p2,p5,p3,p4]"



来源:https://stackoverflow.com/questions/47052319/pair-tournament-design-algorithm

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!