28

Given a list of opponent seeds (for example seeds 1 to 16), I'm trying to write an algorithm that will result in the top seed playing the lowest seed in that round, the 2nd seed playing the 2nd-lowest seed, etc.

Grouping 1 and 16, 2 and 15, etc. into "matches" is fairly easy, but I also need to make sure that the higher seed will play the lower seed in subsequent rounds.

An example bracket with the correct placement:

1 vs 16
            1 vs 8
8 vs 9
                        1 vs 4
4 vs 13
            4 vs 5
5 vs 12
                                    1 vs 2
2 vs 15
            2 vs 7
7 vs 10
                        2 vs 3
3 vs 14
            3 vs 6
6 vs 11

As you can see, seed 1 and 2 only meet up in the final.

glen-84
  • 1,778
  • 2
  • 18
  • 30
  • 3
    This is just a suggestion that I haven't thought through at all: work backwards from the final. – AakashM Dec 02 '11 at 11:21
  • 1
    This is basically a gray code (if you use zero-indexing). To translate the standard (binary reflected) gray code into your numbering system, simply reverse the bits and add one. – Nabb Dec 02 '11 at 16:32
  • @Nabb – I found [this](http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/229068) which looks interesting, but I'm having trouble understanding the code (it's Ruby which I know nothing about) – glen-84 Dec 02 '11 at 20:14
  • @darkangel: A gray code is code when the hamming distance to the next codeword is 1 and unlike binary code it differ only in 1 bit. Here is an explanation: http://dba.stackexchange.com/questions/7887/best-way-to-design-tournament-database/7889#7889 – Micromega Dec 05 '11 at 12:45
  • The principle is correct. However, you might prefer to end up with matches in this specific order: (1, 16), (9, 8), (5, 12), (13, 4), (3, 14), (11, 6), (7, 10), (15, 2). See my answer here: https://stackoverflow.com/a/45566890/760777 – RWC Aug 09 '17 at 08:43

11 Answers11

17

This JavaScript returns an array where each even index plays the next odd index

function seeding(numPlayers){
  var rounds = Math.log(numPlayers)/Math.log(2)-1;
  var pls = [1,2];
  for(var i=0;i<rounds;i++){
    pls = nextLayer(pls);
  }
  return pls;
  function nextLayer(pls){
    var out=[];
    var length = pls.length*2+1;
    pls.forEach(function(d){
      out.push(d);
      out.push(length-d);
    });
    return out;
  }
}

> seeding(2)
[1, 2]
> seeding(4)
[1, 4, 2, 3]
> seeding(8)
[1, 8, 4, 5, 2, 7, 3, 6]
> seeding(16)
[1, 16, 8, 9, 4, 13, 5, 12, 2, 15, 7, 10, 3, 14, 6, 11]
Phil Holden
  • 191
  • 1
  • 4
  • Seems correct. However, you might prefer to end up with matches in this specific order: (1, 16), (9, 8), (5, 12), (13, 4), (3, 14), (11, 6), (7, 10), (15, 2). See my answer here: https://stackoverflow.com/a/45572051/760777 – RWC Aug 09 '17 at 08:41
13

With your assumptions, players 1 and 2 will play in the final, players 1-4 in the semifinals, players 1-8 in the quarterfinals and so on, so you can build the tournament recursively backwards from the final as AakashM proposed. Think of the tournament as a tree whose root is the final.

In the root node, your players are {1, 2}.

To expand the tree recursively to the next level, take all the nodes on the bottom layer in the tree, one by one, and create two children for them each, and place one of the players of the original node to each one of the child nodes created. Then add the next layer of players and map them to the game so that the worst newly added player plays against the best pre-existing player and so on.

Here first rounds of the algorithm:

 {1,2}  --- create next layer

       {1, _}
      /         --- now fill the empty slots
 {1,2}
      \{2, _}

       {1, 4}   --- the slots filled in reverse order
      /         
 {1,2}
      \{2, 3}   --- create next layer again


             /{1, _}
       {1, 4}
      /      \{4, _}
 {1,2}                  --- again fill
      \      /{2, _}
       {2, 3}
             \{3, _}

             /{1, 8}
       {1, 4}
      /      \{4, 5}    --- ... and so on
 {1,2}
      \      /{2, 7}
       {2, 3}
             \{3, 6}

As you can see, it produces the same tree you posted.

Antti Huima
  • 25,136
  • 3
  • 52
  • 71
  • Very interesting idea, although I would have to think about how to translate it into code. – glen-84 Dec 02 '11 at 14:17
  • 1
    I had this thought as well as another on how to do it without going backwards. I think they ultimately boil down to the same thing though really. Certainly the way to just calculate a position for each player from their seeding is really quite complicated, probably more so for translating into code than this. I'd certainly go with this method. – Chris Dec 02 '11 at 14:25
7

I've come up with the following algorithm. It may not be super-efficient, but I don't think that it really needs to be. It's written in PHP.

<?php
    $players = range(1, 32);
    $count = count($players);
    $numberOfRounds = log($count / 2, 2);

    // Order players.
    for ($i = 0; $i < $numberOfRounds; $i++) {
        $out = array();
        $splice = pow(2, $i); 

        while (count($players) > 0) {

            $out = array_merge($out, array_splice($players, 0, $splice));
            $out = array_merge($out, array_splice($players, -$splice));

        }            

        $players = $out;
    }

    // Print match list.
    for ($i = 0; $i < $count; $i++) {
        printf('%s vs %s<br />%s', $players[$i], $players[++$i], PHP_EOL);
    }
?>
James Ganong
  • 1,160
  • 8
  • 15
glen-84
  • 1,778
  • 2
  • 18
  • 30
  • I have a small question about this. How does this work towards feeding the following rounds? – Paul Williams Mar 01 '12 at 01:03
  • I'm not quite sure what you mean – this just ensures that the highest seed will play the lowest seed in each round (and the 2nd-highest will play the 2nd-lowest, etc.) – glen-84 Mar 03 '12 at 07:56
  • This is a great and simple solution. I made a small edit to make it more efficient. – James Ganong Apr 07 '18 at 23:01
4

I also wrote a solution written in PHP. I saw Patrik Bodin's answer, but thought there must be an easier way.

It does what darkangel asked for: It returns all seeds in the correct positions. The matches are the same as in his example, but in a prettier order, seed 1 and seed number 16 are on the outside of the schema (as you see in tennis tournaments).

If there are no upsets (meaning a higher seeded player always wins from a lower seeded player), you will end up with seed 1 vs seed 2 in the final.

It actually does two things more:

  1. It shows the correct order (which is a requirement for putting byes in the correct positions)

  2. It fills in byes in the correct positions (if required)

A perfect explanation about what a single elimination bracket should look like: http://blog.playdriven.com/2011/articles/the-not-so-simple-single-elimination-advantage-seeding/

Code example for 16 participants:

<?php

define('NUMBER_OF_PARTICIPANTS', 16);

$participants = range(1,NUMBER_OF_PARTICIPANTS);
$bracket = getBracket($participants);
var_dump($bracket);

function getBracket($participants)
{
    $participantsCount = count($participants);  
    $rounds = ceil(log($participantsCount)/log(2));
    $bracketSize = pow(2, $rounds);
    $requiredByes = $bracketSize - $participantsCount;

    echo sprintf('Number of participants: %d<br/>%s', $participantsCount, PHP_EOL);
    echo sprintf('Number of rounds: %d<br/>%s', $rounds, PHP_EOL);
    echo sprintf('Bracket size: %d<br/>%s', $bracketSize, PHP_EOL);
    echo sprintf('Required number of byes: %d<br/>%s', $requiredByes, PHP_EOL);    

    if($participantsCount < 2)
    {
        return array();
    }

    $matches = array(array(1,2));

    for($round=1; $round < $rounds; $round++)
    {
        $roundMatches = array();
        $sum = pow(2, $round + 1) + 1;
        foreach($matches as $match)
        {
            $home = changeIntoBye($match[0], $participantsCount);
            $away = changeIntoBye($sum - $match[0], $participantsCount);
            $roundMatches[] = array($home, $away);
            $home = changeIntoBye($sum - $match[1], $participantsCount);
            $away = changeIntoBye($match[1], $participantsCount);
            $roundMatches[] = array($home, $away);
        }
        $matches = $roundMatches;
    }

    return $matches;

}

function changeIntoBye($seed, $participantsCount)
{
    //return $seed <= $participantsCount ?  $seed : sprintf('%d (= bye)', $seed);  
    return $seed <= $participantsCount ?  $seed : null;
}

?>

The output:

Number of participants: 16
Number of rounds: 4
Bracket size: 16
Required number of byes: 0
C:\projects\draw\draw.php:7:
array (size=8)
  0 => 
    array (size=2)
      0 => int 1
      1 => int 16
  1 => 
    array (size=2)
      0 => int 9
      1 => int 8
  2 => 
    array (size=2)
      0 => int 5
      1 => int 12
  3 => 
    array (size=2)
      0 => int 13
      1 => int 4
  4 => 
    array (size=2)
      0 => int 3
      1 => int 14
  5 => 
    array (size=2)
      0 => int 11
      1 => int 6
  6 => 
    array (size=2)
      0 => int 7
      1 => int 10
  7 => 
    array (size=2)
      0 => int 15
      1 => int 2

If you change 16 into 6 you get:

Number of participants: 6
Number of rounds: 3
Bracket size: 8
Required number of byes: 2
C:\projects\draw\draw.php:7:
array (size=4)
  0 => 
    array (size=2)
      0 => int 1
      1 => null
  1 => 
    array (size=2)
      0 => int 5
      1 => int 4
  2 => 
    array (size=2)
      0 => int 3
      1 => int 6
  3 => 
    array (size=2)
      0 => null
      1 => int 2
RWC
  • 4,697
  • 2
  • 22
  • 29
2
# Here's one in python - it uses nested list comprehension to be succinct:

from math import log, ceil

def seed( n ):
    """ returns list of n in standard tournament seed order

    Note that n need not be a power of 2 - 'byes' are returned as zero
    """

    ol = [1]

    for i in range( ceil( log(n) / log(2) ) ):

        l = 2*len(ol) + 1

        ol = [e if e <= n else 0 for s in [[el, l-el] for el in ol] for e in s]

    return ol
anonymous
  • 29
  • 2
1

For JavaScript code, use one of the two functions below. The former embodies imperative style & is much faster. The latter is recursive & neater, but only applicable to relatively small number of teams (<16384).

// imperative style
function foo(n) {
  const arr = new Array(n)
  arr[0] = 0
  for (let i = n >> 1, m = 1; i >= 1; i >>= 1, m = (m << 1) + 1) {
    for (let j = n - i; j > 0; j -= i) {
      arr[j] = m - arr[j -= i]
    }
  }
  return arr
}

Here you fill in the spots one by one by mirroring already occupied ones. For example, the first-seeded team (that is number 0) goes to the topmost spot. The second one (1) occupies the opposite spot in the other half of the bracket. The third team (2) mirrors 1 in their half of the bracket & so on. Despite the nested loops, the algorithm has a linear time complexity depending on the number of teams.

Here is the recursive method:

// functional style
const foo = n =>
  n === 1 ? [0] : foo(n >> 1).reduce((p, c) => [...p, c, n - c - 1], [])

Basically, you do the same mirroring as in the previous function, but recursively:

  • For n = 1 team, it's just [0].

  • For n = 2 teams, you apply this function to the argument n-1 (that is, 1) & get [0]. Then you double the array by inserting mirrored elements between them at even positions. Thus, [0] becomes [0, 1].

  • For n = 4 teams, you do the same operation, so [0, 1] becomes [0, 3, 1, 2].

If you want to get human-readable output, increase each element of the resulting array by one:

const readableArr = arr.map(i => i + 1)
inker
  • 325
  • 8
  • 15
0

Since this comes up when searching on the subject, and it's hopeless to find another answer that solves the problem AND puts the seeds in a "prettier" order, I will add my version of the PHP code from darkangel. I also added the possibility to give byes to the higher seed players.

This was coded in an OO environment, so the number of participants are in $this->finalists and the number of byes are in $this->byes. I have only tested the code without byes and with two byes.

  public function getBracket() {
      $players = range(1, $this->finalists);
      for ($i = 0; $i < log($this->finalists / 2, 2); $i++) {
        $out = array();
        $reverse = false;
        foreach ($players as $player) {
          $splice = pow(2, $i);
          if ($reverse) {
            $out = array_merge($out, array_splice($players, -$splice));
            $out = array_merge($out, array_splice($players, 0, $splice));
            $reverse = false;
          } else {
            $out = array_merge($out, array_splice($players, 0, $splice));
            $out = array_merge($out, array_splice($players, -$splice));
            $reverse = true;
          }
        }
        $players = $out;
      }
      if ($this->byes) {
        for ($i = 0; $i < $this->byes; $i++ ) {
          for ($j = (($this->finalists / pow(2, $i)) - 1); $j > 0; $j--) {
            $newPlace = ($this->finalists / pow(2, $i)) - 1;
            if ($players[$j] > ($this->finalists / (pow(2 ,($i + 1))))) {
              $player = $players[$j];
              unset($players[$j]);
              array_splice($players, $newPlace, 0, $player);
            }
          }
        }
        for ($i = 0; $i < $this->finalists / (pow(2, $this->byes)); $i++ ) {
          $swap[] = $players[$i];
        }
        for ($i = 0; $i < $this->finalists /(pow(2, $this->byes)); $i++ ) {
          $players[$i] = $swap[count($swap) - 1 - $i];
        }
        return array_reverse($players);
      }
      return $players;
    }
0

I worked on a PHP / Laravel plugin that generates brackets with / without preliminary round robin. Maybe it can be useful to you, I don't know what tech you are using. Here is the github.

https://github.com/xoco70/kendo-tournaments

Hope it helps!

Juliatzin
  • 18,455
  • 40
  • 166
  • 325
0

A C version.

int * pctournamentSeedArray(int PlayerCnt)
{
    int * Array;
    int * PrevArray;
    int i;

    Array = meAlloc(sizeof(int) * PlayerCnt);

    if (PlayerCnt == 2)
    {
        Array[0] = 0;
        Array[1] = 1;
        return Array;
    }

    PrevArray = pctournamentSeedArray(PlayerCnt / 2);
    for (i = 0; i < PlayerCnt;i += 2)
    {
        Array[i] = PrevArray[i / 2];
        Array[i + 1] = (PlayerCnt - 1) - Array[i] ;
    }
    meFree(PrevArray);
    return Array;
}
Leonardo Alves Machado
  • 2,747
  • 10
  • 38
  • 53
0

The answer depends a bit on other variables. This is my approach to the problem. It only addresses the first bit of creating the brackets. The other logic of the bracket getting smaller is up to the reader.

  • You need to know the draw size, with a minimum of 8
  • You need a minimum of half the draw size in players, eg draw of 8 has a minimum of 4 players.
  • You have seeded and optionally unseeded, wildcards or qualifiers in your player population.
  1. Add up all the player groups and see if the player count equals the draw size. If the player group size is less than the draw size, you'll need to add "Byes".

  2. In case of Byes, you'll need to assign the Byes to the (highest) seeded player(s). This results in your first couple of seeded matches (assuming you have seeded players).

    a. It could be that you have less seeded players than Byes in a draw but you do have unseeded, wildcards or qualifiers. These can be granted the remainding Byes. I treat these cases as seeded players. You can treat qualifiers or wild cards as seeded players depending on your wishes. But I find a bracket with qualifiers, wildcards and byes odd.

  3. If you have unseeded players, wildcards or qualifiers, create matches vs the seeded players and append this to your seeded matches.

  4. If you don't have unseeded players, wildcards or qualifiers you'll need to split your seeded player group in half. Assuming you have 8 seeds, you'll have to groups: 1-4, 5-8. You'll need to reverse the order of the second group. You now need to create matches for the remainder of the seeds. This concludes the seeded matches.

  5. To create the brackets you have to split the seeded matches in half to a left and a right side. The left side has the #1 seed in it, the right side has the #2 seed in it, the #3 seed goes into left and #4 seed into right, etc. The right side also holds one seed or more with an unequal amount of seeds. Unless you only have one seed, this one stays on the left side.

  6. It would be best if you shuffled the brackets (both left and right) a bit to ensure that the no 1 seed doesn't play the no 3 seed one round later in a draw size of 16 and up.

    a. You create two arrays (foo and bar) for a bracket.

    b. While you loop over each bracket, the first match and last match go into foo and the second and second to last match into bar.

    c. If you prefer your seeds to play against the worst seeded players, you must split the bracket in half, reverse both sides, and merge them again. You can omit this action if you allow a more or less equal difference between seeds.

    d. Now you need to reverse the bar and merge bar into foo, this has now become your bracket.

  7. You create matches with the remainder of the unseeded players, the wildcards and the qualifiers as unseeded matches.

  8. Complete the final brackets you need to determine which side has the fewest entries to start adding the first unseeded match to. This requires the following logic (assuming 0-based arrays).

    a. The index to place something in the matches is 1. Depending on the side you are working on, you'll want to add the first match as the opponent of the #1 or #2 seed. If the array is empty, just push it to the array.

    b. Every second interation you flip the index by multiplying it with -1, this means in most languages that you'll add it before the last element of the array.

    c. To make sure you have equal spacing between all the seeded players you'll need to do the following things at every second iteration:

    • Update the index by 2 when the index is more than 0.
    • Reset the index to 1 if the index is greater than half the size of one of the brackets.
waterkip
  • 1
  • 2
0
  • At each round sort teams by seeding criteria
  • (If there are n teams in a round)team at ith position plays with team n-i+1
Caner
  • 57,267
  • 35
  • 174
  • 180
  • I need to place the teams in the first round so that the top seeds advancing to the next round will automatically be matched up top-seed vs bottom-seed, etc. You can assume that the top seed always wins the match, for the purposes of the algorithm. – glen-84 Dec 02 '11 at 11:12