pythonscheduling

Find ternary relationships with no duplicates for game scheduling algorithm


I'm working on assigning groups in Python using rules.

You could think about it like building a season for a three player based game.

Each player needs to face the other players on the list exactly once.

So suppose I have this list:


    players = ['a','b','c','d','e','f','g','h','i','j','k']

Here's the expected output if we start with 'a':

game schedule for a:

game 1 = a, b, c

game 2 = a, d, e

game 3 = a, f, g

game 4 = a, h, i

game 5 = a, j, k

I'll need a schedule like that above for each player.

What I've got right now gets me 45 combinations for player 'a'... trying to figure out how to whittle it down:


    players = ['a','b','c','d','e','f','g','h','i','j','k']
    
    from itertools import combinations
    
    num = 3
    
    all_games = list(combinations(players, num))
    
    a_s = []
    
    for i in all_games:
        if 'a' in i:
            a_s.append(i)

I'm just not sure how to build in the condition I want.

I suppose I could (in pseudo code):


    create a list without the player I want:
    
    player = ['a']
    opponents = ['b','c','d','e','f','g','h','i','j','k']
    
    extract/get every 2 elements from the list
    
    add player a to the extracted opponent pairs

But I don't know if that's close to the best way to solve this.


Solution

  • Here is a way to solve this problem; using for loops.

    players = ['a','b','c','d','e','f','g','h','i','j','k']
    player_games = {}
    
    for opponent in range(1, len(players), 2):
        player_games[f"game {int((opponent + 1) / 2)}"] = ["a", players[opponent], players[opponent+1]] # Replace "a" with your criteria of finding the player
    
    print(player_games)
    

    Output:

    {'game 1': ['a', 'b', 'c'], 'game 2': ['a', 'd', 'e'], 'game 3': ['a', 'f', 'g'], 'game 4': ['a', 'h', 'i'], 'game 5': ['a', 'j', 'k']}
    

    This code works assuming you have an odd amount of players (even number after excluding the player.). If you just want something like [a,l] in the end, here is a tweaked version of the code:

    players = ['a','b','c','d','e','f','g','h','i','j','k',"l"]
    player_games = {}
    
    for opponent in range(1, len(players), 2):
        try:
            player_games[f"game {int((opponent + 1) / 2)}"] = ["a", players[opponent], players[opponent+1]]
        except IndexError:
            player_games[f"game {int((opponent + 1) / 2)}"] = ["a", players[opponent]]
    
    print(player_games)
    

    we just add a try..except block to catch an IndexError which we get with the above code.

    Output:

    {'game 1': ['a', 'b', 'c'], 'game 2': ['a', 'd', 'e'], 'game 3': ['a', 'f', 'g'], 'game 4': ['a', 'h', 'i'], 'game 5': ['a', 'j', 'k'], 'game 6': ['a', 'l']}
    

    Here is a short explanation for how this code works.

    We use a range to include every other letter's index (skip the first one since that is the player). We then include the current index and the index after the current one in a list. For example, ['b', 'c'] and then squash an a at the front: ['a', 'b', 'c']

    The key increases by one for every iteration, but we are using range with 2 step, meaning we have to divide the opponent by two to get the game number. But, since we started at 1, we also have to add one.