In [1]:
import itertools
import random
import collections
In [2]:
teams = ['A', 'B', 'C', 'D', 'E', 'F']
forbidden_combinations = [('A', 'B')]
In [3]:
def is_forbidden(pair, forbidden_combinations):
    return pair in forbidden_combinations or tuple(reversed(pair)) in forbidden_combinations

def is_legal(draw, remaining_teams, forbidden_combinations):
    for team in remaining_teams:
        if is_forbidden((draw, team), forbidden_combinations):
            return False
    return True

def draw_teams_with_backtracking(teams, forbidden_combinations, matchups=[]):
    random.shuffle(teams)
    if not teams:
        return matchups
        
    for i, current_team in enumerate(teams):
        if len(matchups) % 2 == 0:
            next_matchups = matchups + [current_team]

            remaining_teams = teams[:i] + teams[i+1:]
            result = draw_teams_with_backtracking(remaining_teams, forbidden_combinations, next_matchups)
            if result: 
                return result
        else:
            last_team = matchups[-1]
            if not is_forbidden((last_team, current_team), forbidden_combinations):
                next_matchups = matchups + [current_team]
                remaining_teams = teams[:i] + teams[i+1:]
                result = draw_teams_with_backtracking(remaining_teams, forbidden_combinations, next_matchups)
                if result: 
                    return result

    return None

def calculate_draw_odds(teams, forbidden_combinations, specified_team=None, simulations=10000):
    matchup_counts = {team: {opponent: 0 for opponent in teams if team != opponent} for team in teams}
    
    for _ in range(simulations):
        random.shuffle(teams)
        matchups = draw_teams_with_backtracking(teams, forbidden_combinations)

        if matchups:
            for i in range(0, len(matchups) - 1, 2):
                team1, team2 = matchups[i], matchups[i + 1]
                matchup_counts[team1][team2] += 1
                matchup_counts[team2][team1] += 1

    odds = {team: {} for team in teams}
    for team1 in teams:
        total_matches = sum(matchup_counts[team1].values())
        for team2 in matchup_counts[team1]:
            if total_matches > 0:
                odds[team1][team2] = (matchup_counts[team1][team2] / total_matches) * 100
            else:
                odds[team1][team2] = 0

    return odds
In [4]:
draw_teams = draw_teams_with_backtracking
In [5]:
for i in range(10):
    drawn_teams = draw_teams(teams, forbidden_combinations)
    print("\nFinal draw order:", drawn_teams)
Final draw order: ['E', 'A', 'B', 'D', 'C', 'F']

Final draw order: ['D', 'F', 'C', 'A', 'B', 'E']

Final draw order: ['C', 'E', 'A', 'F', 'D', 'B']

Final draw order: ['A', 'D', 'B', 'F', 'C', 'E']

Final draw order: ['C', 'A', 'D', 'B', 'E', 'F']

Final draw order: ['A', 'E', 'D', 'F', 'C', 'B']

Final draw order: ['C', 'E', 'F', 'B', 'D', 'A']

Final draw order: ['B', 'D', 'A', 'E', 'C', 'F']

Final draw order: ['B', 'E', 'C', 'F', 'D', 'A']

Final draw order: ['E', 'C', 'B', 'D', 'A', 'F']
In [6]:
odds = calculate_draw_odds(teams, forbidden_combinations, simulations=100000)
for team1, opponents in odds.items():
    print(f"{team1}:")
    for team2, percentage in opponents.items():
        if percentage > 0:
            print(f"  vs {team2}: {percentage:.1f}%")
    print()
E:
  vs A: 24.8%
  vs D: 16.7%
  vs F: 16.8%
  vs B: 25.0%
  vs C: 16.7%

B:
  vs E: 25.0%
  vs D: 25.1%
  vs F: 24.9%
  vs C: 25.0%

F:
  vs E: 16.8%
  vs A: 25.0%
  vs D: 16.6%
  vs B: 24.9%
  vs C: 16.7%

A:
  vs E: 24.8%
  vs D: 25.1%
  vs F: 25.0%
  vs C: 25.0%

C:
  vs E: 16.7%
  vs A: 25.0%
  vs D: 16.5%
  vs F: 16.7%
  vs B: 25.0%

D:
  vs E: 16.7%
  vs A: 25.1%
  vs F: 16.6%
  vs B: 25.1%
  vs C: 16.5%

In [18]:
def draw_teams_with_presets(teams, forbidden_combinations, matchups, available_teams):
    if not available_teams:
        return matchups
        
    next_slot = matchups.index(None)

    for i, current_team in enumerate(available_teams):
        if next_slot % 2 == 0:
            next_matchups = matchups[:]
            next_matchups[next_slot] = current_team

            remaining_teams = available_teams[:i] + available_teams[i+1:]
            result = draw_teams_with_presets(teams, forbidden_combinations, next_matchups, remaining_teams)
            if result: 
                return result
        else: 
            last_team = matchups[next_slot - 1]
            if not is_forbidden((last_team, current_team), forbidden_combinations):
                next_matchups = matchups[:]
                next_matchups[next_slot] = current_team

                remaining_teams = available_teams[:i] + available_teams[i+1:]
                result = draw_teams_with_presets(teams, forbidden_combinations, next_matchups, remaining_teams)
                if result: 
                    return result

    return None

def calculate_draw_odds_with_presets(teams, forbidden_combinations, preset_matchups=None, simulations=10000):
    if preset_matchups is None:
        preset_matchups = [None] * len(teams)

    matchup_counts = {team: {opponent: 0 for opponent in teams if team != opponent} for team in teams}
    
    for _ in range(simulations):
        random.shuffle(teams) 
        available_teams = [team for team in teams if team not in preset_matchups]

        matchups = draw_teams_with_presets(teams, forbidden_combinations, preset_matchups[:], available_teams)

        if matchups:
            for i in range(0, len(matchups) - 1, 2):
                team1, team2 = matchups[i], matchups[i + 1]
                matchup_counts[team1][team2] += 1
                matchup_counts[team2][team1] += 1

    odds = {team: {} for team in teams}
    for team1 in teams:
        total_matches = sum(matchup_counts[team1].values())
        for team2 in matchup_counts[team1]:
            if total_matches > 0:
                odds[team1][team2] = (matchup_counts[team1][team2] / total_matches) * 100
            else:
                odds[team1][team2] = 0

    return odds
In [19]:
teams = ['A', 'B', 'C', 'D', 'E', 'F']
forbidden_combinations = [('A', 'B')]

preset_matchups = ['F', None, None, None, None, None]
In [20]:
odds = calculate_draw_odds_with_presets(teams, forbidden_combinations, preset_matchups, simulations=10000)

print("Odds of drawing each team as an opponent (in %), grouped by team:\n")
for team1, opponents in odds.items():
    print(f"{team1}:")
    for team2, percentage in opponents.items():
        print(f"  vs {team2}: {percentage:.2f}%")
    print()
Odds of drawing each team as an opponent (in %), grouped by team:

E:
  vs A: 25.95%
  vs B: 26.64%
  vs C: 13.03%
  vs D: 13.79%
  vs F: 20.59%

A:
  vs B: 0.00%
  vs C: 26.89%
  vs D: 26.67%
  vs E: 25.95%
  vs F: 20.49%

D:
  vs A: 26.67%
  vs B: 26.19%
  vs C: 13.50%
  vs E: 13.79%
  vs F: 19.85%

F:
  vs A: 20.49%
  vs B: 19.83%
  vs C: 19.24%
  vs D: 19.85%
  vs E: 20.59%

B:
  vs A: 0.00%
  vs C: 27.34%
  vs D: 26.19%
  vs E: 26.64%
  vs F: 19.83%

C:
  vs A: 26.89%
  vs B: 27.34%
  vs D: 13.50%
  vs E: 13.03%
  vs F: 19.24%

In [ ]: