I\'m making a HTML/JS powered single/double elimination bracket web app. I am struggling to figure out how to assign the first round matches from a list of seeded teams/players.
This is probably not as efficient as @alex's answer using a custom sort
function, but certainly easier to write and understand:
// This algorithm assumes that seeds.length is an even number
var seeds = [1, 2, 3, 4, 5, 6, 7, 8],
firstRound = [];
while (seeds.length)
{
firstRound.push(seeds.shift());
firstRound.push(seeds.pop());
}
// seeds is now empty
// firstRound is now [1, 8, 2, 7, 3, 6, 4, 5]
Demo 1
Actually, I just thought of a faster algorithm (in-place "sorting", takes O(n)
time):
// Also assumes that seeds.length is an even number
var seeds = [1, 2, 3, 4, 5, 6, 7, 8],
numSeeds = seeds.length,
stop = numSeeds >> 1,
temp;
for (var i=1; i<stop; i=i+2)
{
temp = seeds[i];
seeds[i] = seeds[numSeeds-i];
seeds[numSeeds-i] = temp;
}
// seeds is now [1, 8, 3, 6, 5, 4, 7, 2]
Demo 2
Note that neither of these algorithms generates exactly the same order of pairs as in the OP, but they both generate the same set of pairs:
(1,8)
(2,7)
(3,6)
(4,5)
I wrote a solution in PHP (see https://stackoverflow.com/a/45566890/760777). Here is the javascript version.
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 8 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:
It shows the correct order (which is a requirement for putting byes in the correct positions)
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 8 participants:
var NUMBER_OF_PARTICIPANTS = 8; // Set the number of participants
if (!String.prototype.format) {
String.prototype.format = function() {
var args = arguments;
return this.replace(/{(\d+)}/g, function(match, number) {
return typeof args[number] != 'undefined' ? args[number] : match;
});
};
}
var participants = Array.from({length: NUMBER_OF_PARTICIPANTS}, (v, k) => k + 1) ;
var bracket = getBracket(participants);
console.log(bracket);
function getBracket(participants)
{
var participantsCount = participants.length;
var rounds = Math.ceil(Math.log(participantsCount)/Math.log(2));
var bracketSize = Math.pow(2, rounds);
var requiredByes = bracketSize - participantsCount;
console.log("Number of participants: {0}".format(participantsCount));
console.log("Number of rounds: {0}".format(rounds));
console.log("Bracket size: {0}".format(bracketSize));
console.log("Required number of byes: {0}".format(requiredByes));
if(participantsCount < 2) {
return [];
}
var matches = [[1,2]];
for(var round = 1; round < rounds; round++) {
var roundMatches = [];
var sum = Math.pow(2, round + 1) + 1;
for(var i = 0; i < matches.length; i++) {
var home = changeIntoBye(matches[i][0], participantsCount);
var away = changeIntoBye(sum - matches[i][0], participantsCount);
roundMatches.push([home, away]);
home = changeIntoBye(sum - matches[i][1], participantsCount);
away = changeIntoBye(matches[i][1], participantsCount);
roundMatches.push([home, away]);
}
matches = roundMatches;
}
return matches;
}
function changeIntoBye(seed, participantsCount)
{
//return seed <= participantsCount ? seed : '{0} (= bye)'.format(seed);
return seed <= participantsCount ? seed : null;
}
Change NUMBER_OF_PARTICIPANTS from 8 to 6 to get two byes.
Good luck. RWC
I've come up with a solution, but it's outside the scope of just "sorting arrays".
The (javascript) code is at http://jsbin.com/ukomo5/2/edit.
In basic terms, the algorithm assumes that no upsets will occur in the bracket, therefore seeds 1 and 2 should meet in the final round. It iterates through each seed in each round (starting from the pre-calculated grand final, working backwards), calculating the unknown seed in the match in the previous round that the current seed (in the iteration) had won. This can be done because given a seed and round number, you can work out what the other seed should be:
other seed = number of seeds in round + 1 - the known seed
To illustrate, in the semifinals:
Semifinal 1 (where known seed is 1): other seed = 4 + 1 - 1 = 4
Semifinal 2 (where known seed is 2): other seed = 4 + 1 - 2 = 3
I just noticed this pattern when looking at a "no upsets" bracket I had drawn.
In the final iteration (ie round 1) all seeds and their position are known, ready to be assigned to matches. The correct sorted array is below:
1,16,8,9,4,13,5,12,2,15,7,10,3,14,6,11
Thanks again to Matt Ball who came up with a correct solution for small brackets (It's difficult to state the problem and desired solution without detailed context, which I didn't do completely in my initial question).
If anyone has another solution or a more elegant version of my solution let us know!
The ideas of matching players from the top and bottom is correct but not quite complete. Doing it once works great for the first round:
while (seeds.length)
{
firstRound.push(seeds.shift());
firstRound.push(seeds.pop());
}
1, 2, 3, 4, 5, 6, 7, 8 => 1, 8, 2, 7, 3, 6, 4, 5
...but in the second round, seed 1 meets seed 2 and 3 meets 4. We need to do the first/last shuffle for each round. First time through, we move each element individually. Second time through, we move each PAIR of elements. Third time through we move groups of four, etc, until our group size is seeds.length/2
. Like so:
// this is ruby, aka javascript psuedo-code :)
bracket_list = seeds.clone
slice = 1
while slice < bracket_list.length/2
temp = bracket_list
bracket_list = []
while temp.length > 0
bracket_list.concat temp.slice!(0, slice) # n from the beginning
bracket_list.concat temp.slice!(-slice, slice) # n from the end
end
slice *= 2
end
return bracket_list
Here's what the array will look like as you go through the iterations (parenthesis indicate the increasing group size):
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
(1, 16), (2, 15), (3, 14), (4, 13), (5, 12), (6, 11), (7, 10), (8, 9)
(1, 16, 8, 9), (2, 15, 7, 10), (3, 14, 6, 11), (4, 13, 5, 12)
(1, 16, 8, 9, 4, 13, 5, 12), (2, 15, 7, 10, 3, 14, 6, 11)
So now, after the bottom 8 players are eliminated, we're left with 1, 8, 4, 5, 2, 7, 3, 6
. After the bottom 4 are eliminated from there we have 1, 4, 2, 3
, and in the final round just 1, 2
.
It's hard to explain this without being able to draw a bracket... Let me know if I can clarify something for you.
Here's the algorithm I've developed. Step 1 is to draw a table with as many rows as there are teams (rounded up to a power of 2) and as many columns as needed to represent the number of teams in binary. Say, there are 8 teams. The table will initially look like this (the dots represent horizontal cell borders):
. . . | | | | . . . | | | | . . . | | | | . . . | | | | . . . | | | | . . . | | | | . . . | | | | . . . | | | | . . .
Columns are numbered from the left in ascending order. For each column put an asterisk at every 2^(column number) row. That is, every 2nd row in column 1, every 4th row in column 2 etc.
. . . | | | | . . . | | | | * . . | | | | . . . | | | | * * . | | | | . . . | | | | * . . | | | | . . . | | | |
Start with a 0 in each column in the 1st row. Thereafter, for successive rows in each column toggle from 0-1 and 1-0 unless there is an asterisk in that row. This is the result:
. . . |0|0|0| . . . |1|1|1| * . . |1|0|0| . . . |0|1|1| * * . |0|1|0| . . . |1|0|1| * . . |1|1|0| . . . |0|0|1|
The last step is to evaluate each row treating the string of 0's and 1's as a binary number. This will result in values from 0-7. Adding 1 to each results in values from 1-8. These correspond to the seedings.
. . . |0|0|0| + 1 = 1 . . . |1|1|1| + 1 = 8 * . . |1|0|0| + 1 = 5 . . . |0|1|1| + 1 = 4 * * . |0|1|0| + 1 = 3 . . . |1|0|1| + 1 = 6 * . . |1|1|0| + 1 = 7 . . . |0|0|1| + 1 = 2
Each pair of seeds are the matches to be played in order. ie. 1-8, 5-4, 3-6 and 7-2. This is extendable to any number of seeds. When byes are to be inserted due to the number of entries being less than a power of 2, they take the highest seed values. eg. if there were only 28 entries then byes take the positions assigned to 29, 30, 31 and 32.
I was really intrigued and impressed with Cliff's algorithm here. I think it is very clever. Here is a simple implementation I wrote in ruby. The BYEs are returned as -1.
def seed(n)
rounded_n = next_power_of_two(n)
nbr_bits_required=rounded_n.to_s(2).length-1
binary_seeds = Array.new(rounded_n) {Array.new(nbr_bits_required)}
binary_seeds[0]=Array.new(nbr_bits_required){0}
nbr_bits_required.times do |col|
1.upto(rounded_n-1) do |row|
if row % (2**(col+1)) == 0
#asterisk in the previous row, don't inverse the bit
binary_seeds[row][col] = binary_seeds[row-1][col]
else
#no asterisk in the previous row, inverse the bit
binary_seeds[row][col] = binary_seeds[row-1][col] == 0 ? 1 : 0
end
end
end
#output the result in decimal format
binary_seeds.collect {|bs| s=(bs.join("")).to_i(2)+1; s>n ? -1 : s}
end
def next_power_of_two(n)
k = 1
k*=2 while k<n
k
end
Test it out:
seed(8)
=> [1, 8, 5, 4, 3, 6, 7, 2]
seed(6)
=> [1, -1, 5, 4, 3, 6, -1, 2]