pythonpython-3.xalgorithmrandomsports-league-scheduling-problem

Randomise round-robin league schedule given certain constraints


I'm trying to write a script to randomise a round-robin schedule for a tournament.

The constraints are:

My code works fine in theory, but when it's generated it sometimes freezes on certain weeks when there are only two teams left for that week, and both possible games have already been played. I use a numpy array to check which matchups have been played.

At the moment my code looks like this:

import random
import numpy

regular_season_games = 14
regular_season_week = 0

checker = numpy.full((8,8), 0)

for x in range (0,8):
    checker[x][x] = 1

teams_left = list(range(8))

print ("Week " + str(regular_season_week+1))

while (regular_season_week < regular_season_games):

    game_set = False
    get_away_team = False

    while get_away_team == False:
        Team_A = random.choice(teams_left)
        if 0 in checker[:,Team_A]:
            for x in range (0,8):
                if checker[x][Team_A] == 0 and x in teams_left:
                    teams_left.remove(Team_A)
                    get_away_team = True
                    break

    while game_set == False:
        Team_B = random.choice(teams_left)
        if checker[Team_B][Team_A] == 0:
            teams_left.remove(Team_B)
            print(str(Team_A) + " vs " + str(Team_B))
            checker[Team_B][Team_A] = 1
            game_set = True

    if not teams_left:
        print ("Week " + str(regular_season_week+2))
        teams_left = list(range(8))
        regular_season_week = regular_season_week + 1

Solution

  • I've used an adaptation of the scheduling algorithm from here to achieve this. Basically, we generate a list of the teams - list(range(8)) - and choose as our initial matchup 0 vs 4, 1 vs 5, 2 vs 6, 3 vs 7. We then rotate the list, excluding the first element, and choose as our next matchup 0 vs 3, 7 vs 4, 1 vs 5, 2 vs 6. We continue on in the following way until we have every pairing.

    I've added a handler for home & away matches - if a pairing has already been played, we play the opposite home/away pairing. Below is the code, including a function to check if a list of games is valid, and a sample output.

    Code:

    import random
    
    # Generator function for list of matchups from a team_list
    def games_from_list(team_list):
        for i in range(4):
            yield team_list[i], team_list[i+4]
    
    # Function to apply rotation to list of teams as described in article
    def rotate_list(team_list):
        team_list = [team_list[4]] + team_list[0:3] + team_list[5:8] + [team_list[3]]
        team_list[0], team_list[1] = team_list[1], team_list[0]
        return team_list
    
    # Function to check if a list of games is valid
    def checkValid(game_list):
        if len(set(game_list)) != len(game_list):
            return False
        for week in range(14):
            teams = set()
            this_week_games = game_list[week*4:week*4 + 4]
            for game in this_week_games:
                teams.add(game[0])
                teams.add(game[1])
            if len(teams) < 8:
                return False
        else:
            return True
    
    
    # Generate list of teams & empty list of games played
    teams = list(range(8))
    games_played = []
    
    # Optionally shuffle teams before generating schedule
    random.shuffle(teams)
    
    # For each week -
    for week in range(14):
        print(f"Week {week + 1}")
    
        # Get all the pairs of games from the list of teams.
        for pair in games_from_list(teams):
            # If the matchup has already been played:
            if pair in games_played:
                # Play the opposite match
                pair = pair[::-1]
    
            # Print the matchup and append to list of games.
            print(f"{pair[0]} vs {pair[1]}")
            games_played.append(pair)
    
        # Rotate the list of teams
        teams = rotate_list(teams)
    
    # Checks that the list of games is valid 
    print(checkValid(games_played))
    

    Sample Output:

    Week 1
    0 vs 7
    4 vs 3
    6 vs 1
    5 vs 2
    Week 2
    0 vs 3
    7 vs 1
    4 vs 2
    6 vs 5
    Week 3
    0 vs 1
    3 vs 2
    7 vs 5
    4 vs 6
    Week 4
    0 vs 2
    1 vs 5
    3 vs 6
    7 vs 4
    Week 5
    0 vs 5
    2 vs 6
    1 vs 4
    3 vs 7
    Week 6
    0 vs 6
    5 vs 4
    2 vs 7
    1 vs 3
    Week 7
    0 vs 4
    6 vs 7
    5 vs 3
    2 vs 1
    Week 8
    7 vs 0
    3 vs 4
    1 vs 6
    2 vs 5
    Week 9
    3 vs 0
    1 vs 7
    2 vs 4
    5 vs 6
    Week 10
    1 vs 0
    2 vs 3
    5 vs 7
    6 vs 4
    Week 11
    2 vs 0
    5 vs 1
    6 vs 3
    4 vs 7
    Week 12
    5 vs 0
    6 vs 2
    4 vs 1
    7 vs 3
    Week 13
    6 vs 0
    4 vs 5
    7 vs 2
    3 vs 1
    Week 14
    4 vs 0
    7 vs 6
    3 vs 5
    1 vs 2
    True