Round Robin Tournament algorithm in C#

This should be easy enough to do using modular arithmetic:

UPDATE 2: (As promised correct algorithm)

public void ListMatches(List<string> ListTeam)
{
    if (ListTeam.Count % 2 != 0)
    {
        ListTeam.Add("Bye");
    }

    int numDays = (numTeams - 1);
    int halfSize = numTeams / 2;

    List<string> teams = new List<string>();

    teams.AddRange(ListTeam.Skip(halfSize).Take(halfSize));
    teams.AddRange(ListTeam.Skip(1).Take(halfSize -1).ToArray().Reverse());

    int teamsSize = teams.Count;

    for (int day = 0; day < numDays; day++)
    {
        Console.WriteLine("Day {0}", (day + 1));

        int teamIdx = day % teamsSize;

        Console.WriteLine("{0} vs {1}", teams[teamIdx], ListTeam[0]);

        for (int idx = 1; idx < halfSize; idx++)
        {               
            int firstTeam = (day + idx) % teamsSize;
            int secondTeam = (day  + teamsSize - idx) % teamsSize;
            Console.WriteLine("{0} vs {1}", teams[firstTeam], teams[secondTeam]);
        }
    }
}

which would print each day's team matches.

Let me quickly try to explain how the algorithm works:

I noticed that since we are rotating all the teams except the first one, if we put all the teams in an array except the first one, then we should just read off the first team from that array using index offset based on the day and doing modular arithmetic to wrap around correctly. In practice we would be treating that array as infinitely repeating in both directions and we would be sliding our view incrementally to right (or to the left).

There is one snag, however, and that is the fact that we have to order the teams in a very particular way for this to work correctly. Otherwise, we do not get the correct rotation. Because of this we need to read of the matching second team in a very peculiar way as well.

The correct way to prepare your list is as follows:

  • Never put the first team (Team#1) in the list.
  • Take the last half of the team list and put them in the front of the list.
  • Take the first half of the list, reverse it and put them in the list (but not Team#1).

Now, the correct way to read off the list is as follow:

  • For each day, increment the first index you are looking at by 1.
  • For the first team that you see at that location, match that team with Team#1.
  • For the next team in the list ((day + idx) % numDays), we would normally match it with the team that is offset by half the number of teams minus 1 (minus 1 because we dealt with the first match ourselves). However, since the second half of our list was prepared by reverting, we need to match that offset in the reverted second half of the list. A simpler way to do is to observe that in this is equivalent to matching the same index but from the end of the list. Given the current day offset that is (day + (numDays - idx)) % numDays.

UPDATE 3: I was not happy that my solution involved such convoluted selection, matching, reversing of array elements. After I got thinking about what my solution involved I realized that I was too hung up about keep the order of the teams as given. However, that is not a requirement and one can get a different but equally valid schedule by not caring about the initial ordering. All that matters is the selection algorithm I describe in the second part of my explanation.

Thus you can simplify the following lines:

teams.AddRange(ListTeam.Skip(halfSize).Take(halfSize));
teams.AddRange(ListTeam.Skip(1).Take(halfSize -1).ToArray().Reverse());

to:

teams.AddRange(ListTeam); // Copy all the elements.
teams.RemoveAt(0); // To exclude the first team.

It sounds like you want to schedule a round-robin tournament. The wp article contains the algorithm.

I don't see where you are even trying to rotate the array. The permutation will look something like: 1 -> 2 -> 3 -> 4 ... -> n/2 - 1 -> n - 1 -> n - 2 -> n - 3 -> ... -> n/2 -> 1 (and 0 stays fixed). You can do that in 2 loops (top row and bottom row).


I made some improvements in the answered code block that calculates double round-robin schedule

GameEntities db = new GameEntities();


private void btnTeamFixtures_Click(object sender, RoutedEventArgs e)
    {
        txtResults.Text = null;

        var allTeams = db.Team.Select(t => t.TeamName);

        int numDays = allTeams.Count() - 1;
        int halfsize = allTeams.Count() / 2;

        List<string> temp = new List<string>();
        List<string> teams = new List<string>();

        teams.AddRange(allTeams);
        temp.AddRange(allTeams);
        teams.RemoveAt(0);

        int teamSize = teams.Count;

        for (int day = 0; day < numDays * 2; day++)
        {
            //Calculate1stRound(day);
            if (day % 2 == 0)
            {
                txtResults.Text += String.Format("\n\nDay {0}\n", (day + 1));

                int teamIdx = day % teamSize;

                txtResults.Text += String.Format("{0} vs {1}\n", teams[teamIdx], temp[0]);

                for (int idx = 0; idx < halfsize; idx++)
                {
                    int firstTeam = (day + idx) % teamSize;
                    int secondTeam = ((day + teamSize) - idx) % teamSize;

                    if (firstTeam != secondTeam)
                    {
                        txtResults.Text += String.Format("{0} vs {1}\n", teams[firstTeam], teams[secondTeam]);
                    }
                }
            }

            //Calculate2ndRound(day);
            if (day % 2 != 0)
            {
                int teamIdx = day % teamSize;

                txtResults.Text += String.Format("\n\nDay {0}\n", (day + 1));

                txtResults.Text += String.Format("{0} vs {1}\n", temp[0], teams[teamIdx]);

                for (int idx = 0; idx < halfsize; idx++)
                {
                    int firstTeam = (day + idx) % teamSize;
                    int secondTeam = ((day + teamSize) - idx) % teamSize;

                    if (firstTeam != secondTeam)
                    {
                        txtResults.Text += String.Format("{0} vs {1}\n", teams[secondTeam], teams[firstTeam]);
                    }
                }
            }
        }
    }

If u want u can create 2 methods and pass and integer(Day) like i did in the 2 commented lines, to separate the code.

If you have any questions or suggestions feel free to reply.