0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

もしカーリング女子北京オリンピック最終予選が、敗戦即敗退のトーナメント方式だったら...

Last updated at Posted at 2022-01-05

はじめに

初めまして、まず、この記事を書く前に、北京オリンピックに出場が決まった、カーリング女子、日本代表のロコ・ソラーレに敬意を表しまして、改めておめでとうございます。と申し上げます。
大会開催期間中、2021年12月11日から17日まで、プレーオフ出場圏内で、一進一退の攻防で手に汗握る熱戦を、毎晩固唾を飲んで観戦しておりました。
無事にカーリング女子、日本代表のロコ・ソラーレが北京オリンピックの出場権を獲得したとき、総当たり対戦表結果を見て、ふとしたことに気づきました。
まずは、出場全9チームの総当たり対戦成績表を下記に示します。

カーリング女子・最終予選の総当たり対戦成績表

スコットランド 韓国 日本 ラトビア イタリア ドイツ トルコ エストニア チェコ
スコットランド × ×
韓国 × ×
日本 × ×
ラトビア × × × ×
イタリア × × × ×
ドイツ × × × × ×
トルコ × × × × ×
エストニア × × × × × ×
チェコ × × × × × ×

総当たり対戦表から見えてくるもの

まず、上位3チームの対戦成績を見てみると

スコットランド 韓国 日本
スコットランド ×
韓国 ×
日本 ×
  • スコットランドは日本に勝ち、韓国に負ける。
  • 韓国はスコットランドに勝ち、日本に負ける。
  • 日本は韓国に勝ち、スコットランドに負ける。

と見事、三竦みの関係になっています。ジャンケンでも採用されているこの三竦み。絶対王者がないこの勝敗ルールのおかげで、私たちは今までも、恐らく未来永劫これからも、何かを決めるときにジャンケンで勝敗を決める機会は、これからも幾度となくやってくることは間違い無いと考えています。
そして、興味深いのは、上位3チームだけでなく、4位から6位の3チームも三竦みの関係になっています。

ラトビア イタリア ドイツ
ラトビア ×
イタリア ×
ドイツ ×
  • ラトビアはイタリアに勝ち、ドイツに負ける。
  • イタリアはドイツに勝ち、ラトビアに負ける。
  • ドイツはラトビアに勝ち、イタリアに負ける。

それだけではありません。なんと、6位と8位と9位の3チームの間にも三竦みの関係になっています。

ドイツ エストニア チェコ
ドイツ ×
エストニア ×
チェコ ×
  • ドイツはエストニアに勝ち、チェコに負ける。
  • エストニアはチェコに勝ち、ドイツに負ける。
  • チェコはドイツに勝ち、エストニアに負ける。

極め付けは、この対戦成績には、三竦みの関係だけではなかったのです。7位のトルコに注目すると

スコットランド 韓国 日本 ラトビア イタリア ドイツ エストニア チェコ
トルコ × × × × ×
  • 上位3チーム全てに勝ち、下位5チームに全て負ける。

という、まさにジャイアントキラーっぷりを発揮しています。この対戦成績を見て、もしカーリング女子北京オリンピック最終予選が、敗戦即敗退のトーナメント方式だったら...

  • 今回優勝したスコットランドのトーナメントでの優勝確率はどれくらいだろう?
  • 我ら日本代表、ロコ・ソラーレの優勝するトーナメント表はどのようになるのだろう?
  • ジャイアントキラー3勝5敗の、トルコの優勝確率とそのトーナメント表は?

と色々試してみたくなってきました。

9チームで不戦勝のあるトーナメント表を作成する

ここで、参加チームは9チームです。4、8、16のような2の冪乗では無いので、必ず不戦勝が発生します。
今回は、準々決勝に進むための1回戦1試合2チーム、準々決勝から出場する7チームのトーナメント表を作成します。
上半分の5チームのトーナメントを「Xの山」下半分の4チームのトーナメントを「Yの山」とします。

            ┏━ A
          ┏━┫
          ┃ ┗━ B
        ┏━┫
        ┃ ┗━━━ C
  ┏━ X ━┫
  ┃     ┃ ┏━━━ D
  ┃     ┗━┫
  ┃       ┗━━━ E
 ━┫
  ┃       ┏━━━ F
  ┃     ┏━┫
  ┃     ┃ ┗━━━ G
  ┗━ Y ━┫
        ┃ ┏━━━ H
        ┗━┫
          ┗━━━ I

9チームのトーナメント表は全部で何通りあるか?

野球のような先攻・後攻の区別のない、8チームのトーナメント表は全部で、315通りになります。

    { (  8C2   ×      6C2      ×   4C2  ) ÷ 4! }×{ 4C2  ÷  2! }=315
7(準々決勝第1試合)×5(準々決勝第2試合)×3(準々決勝第3試合)×3(準決勝第1試合)=315通り

しかし、9チームの場合、全部で何通りあるか、上記のような計算式が思いつかないので、全部で何通りあるかのスクリプトを作成することにしました。

免責事項

その前に、このスクリプトの実行は自己責任でお願いします。本スクリプトを実行することにより生じるいかなる問題に関しましても、筆者は一切責任を負いません。予めご了承ください。

9チームのトーナメントの組合せのスクリプトをPythonで作成する

9チームのトーナメントの組合せを全て洗い出すスクリプトを下記に掲載します。Brute force的な片っ端から探索しているスクリプトでツッコミどころ満載ですが、ご愛嬌ということで、目を瞑ってください。

# -*- coding: utf-8 -*-

import itertools

# チーム名の登録
TEAMS = ('スコットランド', '韓国', '日本', 'ラトビア', 'イタリア', 'ドイツ', 'トルコ', 'エストニア', 'チェコ')
# チームの総数
ALL = len(TEAMS)

# 総当たり対戦成績表
table = {'01': '1', '02': '0', '03': '0', '04': '0', '05': '0', '06': '6', '07': '0', '08': '0',
        '10': '1', '12': '2', '13': '1', '14': '1', '15': '1', '16': '6', '17': '1', '18': '1',
        '20': '0', '21': '2', '23': '2', '24': '2', '25': '2', '26': '6', '27': '2', '28': '2',
        '30': '0', '31': '1', '32': '2', '34': '3', '35': '5', '36': '3', '37': '3', '38': '3',
        '40': '0', '41': '1', '42': '2', '43': '3', '45': '4', '46': '4', '47': '4', '48': '4',
        '50': '0', '51': '1', '52': '2', '53': '5', '54': '4', '56': '5', '57': '5', '58': '8',
        '60': '6', '61': '6', '62': '6', '63': '3', '64': '4', '65': '5', '67': '7', '68': '8',
        '70': '0', '71': '1', '72': '2', '73': '3', '74': '4', '75': '5', '76': '7', '78': '7',
        '80': '0', '81': '1', '82': '2', '83': '3', '84': '4', '85': '8', '86': '8', '87': '7'}

# 9チームの順列をタプルとして生成する
PERMUTATION = tuple(itertools.permutations([str(n) for n in range(ALL)], ALL))
# トーナメントの組合せを格納するリスト
combination = []
# 全チームの優勝回数を格納するリスト
wins = [0] * ALL

# 結果をテキストファイル'results.txt'に出力する
with open('results.txt', 'w') as fw:
    # 全ての順列を探索する
    for seq in PERMUTATION:
        # 先攻・後攻は区別しないので、先攻↔︎後攻が入れ替わった組合せは同一と見做し
        # その後の処理をスキップする。全部で31通り
        if (seq[0], seq[1], seq[2], seq[3], seq[4], seq[5], seq[6], seq[8], seq[7]) in combination:
            pass
        elif (seq[0], seq[1], seq[2], seq[3], seq[4], seq[6], seq[5], seq[7], seq[8]) in combination:
            pass
        elif (seq[0], seq[1], seq[2], seq[3], seq[4], seq[6], seq[5], seq[8], seq[7]) in combination:
            pass
        elif (seq[0], seq[1], seq[2], seq[4], seq[3], seq[5], seq[6], seq[7], seq[8]) in combination:
            pass
        elif (seq[0], seq[1], seq[2], seq[4], seq[3], seq[5], seq[6], seq[8], seq[7]) in combination:
            pass
        elif (seq[0], seq[1], seq[2], seq[4], seq[3], seq[6], seq[5], seq[7], seq[8]) in combination:
            pass
        elif (seq[0], seq[1], seq[2], seq[4], seq[3], seq[6], seq[5], seq[8], seq[7]) in combination:
            pass
        elif (seq[1], seq[0], seq[2], seq[3], seq[4], seq[5], seq[6], seq[7], seq[8]) in combination:
            pass
        elif (seq[1], seq[0], seq[2], seq[3], seq[4], seq[5], seq[6], seq[8], seq[7]) in combination:
            pass
        elif (seq[1], seq[0], seq[2], seq[3], seq[4], seq[6], seq[5], seq[7], seq[8]) in combination:
            pass
        elif (seq[1], seq[0], seq[2], seq[3], seq[4], seq[6], seq[5], seq[8], seq[7]) in combination:
            pass
        elif (seq[1], seq[0], seq[2], seq[4], seq[3], seq[5], seq[6], seq[7], seq[8]) in combination:
            pass
        elif (seq[1], seq[0], seq[2], seq[4], seq[3], seq[5], seq[6], seq[8], seq[7]) in combination:
            pass
        elif (seq[1], seq[0], seq[2], seq[4], seq[3], seq[6], seq[5], seq[7], seq[8]) in combination:
            pass
        elif (seq[1], seq[0], seq[2], seq[4], seq[3], seq[6], seq[5], seq[8], seq[7]) in combination:
            pass
        elif (seq[0], seq[1], seq[2], seq[3], seq[4], seq[7], seq[8], seq[5], seq[6]) in combination:
            pass
        elif (seq[0], seq[1], seq[2], seq[3], seq[4], seq[8], seq[7], seq[5], seq[6]) in combination:
            pass
        elif (seq[0], seq[1], seq[2], seq[3], seq[4], seq[7], seq[8], seq[6], seq[5]) in combination:
            pass
        elif (seq[0], seq[1], seq[2], seq[3], seq[4], seq[8], seq[7], seq[6], seq[5]) in combination:
            pass
        elif (seq[0], seq[1], seq[2], seq[4], seq[3], seq[7], seq[8], seq[5], seq[6]) in combination:
            pass
        elif (seq[0], seq[1], seq[2], seq[4], seq[3], seq[8], seq[7], seq[5], seq[6]) in combination:
            pass
        elif (seq[0], seq[1], seq[2], seq[4], seq[3], seq[7], seq[8], seq[6], seq[5]) in combination:
            pass
        elif (seq[0], seq[1], seq[2], seq[4], seq[3], seq[8], seq[7], seq[6], seq[5]) in combination:
            pass
        elif (seq[1], seq[0], seq[2], seq[3], seq[4], seq[7], seq[8], seq[5], seq[6]) in combination:
            pass
        elif (seq[1], seq[0], seq[2], seq[3], seq[4], seq[8], seq[7], seq[5], seq[6]) in combination:
            pass
        elif (seq[1], seq[0], seq[2], seq[3], seq[4], seq[7], seq[8], seq[6], seq[5]) in combination:
            pass
        elif (seq[1], seq[0], seq[2], seq[3], seq[4], seq[8], seq[7], seq[6], seq[5]) in combination:
            pass
        elif (seq[1], seq[0], seq[2], seq[4], seq[3], seq[7], seq[8], seq[5], seq[6]) in combination:
            pass
        elif (seq[1], seq[0], seq[2], seq[4], seq[3], seq[8], seq[7], seq[5], seq[6]) in combination:
            pass
        elif (seq[1], seq[0], seq[2], seq[4], seq[3], seq[7], seq[8], seq[6], seq[5]) in combination:
            pass
        elif (seq[1], seq[0], seq[2], seq[4], seq[3], seq[8], seq[7], seq[6], seq[5]) in combination:
            pass
        else:
            # はじめて出現した組合せをリストに格納する
            combination.append(seq)
            # 準々決勝に進出する1チームを決める
            quaterfinal = table[f'{seq[0]}{seq[1]}']
            # 準決勝に進出する4チームをリストに格納する
            semifinals = []
            semifinals.append(table[f'{quaterfinal}{seq[2]}'])
            semifinals.append(table[f'{seq[3]}{seq[4]}'])
            semifinals.append(table[f'{seq[5]}{seq[6]}'])
            semifinals.append(table[f'{seq[7]}{seq[8]}'])
            # 決勝に進出する2チームをリストに格納する
            finals = []
            finals.append(table[f'{semifinals[0]}{semifinals[1]}'])
            finals.append(table[f'{semifinals[2]}{semifinals[3]}'])
            # 優勝チームを決定する
            win = int(table[f'{finals[0]}{finals[1]}'])
            # 優勝チームの優勝回数を +1 増やす
            wins[win] += 1
            # 優勝チームとその時のトーナメント表を出力する
            print(f'case {len(combination)} 優勝: {TEAMS[win]}')
            fw.write(f'case {len(combination)} 優勝: {TEAMS[win]} [')
            for i, team in enumerate(seq):
                if i < ALL - 1:
                    fw.write(f'{TEAMS[int(team)]}, ')
                else:
                    fw.write(f'{TEAMS[int(team)]}]\n')
    
    # 確認用、順列の総数を表示する
    print(f'順列: {len(PERMUTATION)}')
    fw.write(f'順列: {len(PERMUTATION)}\n')
    # トーナメントの組合せの総数を表示する
    print(f'組合せ: {len(combination)}')
    fw.write(f'組合せ: {len(combination)}\n')
    
    # 全9チームが優勝した回数を表示する
    for i, team in enumerate(TEAMS):
        print(f'{team}: {wins[i]}')
        fw.write(f'{team}: {wins[i]}\n')

スクリプトの説明

itertoolsはPythonの標準モジュールなので、インストール不要です。便利ですね。ここで、importしていますが、全9チームのトーナメントの順列を作成するために使用します。

import itertools

カーリング女子最終予選に出場した9チームを登録しています。

# チーム名の登録
TEAMS = ('スコットランド', '韓国', '日本', 'ラトビア', 'イタリア', 'ドイツ', 'トルコ', 'エストニア', 'チェコ')
# チームの総数
ALL = len(TEAMS)

総当たり対戦成績表になります。{'0': 'スコットランド', '1': '韓国', ... '8': 'チェコ'}というルールで登録しています。
総当たり対戦成績表の見方は、例えば、{'01': '1'}の場合、スコットランドと韓国が対戦して、韓国が勝利する。という意味になります。
他にも、{'37': '3'}であれば、ラトビアとエストニアが対戦して、ラトビアが勝利する。といった感じです。

# 総当たり対戦成績表
table = {'01': '1', '02': '0', '03': '0', '04': '0', '05': '0', '06': '6', '07': '0', '08': '0',
        '10': '1', '12': '2', '13': '1', '14': '1', '15': '1', '16': '6', '17': '1', '18': '1',
        '20': '0', '21': '2', '23': '2', '24': '2', '25': '2', '26': '6', '27': '2', '28': '2',
        '30': '0', '31': '1', '32': '2', '34': '3', '35': '5', '36': '3', '37': '3', '38': '3',
        '40': '0', '41': '1', '42': '2', '43': '3', '45': '4', '46': '4', '47': '4', '48': '4',
        '50': '0', '51': '1', '52': '2', '53': '5', '54': '4', '56': '5', '57': '5', '58': '8',
        '60': '6', '61': '6', '62': '6', '63': '3', '64': '4', '65': '5', '67': '7', '68': '8',
        '70': '0', '71': '1', '72': '2', '73': '3', '74': '4', '75': '5', '76': '7', '78': '7',
        '80': '0', '81': '1', '82': '2', '83': '3', '84': '4', '85': '8', '86': '8', '87': '7'}

[0, 1, ... 8]のレンジを、['0', '1', ... '8']のリストに変換して、そのリストの全ての順列を、itertools.permutations()を使って生成しています。生成された全ての順列をタプルに変換し、PERMUTATIONに格納しています。
combinationは、トーナメントの組合せを格納するリストです。組合せの重複判定に使用しています。
winsは、全チームの優勝回数を格納するリストです。優勝回数の結果表示に使用しています。

# 9チームの順列をタプルとして生成する
PERMUTATION = tuple(itertools.permutations([str(n) for n in range(ALL)], ALL))
# トーナメントの組合せを格納するリスト
combination = []
# 全チームの優勝回数を格納するリスト
wins = [0] * ALL

結果をテキストファイル'results.txt'として出力しています。

# 結果をテキストファイル'results.txt'に出力する
with open('results.txt', 'w') as fw:

全9チームの順列をfor文を使用して探索しています。

# 全ての順列を探索する
for seq in PERMUTATION:

ここが肝となりますが、全順列を探索している最中に、以前探索した組合せと先攻↔︎後攻が入れ替わった組合せは同一と見做し、その後の優勝チームを決定する処理をスキップしています。先攻↔︎後攻が入れ替わった組合せは全部で31通りあります。

# 先攻・後攻は区別しないので、先攻↔︎後攻が入れ替わった組合せは同一と見做し
# その後の処理をスキップする。全部で31通り
if (seq[0], seq[1], seq[2], seq[3], seq[4], seq[5], seq[6], seq[8], seq[7]) in combination:
    pass
elif (seq[0], seq[1], seq[2], seq[3], seq[4], seq[6], seq[5], seq[7], seq[8]) in combination:
    pass
elif (seq[0], seq[1], seq[2], seq[3], seq[4], seq[6], seq[5], seq[8], seq[7]) in combination:
    pass
elif (seq[0], seq[1], seq[2], seq[4], seq[3], seq[5], seq[6], seq[7], seq[8]) in combination:
    pass
elif (seq[0], seq[1], seq[2], seq[4], seq[3], seq[5], seq[6], seq[8], seq[7]) in combination:
    pass
elif (seq[0], seq[1], seq[2], seq[4], seq[3], seq[6], seq[5], seq[7], seq[8]) in combination:
    pass
elif (seq[0], seq[1], seq[2], seq[4], seq[3], seq[6], seq[5], seq[8], seq[7]) in combination:
    pass
elif (seq[1], seq[0], seq[2], seq[3], seq[4], seq[5], seq[6], seq[7], seq[8]) in combination:
    pass
elif (seq[1], seq[0], seq[2], seq[3], seq[4], seq[5], seq[6], seq[8], seq[7]) in combination:
    pass
elif (seq[1], seq[0], seq[2], seq[3], seq[4], seq[6], seq[5], seq[7], seq[8]) in combination:
    pass
elif (seq[1], seq[0], seq[2], seq[3], seq[4], seq[6], seq[5], seq[8], seq[7]) in combination:
    pass
elif (seq[1], seq[0], seq[2], seq[4], seq[3], seq[5], seq[6], seq[7], seq[8]) in combination:
    pass
elif (seq[1], seq[0], seq[2], seq[4], seq[3], seq[5], seq[6], seq[8], seq[7]) in combination:
    pass
elif (seq[1], seq[0], seq[2], seq[4], seq[3], seq[6], seq[5], seq[7], seq[8]) in combination:
    pass
elif (seq[1], seq[0], seq[2], seq[4], seq[3], seq[6], seq[5], seq[8], seq[7]) in combination:
    pass
elif (seq[0], seq[1], seq[2], seq[3], seq[4], seq[7], seq[8], seq[5], seq[6]) in combination:
    pass
elif (seq[0], seq[1], seq[2], seq[3], seq[4], seq[8], seq[7], seq[5], seq[6]) in combination:
    pass
elif (seq[0], seq[1], seq[2], seq[3], seq[4], seq[7], seq[8], seq[6], seq[5]) in combination:
    pass
elif (seq[0], seq[1], seq[2], seq[3], seq[4], seq[8], seq[7], seq[6], seq[5]) in combination:
    pass
elif (seq[0], seq[1], seq[2], seq[4], seq[3], seq[7], seq[8], seq[5], seq[6]) in combination:
    pass
elif (seq[0], seq[1], seq[2], seq[4], seq[3], seq[8], seq[7], seq[5], seq[6]) in combination:
    pass
elif (seq[0], seq[1], seq[2], seq[4], seq[3], seq[7], seq[8], seq[6], seq[5]) in combination:
    pass
elif (seq[0], seq[1], seq[2], seq[4], seq[3], seq[8], seq[7], seq[6], seq[5]) in combination:
    pass
elif (seq[1], seq[0], seq[2], seq[3], seq[4], seq[7], seq[8], seq[5], seq[6]) in combination:
    pass
elif (seq[1], seq[0], seq[2], seq[3], seq[4], seq[8], seq[7], seq[5], seq[6]) in combination:
    pass
elif (seq[1], seq[0], seq[2], seq[3], seq[4], seq[7], seq[8], seq[6], seq[5]) in combination:
    pass
elif (seq[1], seq[0], seq[2], seq[3], seq[4], seq[8], seq[7], seq[6], seq[5]) in combination:
    pass
elif (seq[1], seq[0], seq[2], seq[4], seq[3], seq[7], seq[8], seq[5], seq[6]) in combination:
    pass
elif (seq[1], seq[0], seq[2], seq[4], seq[3], seq[8], seq[7], seq[5], seq[6]) in combination:
    pass
elif (seq[1], seq[0], seq[2], seq[4], seq[3], seq[7], seq[8], seq[6], seq[5]) in combination:
    pass
elif (seq[1], seq[0], seq[2], seq[4], seq[3], seq[8], seq[7], seq[6], seq[5]) in combination:
    pass

例えば、下記の1回戦、1試合の先攻↔︎後攻が入れ替わった組合せは、同一トーナメントと見做します。

A ━┓                         ┏━ B
   ┣━┓                     ┏━┫
B ━┛ ┃                     ┃ ┗━ A
     ┣━┓                 ┏━┫
C ━━━┛ ┃                 ┃ ┗━━━ C
       ┣━ X ━┓     ┏━ X ━┫
D ━━━┓ ┃     ┃     ┃     ┃ ┏━━━ D
     ┣━┛     ┃     ┃     ┗━┫
E ━━━┛       ┃     ┃       ┗━━━ E
             ┣━   ━┫
F ━━━┓       ┃     ┃       ┏━━━ F
     ┣━┓     ┃     ┃     ┏━┫
G ━━━┛ ┃     ┃     ┃     ┃ ┗━━━ G
       ┣━ Y ━┛     ┗━ Y ━┫
H ━━━┓ ┃                 ┃ ┏━━━ H
     ┣━┛                 ┗━┫
I ━━━┛                     ┗━━━ I

1回戦、準々決勝の先攻↔︎後攻が全て入れ替わった組合せも、同一トーナメントと見做します。

A ━┓                         ┏━ B
   ┣━┓                     ┏━┫
B ━┛ ┃                     ┃ ┗━ A
     ┣━┓                 ┏━┫
C ━━━┛ ┃                 ┃ ┗━━━ C
       ┣━ X ━┓     ┏━ X ━┫
D ━━━┓ ┃     ┃     ┃     ┃ ┏━━━ E
     ┣━┛     ┃     ┃     ┗━┫
E ━━━┛       ┃     ┃       ┗━━━ D
             ┣━   ━┫
F ━━━┓       ┃     ┃       ┏━━━ G
     ┣━┓     ┃     ┃     ┏━┫
G ━━━┛ ┃     ┃     ┃     ┃ ┗━━━ F
       ┣━ Y ━┛     ┗━ Y ━┫
H ━━━┓ ┃                 ┃ ┏━━━ I
     ┣━┛                 ┗━┫
I ━━━┛                     ┗━━━ H

Yの山の準決勝の先攻↔︎後攻が入れ替わった組合せも、同一トーナメントと見做します。

A ━┓                         ┏━ A
   ┣━┓                     ┏━┫
B ━┛ ┃                     ┃ ┗━ B
     ┣━┓                 ┏━┫
C ━━━┛ ┃                 ┃ ┗━━━ C
       ┣━ X ━┓     ┏━ X ━┫
D ━━━┓ ┃     ┃     ┃     ┃ ┏━━━ D
     ┣━┛     ┃     ┃     ┗━┫
E ━━━┛       ┃     ┃       ┗━━━ E
             ┣━   ━┫
F ━━━┓       ┃     ┃       ┏━━━ H
     ┣━┓     ┃     ┃     ┏━┫
G ━━━┛ ┃     ┃     ┃     ┃ ┗━━━ I
       ┣━ Y ━┛     ┗━ Y ━┫
H ━━━┓ ┃                 ┃ ┏━━━ F
     ┣━┛                 ┗━┫
I ━━━┛                     ┗━━━ G

しかし、Xの山の準決勝の先攻↔︎後攻が入れ替わった組合せは、同一トーナメントと見做しません。なぜなら、この2つのトーナメントでは優勝チームが変わってくるからです。

A ━┓                         ┏━ D
   ┣━┓                     ┏━┫
B ━┛ ┃                     ┃ ┗━ E
     ┣━┓                 ┏━┫
C ━━━┛ ┃                 ┃ ┗━━━ C
       ┣━ X ━┓     ┏━ X ━┫
D ━━━┓ ┃     ┃     ┃     ┃ ┏━━━ A
     ┣━┛     ┃     ┃     ┗━┫
E ━━━┛       ┃     ┃       ┗━━━ B
             ┣━   ━┫
F ━━━┓       ┃     ┃       ┏━━━ F
     ┣━┓     ┃     ┃     ┏━┫
G ━━━┛ ┃     ┃     ┃     ┃ ┗━━━ G
       ┣━ Y ━┛     ┗━ Y ━┫
H ━━━┓ ┃                 ┃ ┏━━━ H
     ┣━┛                 ┗━┫
I ━━━┛                     ┗━━━ I

今まで格納した組合せcombinationに無かった組合せが出現したら、combinationのリストに追加します。

# はじめて出現した組合せをリストに格納する
combination.append(seq)

1回戦1試合、準々決勝に進出する1チームを決定します。

# 準々決勝に進出する1チームを決める
quaterfinal = table[f'{seq[0]}{seq[1]}']

1回戦を勝ち上がってきた1チームと、準々決勝から出場する7チームで、準決勝に進出する4チームを決定します。

# 準決勝に進出する4チームをリストに格納する
semifinals = []
semifinals.append(table[f'{quaterfinal}{seq[2]}'])
semifinals.append(table[f'{seq[3]}{seq[4]}'])
semifinals.append(table[f'{seq[5]}{seq[6]}'])
semifinals.append(table[f'{seq[7]}{seq[8]}'])

準決勝に進出した4チームから、決勝に進出する2チームを決定します。

# 決勝に進出する2チームをリストに格納する
finals = []
finals.append(table[f'{semifinals[0]}{semifinals[1]}'])
finals.append(table[f'{semifinals[2]}{semifinals[3]}'])

決勝に進出した2チームから、優勝チームを決定します。そして、優勝回数を後で確認するため、優勝チームの優勝回数を +1 増やしています。

# 優勝チームを決定する
win = int(table[f'{finals[0]}{finals[1]}'])
# 優勝チームの優勝回数を +1 増やす
wins[win] += 1

優勝チームとその時のトーナメント表を、'results.txt'に出力しています。

# 優勝チームとその時のトーナメント表を出力する
print(f'case {len(combination)} 優勝: {TEAMS[win]}')
fw.write(f'case {len(combination)} 優勝: {TEAMS[win]} [')
for i, team in enumerate(seq):
    if i < ALL - 1:
        fw.write(f'{TEAMS[int(team)]}, ')
    else:
        fw.write(f'{TEAMS[int(team)]}]\n')

確認用です。PERMUTATIONをカウントし、トーナメントの順列の総数、9!=362880であることを確認しています。
そして、combinationをカウントし、9チームのトーナメントの組合せの総数を出力しています。

# 確認用、順列の総数を表示する
print(f'順列: {len(PERMUTATION)}')
fw.write(f'順列: {len(PERMUTATION)}\n')
# トーナメントの組合せの総数を表示する
print(f'組合せ: {len(combination)}')
fw.write(f'組合せ: {len(combination)}\n')

最後に全9チームの優勝回数を出力しています。

# 全9チームが優勝した回数を表示する
for i, team in enumerate(TEAMS):
    print(f'{team}: {wins[i]}')
    fw.write(f'{team}: {wins[i]}\n')

動作確認環境

• MacOS 12.0.1
• Python 3.8.9

スクリプトの実行結果

私の環境では、このスクリプトが完了するまで、22分かかりました。環境によって、もっと時間がかかるかもしれません。
実行結果は、下記の通りになりました。9チームの順列の総数は、9!=362880で期待通りの結果が返ってきています。

順列: 362880
組合せ: 11340
スコットランド: 3045
韓国: 3045
日本: 3045
ラトビア: 195
イタリア: 132
ドイツ: 78
トルコ: 1800
エストニア: 0
チェコ: 0

9チームのトーナメントの組合せ総数は、11340通りとなりました。8チームのトーナメントの組合せの総数は315通りなので、単純に割り算すると、11340÷315=36倍になります。この結果から、9チームのトーナメントの組合せ総数は、下記のような式が成り立ちそうです。

 { (   9C2   ×      8C2      ×      6C2      ×   4C2  ) ÷ 4! }×{ 4C2  ÷  2! }=11340
36(1回戦1試合)×7(準々決勝第1試合)×5(準々決勝第2試合)×3(準々決勝第3試合)×3(準決勝第1試合)=11340通り

9チームのトーナメント戦の考察

9チームの優勝回数を考察すると、以下のようなことが言えます。

  • 総当たり対戦成績の結果、6勝2敗で並んでいる、上位3チームの優勝回数は、全て3045回である。
  • 4勝5敗で並んでいる、ラトビアの優勝回数は195回に対し、イタリアは132回である。
  • 3勝6敗で並んでいる、ドイツの優勝回数は78回だけなのに対し、トルコは1800回も優勝している。
  • 2勝しかしていない、エストニア、チェコは優勝できない。

6勝2敗で並んでいる、上位3チームは、お互いの直接対決が3竦みの状態になっている。残るもう1敗は、全て同じ相手トルコである。
ラトビアとイタリアの優勝回数に差があるのは、両者の直接対決で、ラトビアが勝利している。ラトビアはドイツに負けているが、下位のチームに負けるより直接対決で勝利する方が優勝回数を伸ばせそうだ。
ドイツとトルコの優勝回数に大きな開きがある。両者の直接対決では、ドイツが勝利している。しかし、ドイツは上位3チームに全く勝てていないのに対し、トルコは上位3チームに全て勝っている。優勝回数を伸ばすためには、より上位のチームに勝利することが鍵となりそうだ。
エストニアとチェコが優勝できないのは、トーナメント戦で優勝するためには最低3回勝たないといけないが、2勝しかしていないので、優勝できない。当然と言えば当然の結果である。

上位3チームの優勝確率は、3045÷11340=26.85%の確率で優勝する。
上位3チームの優勝確率の合計は80.56%で、上位3チームで8割以上を占めている。
ラトビアの優勝確率は、195÷11340=1.72%で、イタリアの優勝確率は132÷11340=1.16%である。
ドイツの優勝確率は、78÷11340=0.68%と1%にも満たないのに対し、トルコの優勝確率は1800÷11340=15.87%と、総当たり対戦成績では上位3チームの6勝の半分、3勝しかしていないのに、トーナメント戦での優勝確率は、上位3チームの26.85%の半分以上、15.87%叩き出している。

上記の考察結果から、敗戦即敗退のトーナメント方式で、優勝する秘訣は

  • 8チームのトーナメント戦であれば、最低3勝、16チームのトーナメント戦であれば、最低4勝は必要である。
  • 少ない勝利数で優勝するためには、より上位のチームに勝つことが重要である。なぜなら、上位のチームは準決勝、決勝まで勝ち進む確率が高く、下位チームより対戦する確率が高くなるからである。

もう少し掘り下げて、優勝した時のトーナメント表から優勝できる条件を分析する

まず、スコットランドを見てみよう。スコットランドにとっての天敵は、韓国とトルコである。優勝するためには、初戦で韓国とトルコ戦を回避しつつ、準決勝、決勝までに、韓国とトルコが敗退しているのが条件である。

スコットランドが優勝するトーナメント表の例

            ┏━ ドイツ
          ┏━┫
          ┃ ┗━ チェコ
        ┏━┫
        ┃ ┗━━━ 韓国
  ┏━ X ━┫
  ┃     ┃ ┏━━━ 日本
  ┃     ┗━┫
  ┃       ┗━━━ イタリア
 ━┫
  ┃       ┏━━━ ラトビア
  ┃     ┏━┫
  ┃     ┃ ┗━━━ トルコ
  ┗━ Y ━┫
        ┃ ┏━━━ エストニア
        ┗━┫
          ┗━━━ スコットランド

韓国も、スコットランドと同様、韓国にとっての天敵は、日本とトルコである。優勝するためには、初戦で日本とトルコ戦を回避しつつ、準決勝、決勝までに、日本とトルコが敗退しているのが条件である。

韓国が優勝するトーナメント表の例

            ┏━ ドイツ
          ┏━┫
          ┃ ┗━ チェコ
        ┏━┫
        ┃ ┗━━━ 韓国
  ┏━ X ━┫
  ┃     ┃ ┏━━━ トルコ
  ┃     ┗━┫
  ┃       ┗━━━ イタリア
 ━┫
  ┃       ┏━━━ ラトビア
  ┃     ┏━┫
  ┃     ┃ ┗━━━ 日本
  ┗━ Y ━┫
        ┃ ┏━━━ エストニア
        ┗━┫
          ┗━━━ スコットランド

日本も、韓国と同様、日本にとっての天敵は、スコットランドとトルコである。優勝するためには、初戦でスコットランドとトルコ戦を回避しつつ、準決勝、決勝までに、スコットランドとトルコが敗退しているのが条件である。

日本が優勝するトーナメント表の例

            ┏━ ドイツ
          ┏━┫
          ┃ ┗━ チェコ
        ┏━┫
        ┃ ┗━━━ トルコ
  ┏━ X ━┫
  ┃     ┃ ┏━━━ 日本
  ┃     ┗━┫
  ┃       ┗━━━ イタリア
 ━┫
  ┃       ┏━━━ ラトビア
  ┃     ┏━┫
  ┃     ┃ ┗━━━ 韓国
  ┗━ Y ━┫
        ┃ ┏━━━ エストニア
        ┗━┫
          ┗━━━ スコットランド

ラトビアが優勝するためには、ラトビアがXの山の場合、スコットランドと韓国と日本とトルコがYの山で対戦し、初戦でドイツと対戦せず、準決勝までにドイツが敗退し、決勝でトルコと対戦するのが条件である。

ラトビアが優勝するトーナメント表の例

            ┏━ ドイツ
          ┏━┫
          ┃ ┗━ チェコ
        ┏━┫
        ┃ ┗━━━ エストニア
  ┏━ X ━┫
  ┃     ┃ ┏━━━ ラトビア
  ┃     ┗━┫
  ┃       ┗━━━ イタリア
 ━┫
  ┃       ┏━━━ 韓国
  ┃     ┏━┫
  ┃     ┃ ┗━━━ トルコ
  ┗━ Y ━┫
        ┃ ┏━━━ 日本
        ┗━┫
          ┗━━━ スコットランド

イタリアが優勝するためには、イタリアがXの山の場合、スコットランドと韓国と日本とトルコがYの山で対戦し、初戦でラトビアと対戦せず、準決勝までにラトビアが敗退し、決勝でトルコと対戦するのが条件である。

イタリアが優勝するトーナメント表の例

            ┏━ ドイツ
          ┏━┫
          ┃ ┗━ ラトビア
        ┏━┫
        ┃ ┗━━━ エストニア
  ┏━ X ━┫
  ┃     ┃ ┏━━━ チェコ
  ┃     ┗━┫
  ┃       ┗━━━ イタリア
 ━┫
  ┃       ┏━━━ 韓国
  ┃     ┏━┫
  ┃     ┃ ┗━━━ トルコ
  ┗━ Y ━┫
        ┃ ┏━━━ 日本
        ┗━┫
          ┗━━━ スコットランド

ドイツが優勝するためには、ドイツは3勝しかしていないので、準々決勝が初戦となるトーナメントが必須条件である。ドイツがXの山の場合、スコットランドと韓国と日本とトルコがYの山で対戦し、準々決勝でエストニアと対戦し、準々決勝でイタリアを破ったラトビアと準決勝で対戦し、決勝でトルコと対戦するのが条件である。

ドイツが優勝するトーナメント表の例

            ┏━ イタリア
          ┏━┫
          ┃ ┗━ チェコ
        ┏━┫
        ┃ ┗━━━ ラトビア
  ┏━ X ━┫
  ┃     ┃ ┏━━━ エストニア
  ┃     ┗━┫
  ┃       ┗━━━ ドイツ
 ━┫
  ┃       ┏━━━ 韓国
  ┃     ┏━┫
  ┃     ┃ ┗━━━ トルコ
  ┗━ Y ━┫
        ┃ ┏━━━ 日本
        ┗━┫
          ┗━━━ スコットランド

最後にジャイアントキラー、トルコが優勝するためには、トルコも3勝しかしていないので、準々決勝が初戦となるトーナメントが必須条件である。トルコがYの山の場合、上位3チームの内2チームはYの山、かつ、残りの1チームはXの山、かつ、初戦で上位3チームと対戦することが条件である。

トルコが優勝するトーナメント表の例

            ┏━ イタリア
          ┏━┫
          ┃ ┗━ チェコ
        ┏━┫
        ┃ ┗━━━ ラトビア
  ┏━ X ━┫
  ┃     ┃ ┏━━━ 日本
  ┃     ┗━┫
  ┃       ┗━━━ ドイツ
 ━┫
  ┃       ┏━━━ 韓国
  ┃     ┏━┫
  ┃     ┃ ┗━━━ トルコ
  ┗━ Y ━┫
        ┃ ┏━━━ エストニア
        ┗━┫
          ┗━━━ スコットランド

因みに、もしカーリング男子・最終予選が、トーナメント方式だったらも試してみました

カーリング男子・最終予選の総当たり対戦成績表は、下記の通りです。

ノルウェー イタリア チェコ デンマーク フィンランド 日本 オランダ 韓国 ドイツ
ノルウェー
イタリア ×
チェコ × × ×
デンマーク × × × ×
フィンランド × × × × ×
日本 × × × × ×
オランダ × × × × ×
韓国 × × × × × ×
ドイツ × × × × × × ×

チーム名と総当たり対戦成績表を入れ替えてスクリプトの実行してみた結果は下記の通りです。
無敗の絶対王者、ノルウェーが全て優勝しています。敗戦即敗退のトーナメント方式ですから、無敗のノルウェーは、敗退することはありませんよね。当然と言えば当然の結果です。

順列: 362880
組合せ: 11340
ノルウェー: 11340
イタリア: 0
チェコ: 0
デンマーク: 0
フィンランド: 0
日本: 0
オランダ: 0
韓国: 0
ドイツ: 0

最後に

カーリング女子・最終予選の総当たり対戦成績表をみて、興味本位で、トーナメント方式に置き換えた場合の、優勝するチームをPythonスクリプトで作成してみました。今まで、不戦勝が入ったトーナメントの組合せ総数を計算する方法を知りませんでしたが、このスクリプトを作成して、不戦勝が入ったトーナメントの組合せを逆算することができました。
また、3チーム全てが勝つ可能性がある、三竦みというゲーム理論は、条件によって優勝するチームが変わってくるので、試合を面白くする要因となっていると再認識いたしました。
2022年北京オリンピック・カーリング競技では、総当たり対戦方式で、上位4チームが決勝トーナメントに進みます。今回のいきなりトーナメント方式は当てはなりませんが、カーリング女子、日本代表のロコ・ソラーレには、健闘してもらいたいものです。
最後まで、この記事を読んでいただき、ありがとうございました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?