4

I've been doing a lot of research on this topic, and while I have found many questions similar to mine, I haven't quite found the answer I am looking for. I am creating a Seeded Single Elimination Tournament bracket. The rules for this type of bracket state that the two best teams will play in the finals. So for example if we had 8 teams with Team 1 being the best and Team 8 the worst.

If we had 8 teams it will result in:

Round 1 =========> Round 2 =========> Round 3
Team 1 vs Team 8
Team 4 vs Team 5 ==> Team 1 vs Team 4 ==> Team 1 vs Team 2
Team 3 vs Team 6 ==> Team 3 vs Team 2
Team 2 vs Team 7

Please note the order of teams in round 1 as my question is going to evolve around having the correct order of teams. You will see that team 1 is at the top and team 2 is at the bottom.

Now I have figured out a simple function which will pair teams up in round 1 to produce correct matchups:

//Bracket Structure represents the order of teams in which they should be printed in round 1 for specific number of teams.  
//As you see I have to manually specify for each team size, I wish to write an algorithm that will calculate this for me so that I do not have a maximum teams restriction.
private static readonly Dictionary<int, int[]> BracketStructure = new Dictionary<int, int[]>
{
    //2 is number of teams, 0 represents the team in the array, so 0 is actually team 1, 1 is team 2, etc...
    { 2, new [] { 0 } },
    { 4, new [] { 0, 1} },
    { 8, new [] { 0, 3, 2, 1} },
    { 16, new [] { 0, 7, 4, 3, 2, 5, 6, 1} },
    { 32, new [] { 0, 15, 7, 8, 3, 12, 4, 11, 1, 14, 6, 9, 2, 13, 5, 10 }},
    { 64, new [] { 0, 31, 16, 15, 8, 23, 24, 7, 3, 28, 19, 12, 11, 20, 27, 4, 1, 30, 17, 14, 9, 22, 25, 6, 2, 29, 18, 13, 10, 21, 26, 5 }}
};

private static void CreateMatchups(int totalTeams)
{
    var teams = new List<int>();
    var matchups = new List<string>();
    var c = 1;
    while (totalTeams >= c)
    {
        teams.Add(c);
        c++;
    }

    for (var i = 0; i < teams.Count/2; i++)
    {
        matchups.Add(teams[i] + " vs " + teams[totalTeams - (i + 1)]);
    }
    PrintBracket(matchups, BracketStructure[totalTeams]);
}

private static void PrintBracket(IReadOnlyList<string> matchups, IEnumerable<int> teams)
{
    foreach (var team in teams)
    {
        Console.WriteLine(matchups[team]);
    }
}

The problem with the above code is that it will not print them in the correct order. The order will be:

Team 1 vs Team 8
Team 2 vs Team 7
Team 3 vs Team 6
Team 4 vs Team 5

I can't seem to come up with the algorithm that will sort these in a seeded fashion. This needs to work for anywhere form a 2 man tournament to any number really... Any help or advise on this is most appreciated.

I have been basing my ordering on http://www.printyourbrackets.com/

I posted this originally on https://softwareengineering.stackexchange.com/ but I was told stack overflow is the right place for this question so I'm posting it here instead.

For more information on what a seeded bracket is, you can read here: https://en.wikipedia.org/wiki/Seed_(sports)

EDIT:

I came accross this as I was doing more research: https://jsfiddle.net/vrnb16r9/1/

The games are printed out in the correct order there, well almost, but I believe that his formula there is still accurate. For example with my 8 team example above it will be printed like this which is the correct order:

Round 1 =========> Round 2 =========> Round 3
Team 1 vs Team 8
Team 4 vs Team 5 ==> Team 1 vs Team 4 ==> Team 1 vs Team 2
Team 3 vs Team 6 ==> Team 3 vs Team 2
Team 2 vs Team 7

However his formula prints it out like this:

Round 1 =========> Round 2 =========> Round 3
Team 1 vs Team 8
Team 4 vs Team 5 ==> Team 1 vs Team 4 ==> Team 1 vs Team 2
Team 2 vs Team 7 ==> Team 2 vs Team 3
Team 3 vs Team 8

Notice that team 2 got moved up once and team 3 dropped down once? While his formula still leads to correct pairings in each round it doesn't 100% has the correct order. I realize that he just swapped the two teams position and it ultimately doesn't matter because no matter what, the next round will have correct matchups, However I would still love to figure out how to have them in the exact order they are suppose to, just like on print your bracket link I gave above. I believe his approach is in the right direction, figure out the winner and work your way backwards, however I'm having a little trouble understanding his code.

EDIT 2:

I've started a bounty because I'm still very lost with this and had very little luck putting together the logic. I'm basically hoping to get a c# code example on how to achieve what I've discussed above.

Community
  • 1
  • 1
Bagzli
  • 6,254
  • 17
  • 80
  • 163
  • What is the correct order of it? I see the order you posted at the begining, but I can't figure the criteria you used to do that sort. You are talking about "the better" and "the worst" but that will only aply from the second round, no? – Gusman May 12 '16 at 23:39
  • @Gusman look at my first 8 man display that has 3 rounds, that is the correct output. See because in round 2 team 1 should play against team 4, then the next set of games should be the ones that involve team 4. Its just a bracket and I'm trying to figure out the order. – Bagzli May 12 '16 at 23:41
  • What's your logic for team 1 playing team 4 instead of team 2? – John Biddle May 12 '16 at 23:46
  • @JohnBiddle the fact that this is a seeded bracket. In a seeded bracket the best team plays the worst team. This gives the highest chances of the best two teams playing in the finals. Here is more info: https://en.wikipedia.org/wiki/Seed_(sports) – Bagzli May 13 '16 at 01:13
  • Your algorithm seems to create and print the matchups in the order you want for me. Also, what is `BracketStructure`? – Chakrava May 13 '16 at 01:32
  • @Chakrava I updated the question to show that, sorry I can't believe I missed that part out. – Bagzli May 13 '16 at 14:04
  • You understand that there are many valid orderings? Why you insist on this concrete one, how is it better? – Evk May 16 '16 at 20:22
  • @Bagzli The way I would approach this is I would throw everything into a List using linq's orderby method (ordering by their seed number). For each matchup, you take position 0 and position (size - 1) of the list, and either remove those two from the list or iterate one into the list (e.g. position1 and position size-2, etc). Then each round you remove teams to be considered to be added to the list in the first place until you arrive at one. If you have odd numbers of teams, you can add in logic so one or more teams gets a bye round. – Marshall Tigerus May 16 '16 at 20:36
  • @Evk Yes I do, unfortunately my business rules dictate that i have to put them in this order. – Bagzli May 16 '16 at 20:37
  • you could make two lists. Consider teams 1, 8, 4, and 5 one list, and the rest the other, then when a list gets down to 1 team, merge them. – Marshall Tigerus May 16 '16 at 20:38
  • @MarshallTigerus Can you write a c# working example and post as an answer? I'm not fully following. – Bagzli May 16 '16 at 20:38
  • @Bagzli I'll try to get back on later to write one up, but I'm unable to commit that time at the momnet – Marshall Tigerus May 16 '16 at 20:39
  • That's understandable, but what is the logic behind this ordering? I understand logic behind seeding. Say we can put this right, but what about 16 people? Who knows what ordering you would expect there (since there are many valid ones). – Evk May 16 '16 at 20:40
  • @Evk Have a look at printyourbracket.com, the ordering logic follows that. I'm not sure how much better I can explain than examples there. – Bagzli May 16 '16 at 20:41
  • As I said, there are many valid orderings which will all result in final play between 1 and 2 (your goal), they are all equal. But ok, I understand that from all that equal orderings you need ones "as at printyourbracket.com site". – Evk May 16 '16 at 20:44
  • @Evk my goal is not just for 1 and 2 to play in final, my goal is also for 1 and 4 to play in round before that and 2 and 3, and then round before that 1 and 8, 2 and 7, 3 and 6, 4 and 5, etc... See the pattern? I also wish them to be listed like that, team 1 at the top, team 2 at the bottom, etc.. Assuming that this is not possible, the javascripts I posted above come pretty close. I was trying to translate that to c# but I kept getting nulls with my arrays, not sure how to write that efficiently without instantiating empty arrays all over the place. – Bagzli May 16 '16 at 20:48
  • It's in Python, but I suggest you have a look at this answer: https://stackoverflow.com/a/17752588/352085 – François Jun 14 '18 at 12:55

3 Answers3

6

I'll publish a code which I think does what you want, but ordering is not exact as you wish. I think those tables on printyourbracket.com were crafted by hand, not by algorithm (I also looked at 16 and 32 version). All in all, here is the code. First define small classes to make it easier to read code:

class Round {
    public Match[] Matches { get; set; }
}

class Match {
    // where number is player's seed number        
    public int PlayerA { get; set; }
    public int PlayerB { get; set; }
}

And here is the algorithm:

    static Round[] Generate(int playersNumber) {        
        // only works for power of 2 number of players   
        var roundsNumber = (int) Math.Log(playersNumber, 2);
        var rounds = new Round[roundsNumber];
        for (int i = 0; i < roundsNumber; i++) {
            var round = new Round();
            var prevRound = i > 0 ? rounds[i - 1] : null;
            if (prevRound == null) {
                // if first round - result is known
                round.Matches = new[] {
                    new Match() {
                        PlayerA = 1,
                        PlayerB = 2
                    }
                };
            }
            else {
                round.Matches = new Match[prevRound.Matches.Length*2];
                // find median. For round 2 there are 4 players and median is 2.5 (between 2 and 3)
                // for round 3 there are 8 players and median is 4.5 (between 4 and 5)
                var median = (round.Matches.Length*2 + 1)/2f;
                var next = 0;
                foreach (var match in prevRound.Matches) {
                    // you can play here by switching PlayerA and PlayerB or reordering stuff
                    round.Matches[next] = new Match() {
                        PlayerA = match.PlayerA,
                        PlayerB = (int) (median + Math.Abs(match.PlayerA - median))
                    };
                    next++;
                    round.Matches[next] = new Match() {
                        PlayerA = match.PlayerB,
                        PlayerB = (int) (median + Math.Abs(match.PlayerB - median))
                    };
                    next++;
                }
            }
            rounds[i] = round;
        }
        return rounds.Reverse().ToArray();
    }

Usage:

var rounds = Generate(8);
foreach (var round in rounds) {
     foreach (var match in round.Matches) {
         Console.WriteLine("{0} vs {1}", match.PlayerA, match.PlayerB);
     }
     Console.WriteLine();
}
Console.ReadKey();

Basically we start at root (1,2) and go backwards, matching high rank players to lowest ranks, second highest to second lowest and so on. From your description it's like in that javascript algorithm, though didn't look at their implementation. You can play with this code to try to achieve ordering you want, but if those table were made by humans - that might be not possible.

Evk
  • 98,527
  • 8
  • 141
  • 191
  • thanks for this, I'm going to try and test it when I get home tonight and see if I can play with ordering. – Bagzli May 16 '16 at 21:18
  • @Bagzi so that worked out fine, found a way to get your specific ordering? – Evk May 17 '16 at 19:01
  • No I have not, instead I've done some research and compared to other tournament websites and their ordering matches this logic. Because of this I was able to change my requirements. I used other websites as proof. Now I've got to figure out how to do double elimination :P lol – Bagzli May 17 '16 at 19:16
4

You can get the correct ordering using recursion. Fewer lines of code as well.

(fiddle https://dotnetfiddle.net/nD8dXb)

Recursive function:

public static void branch(int seed, int level, int limit) {
    var levelSum = (int) Math.Pow(2, level) + 1;

    if (limit == level + 1) {
        Console.WriteLine("Seed {0} vs. Seed {1}", seed, levelSum - seed);
        return;
    }
    else if (seed % 2 == 1) {
        branch(seed, level + 1, limit);
        branch(levelSum - seed, level + 1, limit);
    }
    else {
        branch(levelSum - seed, level + 1, limit);
        branch(seed, level + 1, limit);
    }
}

Call it for each round:

var numberTeams = 16;   // must be a power of 2
var limit = (int) (Math.Log(numberTeams, 2) + 1);

for (int round = 1; round < limit; round++) {
    Console.WriteLine("Round #{0}", round);
    branch(1, 1, limit - round + 1);
    Console.WriteLine();
}

Output:

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

Round #2
Seed 1 vs. Seed 8
Seed 4 vs. Seed 5
Seed 3 vs. Seed 6
Seed 2 vs. Seed 7

Round #3
Seed 1 vs. Seed 4
Seed 2 vs. Seed 3

Round #4
Seed 1 vs. Seed 2
bbobbo
  • 41
  • 3
  • Do you know how I can extend this to a double elimination bracket? – Bagzli Jan 14 '17 at 15:34
  • This always assumes that the highest seed wins their game though? How does it work if the lower seeded team wins? – Bad Dub Apr 25 '17 at 11:11
  • @BadDub You can't guarantee that the highest seeds wins. Seeding is only intended to give the highest probability that the highest seeded players will meet in the final. You would still want to display the intended paths of the seeds even though they may not win. – JordanGW Mar 19 '19 at 12:04
0

First off, I would create some classes for Teams and Matchups to better represent your problem and make it easier to manipulate data.

( Working example at: https://dotnetfiddle.net/Z1CJib )

public class Team
{
    public Team(string name, int seed){
        Name = name;
        Seed = seed;
    }

    public string Name {get; set;}

    public int Seed {get; set;}

    //simple method to compare two teams' seed values and return the better (1 > 2) team
    public static Team GetBetterSeed(Team t1, Team t2){
        return t1.Seed < t2.Seed ? t1 : t2;
    }
}

public class Matchup
{
    public Matchup(Team t1, Team t2){
        Team1 = t1;
        Team2 = t2;
    }

    public Team Team1 {get; set;}
    public Team Team2 {get; set;}

    //"Team 1's Name vs. Team 2's Name"
    public override string ToString(){
        return Team1.Name + " vs. " + Team2.Name;
    }

    //using the GetBetterSeed() method above we can get which team out of a matchup is favored
    public Team GetFavored(){
        return Team.GetBetterSeed(Team1, Team2);
    }
}

Now you can create a set of Teams that will have names and seeds values

var newTeam = new Team("Team Name", 1);
teamList.Add(newTeam);

or however you want to create a list of teams.


Your matchup algorithm actually works (so long are your team count is even), I just separated out the team creation from the matchups:

private static List<Matchup> CreateMatchups(List<Team> teams)
{
    var matchups = new List<Matchup>();

    for (int i = 0; i < teams.Count / 2; i++)
    {
        matchups.Add(new Matchup(teams[i], teams[teams.Count - (i+1)]));
    }

    return matchups;
}

To print your matchups in order you can use Linq's OrderBy. You give OrderBy a function telling it what part of an item you want to order by (in this case Team.Seed) and it will give the set to you ordered least to greatest by that value:

private static void PrintBracket(List<Matchup> Matchups)
{
    foreach(var matchup in Matchups.OrderBy(x=> x.GetFavored().Seed)){
        Console.WriteLine(matchup);
    }
}
Chakrava
  • 823
  • 7
  • 10
  • Hi Chakrava, what you have achieved is not exactly what I was asking. Your matchups are correct in each round, I agree to that and my examples above does the same result in round 1. However that is the problem. This needs to be printed out in a bracket format. So because team 1 and team 2 will get to play only in final round, the game for team 1 has to be first and then game for team 2 has to be last in the first round so that they can meet in the middle for final game. All other teams also have to play by this rule. Look at my question, My first example shows you the correct order. – Bagzli May 13 '16 at 13:57
  • I made a big update to my question, see if it makes more sense now. – Bagzli May 13 '16 at 14:14
  • Thought I'd let you know that I've started a bounty, in case you are still interested in helping out. – Bagzli May 16 '16 at 16:36
  • What happens if the lower seed wins there matchup, how would this code represent this situation? – Bad Dub Apr 25 '17 at 10:50