9

I am having some trouble to achieve this little round robin project. What i try to do is to generate a preview calendar of games

then I want to output;

day 1: Team 1 vs Team 2; Team 3 vs Team 4; Team 5vs Team 6;

day 2 Team 1 vs Team 4; Team 6 vs Team 3; Team 2 vs Team 5;

till the end of the championship;

Here is the code i've got so far but i'm having trouble to let the first team fixed while the rest of the array rotates...:

static void Main(string[] args)
   {
        string[] ListTeam = new string[] {"Equipe1", "Equipe2", "Equipe3", "Equipe4", "Equipe5", "Equipe6"};
        IList<Match> ListMatch = new List<Match>();
        it NumberOfDays = (ListTeam.Count()-1);
        int y = 2;

        for (int i = 1; i <= NumberOfDays; i++)
        {
            Console.WriteLine("\nDay {0} : \n",i);
            Console.WriteLine(ListTeam[0].ToString() + " VS " + ListTeam[i].ToString());

            for (y =ListTeam.Count(); y>0 ; y--)
            {
                Console.WriteLine(ListTeam[y].ToString() + " VS " + ListTeam[y+1].ToString());
                y++;
            }

        }
    }

EDIT: I found a code sample in java but i cant translate it...

Matt
  • 74,352
  • 26
  • 153
  • 180
Polo
  • 1,770
  • 4
  • 18
  • 29
  • I cant find the way to encre the first team and still do ouput the other matches – Polo Aug 18 '09 at 08:51
  • @John Nolan : sorry for the typos and spelling... – Polo Aug 18 '09 at 10:54
  • The Java implementation that you have linked to is very obscure. Also, the fact that the text and variables are in French does not help (me, at least). – paracycle Aug 19 '09 at 17:17

8 Answers8

13

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.
paracycle
  • 7,665
  • 1
  • 30
  • 34
  • So far so good but if you try to ouput Team 1 vs Team 2 you'll get an exeption – Polo Aug 18 '09 at 11:06
  • Nop i get twice the same games day 2 : Team4 vs Team 5 day 5: Team5 vs Team4 same for Team3 vs Team4 – Polo Aug 18 '09 at 12:13
  • 1
    Hey, sorry, I see the mistake in my implementation. Will fix it. – paracycle Aug 18 '09 at 12:27
  • Its weird i cant find why i cant do this... It really get on my nervs – Polo Aug 18 '09 at 12:44
  • Yeah. The problem of solving it in two loops **is** harder than I initially thought. I am working on it, will post back. Promise. :) – paracycle Aug 18 '09 at 18:34
  • Any news? if you want i have the code in java but cant find a way to do int[][] – Polo Aug 19 '09 at 07:50
  • If you have the code in Java it should be trivial to post it over to C# (the differences are very minimal). C# also supports multidimensional arrays, so you should be able to do int[][] or (even better) int[,]. See http://msdn.microsoft.com/en-us/library/aa288453%28VS.71%29.aspx for reference. However, it should be trivial to implement the solution if you are keeping track of the previous rotations. I am trying to find a solution that would give the correct iteration just being given the *day* number. Unfortunately, work is interfering. :) Will keep you posted. – paracycle Aug 19 '09 at 08:00
  • I meant "port" instead of "post" obviously. :) – paracycle Aug 19 '09 at 08:01
  • Its the same for me i am at work for the day and it more tricky then i thought – Polo Aug 19 '09 at 08:53
  • Here is the java round robin code http://www.developpez.net/forums/d267356-2/c-cpp/cpp/generation-calendrier-football/#post1700042 – Polo Aug 19 '09 at 10:18
  • There you go. I fixed my answer. – paracycle Aug 20 '09 at 06:54
6

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).

bmm6o
  • 6,187
  • 3
  • 28
  • 55
  • Thank you for the answer but is there a way to generate it by random? – Polo Aug 18 '09 at 07:38
  • where? i could only find the methode – Polo Aug 18 '09 at 12:18
  • ? method should lead directly to an appropriate algorithm. Adding a random labeling of the teams is a trivial addition and satisfies your requirements. It might be noted that the 'clockwise rotation' the wp page speaks is of a ring, so, 1 2 3 4, rotated clockwise one is 4 1 2 3. – nlucaroni Aug 18 '09 at 14:50
  • You can randomly permute both teams and days. – bmm6o Aug 18 '09 at 16:15
  • But isn't what is being asked for an implementation of the algorithm rather than the algorithm itself? The question is pointing to the Wikipedia article in the first place, so the poster is obviously aware of what he has to do, but he is asking how he should do it. – paracycle Aug 19 '09 at 17:15
  • 1
    hmm, I don't remember the link to an algorithm being there when I answered. – bmm6o Aug 19 '09 at 17:58
  • Yes, you are right, the original question did not include the link. My mistake. – paracycle Aug 19 '09 at 22:03
2

Based on the @paracycle's answer, I am providing a better code with the following changes:

  • My method is based on the generic type T
  • My code doesn't change the original teams list innstance
  • Improved code quality
public static IEnumerable<(int Day, T First, T Second)> ListMatches<T>(IList<T> teams)
{
    var matches = new List<(int, T, T)>();
    if (teams == null || teams.Count < 2)
    {
        return matches;
    }

    var restTeams = new List<T>(teams.Skip(1));
    var teamsCount = teams.Count;
    if (teams.Count % 2 != 0)
    {
        restTeams.Add(default);
        teamsCount++;
    }

    for (var day = 0; day < teamsCount - 1; day++)
    {
        if (restTeams[day % restTeams.Count]?.Equals(default) == false)
        {
            matches.Add((day, teams[0], restTeams[day % restTeams.Count]));
        }

        for (var index = 1; index < teamsCount / 2; index++)
        {
            var firstTeam = restTeams[(day + index) % restTeams.Count];
            var secondTeam = restTeams[(day + restTeams.Count - index) % restTeams.Count];
            if (firstTeam?.Equals(default) == false && secondTeam?.Equals(default) == false)
            {
                matches.Add((day, firstTeam, secondTeam));
            }
        }
    }

    return matches;
}

Code to test it:

foreach (var match in ListMatches(new List<string> { "T1", "T2", "T3", "T4", "T5", "T6" }))
{
    Console.WriteLine($"{match.Day} => {match.First}-{match.Second}");
}

Output for 6 teams:

0 => T1-T2
0 => T3-T6
0 => T4-T5
1 => T1-T3
1 => T4-T2
1 => T5-T6
2 => T1-T4
2 => T5-T3
2 => T6-T2
3 => T1-T5
3 => T6-T4
3 => T2-T3
4 => T1-T6
4 => T2-T5
4 => T3-T4

Output for 5 teams:

0 => T1-T2
0 => T4-T5
1 => T1-T3
1 => T4-T2
2 => T1-T4
2 => T5-T3
3 => T1-T5
3 => T2-T3
4 => T2-T5
4 => T3-T4
Nikolay Kostov
  • 16,433
  • 23
  • 85
  • 123
2

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.

Adonis07
  • 21
  • 2
1

It may be a convoluted method, but this can be reduced to a graph theory problem. Create a graph vertex for each team, and create an edge between every vertex (so it is a complete graph). Then for the algorithm:

For each day i = 1 .. n :

  • Pick any two unmarked vertices that are directly connected and label the edge between them with i. Mark both vertices.
  • Repeat until all vertices are marked.
  • Output the labelled edges (i.e. team1 vs team2, team3 vs team4 etc)
  • Delete the labelled edges from the graph and reset all vertices to unmarked.
tw39124
  • 9,103
  • 2
  • 20
  • 14
1

I've posted my implementation on github RoundRobinTournamentSchedule You can find corresponding nuget package and use it in your own solution

Cheers

Vlad Nan
  • 61
  • 1
  • 6
0

How about calculating the possible combinations for each day that you want then

  1. sort them within each pair i.e. lowest number team is always first in any pair.
  2. sort the listed pairings for each day by the first in each pair.
RobS
  • 3,807
  • 27
  • 34
  • (NumberOfTeams /2) and after ??? i didn't get your point, but as supose to i need to encre the first team and rotate the array until it goes back to first node but i can't find a way – Polo Aug 18 '09 at 10:52
  • I am supose to encre the first team and then rotate the array (the part i miss) until it goes back to original point – Polo Aug 18 '09 at 10:57
  • I think I'm trying to say, instead of trying to do the rotation *and* keep team1 first using a one pass algorithm do it in a two-pass algorithm instead. – RobS Aug 18 '09 at 10:58
  • Ok, I think I understand now, you're actually struggling to get all the possible team pairings in the first place. In that case paracycle's answer http://stackoverflow.com/questions/1293058/round-robin-tournament-algorithm-in-c/1293174#1293174 gets you all the possible pairings, and it's up to you to allot them to each day. – RobS Aug 18 '09 at 11:05
0

Assuming we always have even number teams/players (if odd, add BYE; for my case the teams/players ranked by their rating, we would like to have the top seeded team/player to play weaker opponent first), Here is my implementation.

void CreateSchedule(int n)
{
    int[] orig = new int[n];
    for(int i=0;i<n; i++){
        orig[i] = i + 1;
    }   
    IEnumerable<int> rev = orig.Reverse();

    int len = orig.Length;
    for (int j = 0; j < len - 1; j++)
    {
        List<int> tmp = new List<int>();
        tmp.Add(orig[0]);
        tmp.AddRange(rev.Take(j).Reverse());
        if (j < len && len > 1 + j) tmp.AddRange(orig.Skip(1).Take(len - 1 - j));
        PrintMe(tmp, j + 1);
    }
}

void PrintMe(IEnumerable<int> arr, int round)
{

    Console.WriteLine("----------Round {0}----------------", round);
    int halfSize = arr.Count() / 2;

    IEnumerable<int> A = arr.Take(halfSize);
    IEnumerable<int> B = arr.Skip(halfSize).Take(halfSize).Reverse();

    var Result = A.Zip(B, (x, y) => $"{x} vs {y}");
    Console.WriteLin(Result);
}
PerlDev
  • 437
  • 1
  • 5
  • 15