web-dev-qa-db-ja.com

スケジューリング:バランスのとれたホーム/アウェイラウンドロビントーナメントアルゴリズム

私は、公平またはバランスのとれたホーム/アウェイローテーションも保証するスポーツスケジューリングのラウンドロビンアルゴリズムを実現しようとしています。

私のアルゴリズムは ラウンドロビンスケジューリングアルゴリズム に基づいています:

def round_robin(teams, rounds):
    # bye
    if len(teams) % 2:
        teams.append(None)

    schedule = []
    for turn in range(rounds):
        pairings = []
        for i in range(len(teams) / 2):
            pairings.append((teams[i], teams[len(teams) - i - 1]))
        teams.insert(1, teams.pop())
        schedule.append(pairings)

    return schedule

ただし、このアルゴリズムはH/Aバランスには対応していません。私はこれら2つの目的を達成しようとしています。

  • H/Aローテーション:各チームは1ラウンドおきにホームゲームとアウェイゲームをプレイする必要があります。つまり、5ラウンドのトーナメントでは、特定のチームは次のローテーションを持つ必要があります:H-A-H-A-H

  • H/Aゲームの数:ホームゲームとアウェイゲームの数は同じ(または合計が奇数の場合は1でオフセット)である必要があるため、チームごとに10ゲームのトーナメントには5つのホームゲームと5つのアウェイゲームが必要です。各チーム。

前のアルゴリズムはこれらの目的のどれも満たしていません:

  • H/Aゲームを代替しません。たとえば、最初のチームはホームゲームのみを持ち、他のチームはリストの最後にローテーションされるまでホームゲームを獲得し、次にアウェイゲームのみを獲得します(逆も同様です。 )。以下の意味を参照してください(1)。

  • H/Aゲームのバランスの取れた数を保証しますが、ラウンド数がゲーム数(またはゲーム数-1)の倍数である場合に限り、それ以外の場合、ホームまたはアウェイゲームの数が多すぎます(違い)実際のゲーム数と次の倍数の間)。

(1)

[(1, 8), (2, 7), (3, 6), (4, 5)]
[(1, 7), (8, 6), (2, 5), (3, 4)]
[(1, 6), (7, 5), (8, 4), (2, 3)]
[(1, 5), (6, 4), (7, 3), (8, 2)]
[(1, 4), (5, 3), (6, 2), (7, 8)]
[(1, 3), (4, 2), (5, 8), (6, 7)]
[(1, 2), (3, 8), (4, 7), (5, 6)]

バランスのとれた回転に近づくために、いくつかの簡単な変更を加えました。

def round_robin(teams, rounds):
    # bye
    if len(teams) % 2:
        teams.append(None)

    schedule = []
    for turn in range(rounds):
        pairings = []
        for i in range(len(teams) / 2):
            pairing = (teams[i], teams[len(teams) - i - 1])
            # alternate first team
            if i == 0 and turn % 2:
                pairing = pairing[::-1]
            # alternate based on the team's previous round appearance
            if schedule and None not in pairing:
                previous_round = list(sum(schedule[-1], ()))
                for team in pairing:
                    if team in previous_round and pairing[previous_round.index(team) % 2] == team:
                        pairing = pairing[::-1]
            pairings.append(pairing)
        teams.insert(1, teams.pop())
        schedule.append(pairings)

    return schedule

しかし、もちろんこれは賢いアルゴリズムではありません。それは役立ちますが、両方の目的のいずれかを完全には満たしません。

私はこの問題に対処するアルゴリズムを見つけようとしましたが、それを見つけることができませんでした。トーナメントのスケジュールが一般的な問題であるため、驚きです。

私はこの問題を使用するために制約プログラミングを使用する考えがありますが、これが最終的な解決策になるかどうかは完全にはわかりません。多分それは少しやり過ぎで、もう1つの最も古典的なアプローチの方が優れています。また、問題を制約プログラミング問題(またはCSP)として定式化することは、少なくとも私にとってはかなりの困難であることがわかります。

どんな種類の助け、提案、洞察も大歓迎です。

5
dabadaba

ラウンドロビンシステムでこの要件を厳密に満たすことは不可能だと思います。

H/Aローテーション:各チームは1ラウンドおきにホームゲームとアウェーゲームをプレイする必要があります。

ラウンドロビンシステムがあり、各チームが各ラウンドで1つの試合に参加し、すべてのチームが完全に交互のホームアウェイスケジュールを持っているとします。 AとBを、最初のラウンドで両方ともホームでプレーするチームとします。その後、彼らは自宅で奇数ラウンドごとにプレーし、偶数ラウンドごとにプレーします。 Aが自宅でプレーし、Bが離れてプレーする、またはその逆のラウンドは決して発生しません。その結果、AとBがホームロケーションを共有しない場合、AとBが互いに向き合うことはありません。システムに欠陥があります。

チームの場所(ホームまたはアウェイ)を標準ラウンドロビンアルゴリズムの行に割り当て、それらを交互に配置することで、かなりうまくいくと思います。

したがって、ラウンド1(および奇数の各ラウンド)では、最初の行にホームチームがあり、2番目の行にアウェイチームがあります。

ラウンド1.(1プレイ14、2プレイ13、...)
 1 2 3 4 5 6 7(ホーム)
 14 13 12 11 10 9 8(アウェイ)

ラウンド2(および各ラウンド)では、最初の行にアウェイチームがあり、2番目の行にホームチームがあります。

ラウンド2(1プレイ13、14プレイ12、...)
 1 14 2 3 4 5 6(アウェイ)
 13 12 11 10 9 8 7(ホーム)

ホームとアウェイを交互に行うには、最初のアルゴリズムを使用してこの行を変更します。

pairings.append((teams[i], teams[len(teams) - i - 1]))

このようなものに

if turn % 2:
    home = teams[i]
    away = teams[len(teams) - i - 1])
else:
    home = teams[len(teams) - i - 1])
    away = teams[i]
pairings.append((home, away))
1
COME FROM

私はこのようなもので行くことになりました。デフはいくつかのクリーンアップと重複除外を使用できます。

import collections
teams = [1, 2, 3, 4, 5, 6, 7, 8, 9]
rounds = 8
schedule = []

teams.insert(0, None)
d = collections.deque(teams)
dl = list(collections.deque(d))
pl = list(Zip(dl[::2], dl[1::2]))
schedule.append(pl)

for turn in range(rounds):
    d.rotate(1)
    # Swap bye week back to top
    d[1] = d[0]
    d[0] = None
    dl = list(collections.deque(d))
    pl = list(Zip(dl[::2], dl[1::2]))

    schedule.append(pl)

print(schedule)

出力は正気のようです:

[(None, 1), (2, 3), (4, 5), (6, 7), (8, 9)], 
[(None, 9), (1, 2), (3, 4), (5, 6), (7, 8)], 
[(None, 8), (9, 1), (2, 3), (4, 5), (6, 7)], 
[(None, 7), (8, 9), (1, 2), (3, 4), (5, 6)], 
[(None, 6), (7, 8), (9, 1), (2, 3), (4, 5)], 
[(None, 5), (6, 7), (8, 9), (1, 2), (3, 4)], 
[(None, 4), (5, 6), (7, 8), (9, 1), (2, 3)], 
[(None, 3), (4, 5), (6, 7), (8, 9), (1, 2)], 
[(None, 2), (3, 4), (5, 6), (7, 8), (9, 1)], 
1
John