最小のチェスゲームの圧縮


8

インスピレーション:

最小のチェス盤の圧縮に強く触発されて、私は類似しているが明らかに異なる競争をすることにしました。

tl; dr

Chess_Games.txtファイルを取得し、できるだけ元のファイルに展開できるように圧縮します。

目的:

チェスデータベース全体を開始位置から終了位置までエンコードおよびデコードするアルゴリズムを記述します

エンコーディングは、すべてのゲームですべての位置を決定できる必要があります。

  • すべての作品の場所
  • 誰の番ですか
  • プレイヤーが両サイドでキャッスルできるかどうか。
  • プレイヤーがen-passantを実行できるかどうか、実行できる場合、どのポーンを実行できますか?
  • 前職

さらに:

  • 各ゲームには、誰が勝ったか、どのように終了したか(没収、抽選、チェックメイト、ステイルメイトなど)も含める必要があります。

入出力:

次のプロパティを満たす2つのアルゴリズムCompress / Expandが必要です。

  • Compressは、チェス記法による一連の動きを介してゲームのファイルを取り込み、圧縮ファイルを出力します

  • Expandはその逆を行い、圧縮ファイルを取り込み、すべてのゲームが同じ順序で元のファイルを出力します

  • 正確性:Expand(Compress(file))=すべての適切な形式のファイルのファイル

正しく形成されていない、またはチェスのルールに違反しているゲームはすべて悪いと見なされます。すべての悪いゲームはスキップされるかもしれません。

sen表記を解析できなければなりません。いくつかの例については、chessgames.comおよびhttps://database.lichess.org/参照してください

私は最初から10000ゲームのファイルをコンパイルした「2017年5月」Chess_Games.txt

ファイルは次のようになります。

e4 d5 exd5 Nf6 d3 Qxd5 Nc3 Qf5 Be2 Bd7 g4 Qe6 g5 Nd5 Ne4 Bc6 Bg4 Qe5 f4 Qd4 Nf3 Qb6 Qe2 e6 Be3 Qa6 O-O Nd7 c4 Ne7 f5 Bxe4 dxe4 exf5 exf5 O-O-O f6 gxf6 gxf6 Nc6 Nd4 Nxd4 Bxd4 Bc5 Bxc5 Rhg8 Be7 Rde8 b4 Qxf6 Bxf6 Rxe2 h3 h5 Kh1 hxg4 Rg1 Nxf6 Rae1 Rxe1 Rxe1 gxh3 Kh2 Ng4+ Kxh3 Nf2+ Kh2 Ng4+ Kg3 f5 Kf4 Nh6 Re7 Rg4+ Ke5 Kd8 Kf6 Ng8+ Kf7 Nxe7 Kf8 f4 c5 f3 c6 f2 cxb7 Nc6 b8=Q+ Nxb8 Kf7 Kd7 0-1
d3 e6 e3 d5 Nf3 Nf6 Be2 Be7 O-O O-O Nc3 Nbd7 Ne1 c5 f3 e5 e4 d4 Nb1 b6 f4 exf4 Rxf4 Qc7 Rf3 Bd6 g3 Ng4 Rf1 Ndf6 Bxg4 Bxg4 Qd2 Rae8 Qf2 Re5 Bf4 Rh5 Bxd6 Qxd6 Nf3 c4 e5 Rxe5 Nxe5 Qxe5 Nd2 cxd3 cxd3 Be2 Rfe1 Qe3 Qxe3 dxe3 Ne4 Bxd3 Nxf6+ gxf6 Rxe3 Bg6 Rae1 Kg7 h4 h5 R1e2 Bf5 Rg2 Bg4 Re4 Kg6 Rge2 f5 Re5 f6 Re6 Rg8 Re7 Rg7 Re8 1-0
d4 d5 e4 dxe4 c4 Nf6 Qc2 Bf5 Nc3 Qxd4 Be3 Qe5 Nge2 e6 Ng3 Bb4 a3 Bxc3+ bxc3 Nc6 Be2 Na5 O-O Nxc4 Bd4 Qb5 Nxf5 exf5 Bxf6 gxf6 Rab1 Nxa3 Qa2 Qxb1 Rxb1 Nxb1 Qxb1 O-O-O Qb3 b6 Ba6+ Kb8 Qb5 Rhe8 Qc6 1-0
e3 c5 d3 d5 Nf3 Nc6 Be2 Nf6 O-O g6 h3 Bg7 Re1 O-O Nbd2 Re8 Nf1 e5 c3 b6 N3h2 Bb7 Qc2 Qc7 b3 Rac8 Bb2 a5 Rac1 a4 Qb1 axb3 axb3 Ba6 d4 Bxe2 Rxe2 exd4 cxd4 Qb8 dxc5 d4 Ree1 dxe3 Rxe3 Nd5 Rxe8+ Rxe8 Bxg7 Kxg7 Re1 Rxe1 Qxe1 bxc5 Qd1 Nd4 Ne3 Nf4 Neg4 Qxb3 Qe1 Qd5 Ne3 Qe6 Qf1 c4 Nhg4 Nde2+ Kh2 c3 g3 c2 gxf4 c1=Q Qxc1 Nxc1 Ng2 Qe4 Kg3 Nd3 f3 Qe2 Nh4 h5 Nh2 Qe1+ Kg2 Nf2 Nf5+ gxf5 Kg3 Qg1+ Kh4 Nxh3 Kxh5 1-0
e4 d5 exd5 Qxd5 Nc3 Qd8 d4 Bf5 Nf3 e6 Nh4 Bg6 Nxg6 hxg6 Be3 Bd6 Bc4 a6 Qf3 c6 O-O-O Nd7 Ne4 Qc7 Nxd6+ Qxd6 Bf4 Qb4 Bb3 Ngf6 Rhe1 O-O-O Bg3 a5 Qf4 Qb6 Re3 Rh5 Bc4 Rf5 Qd6 Ne8 Qa3 Qa7 Red3 b5 Bxb5 cxb5 Rc3+ Kb7 Qe7 b4 Rc5 Rxc5 dxc5 Qxc5 Qxd8 Ndf6 Qb8+ Ka6 Rd8 Qa7 Rxe8 Nxe8 Qxe8 Qc5 Qa8+ Kb5 Qb8+ Ka4 b3+ Ka3 Qf4 Qc3 Qe5 1-0
e4 d5 exd5 Qxd5 Nf3 Qd8 Nc3 e6 Bc4 Nf6 Bb3 Be7 a3 a6 O-O O-O h3 b6 d3 Bb7 Bg5 Nbd7 Ne4 h6 Bh4 Nxe4 Bxe7 Qxe7 dxe4 Bxe4 Re1 Bb7 Ba2 Nf6 b4 Rfd8 Qc1 Qd6 c4 Qc6 Qc2 Rd7 Rac1 Rad8 c5 bxc5 Qxc5 Qb5 Qxb5 axb5 Ne5 Rd2 Bb3 Rb2 Bd1 Rdd2 Re2 Rxe2 Bxe2 Rxe2 Nf3 Ra2 Rxc7 Bd5 Rc8+ Kh7 Ne5 Rxa3 Rf8 Ra1+ Kh2 h5 Rxf7 Kh6 Rf8 Kg5 g3 Kf5 Nd7 Ra2 Nxf6 gxf6 Rg8 Rxf2+ Kg1 Rg2+ Kf1 Rh2 g4+ hxg4 hxg4+ Ke5 Re8 Rh1+ Kf2 Rh2+ Kg3 Rg2+ Kh4 Rf2 Kg3 Rf4 g5 Re4 gxf6 Kxf6 Rf8+ Ke5 Rh8 Re3+ Kf2 Re4 Rh5+ Kd4 Rh6 Rf4+ Kg3 Re4 Rh8 Re3+ 0-1
...

得点:

物事を客観的にするために、勝者はhttps://database.lichess.org/でファイルをできるだけ小さくできるアルゴリズムです。対象データベースは「2017年5月」です。勝者は、適切に拡大する最小のファイルを持っている人です。

使用するファイルはChess_Games.txtで、これはhttps://database.lichess.org/の「2017年5月」データベースの最初の10000ゲームであり、すべてのヘッダー情報が削除されています。このファイルは、使用するベンチマークになります。SHA-256ハッシュの2,789,897バイトである必要があります(Pastebinはゲーム10000の後に最後の改行を削除する可能性があります)56b6d2fffc7644bcd126becd03d859a3cf6880fa01a47fa08d4d6a823a4866bc

素朴なソリューション:

一般的な圧縮アルゴリズムの使用:

  • zip:647,772バイト(76.841%)
  • 7z:652,813バイト(76.661%)
  • bz2:722,158バイト(74.182%)
  • xz:784,980バイト(71.936%)
  • rar:853,482バイト(69.487%)
  • gz:923,474バイト(66.985%)

後にスペースがあってはいけません http://
入れる

修正していただきありがとうございます。適切な評判がないため、このサイトでは複数のリンクを含む質問を投稿できません。だから私はスペースを追加し、誰かがそれを修正するか、後で修正できることを望みました。
エッジーな2017

2
いい質問だ!スコアの計算方法について詳しく教えてください。また、標準のチェス表記のどのバリエーションを期待して出力する必要がありますか?将来的には、サンドボックスを見つけるかもしれません役立つ :)
musicman523

1
回答の採点に使用するチェスのゲーム/表記を入手できますか?
wrymug 2017

1
ベンチマークファイルから注釈を取り除き、すべてをより明確にするために、混乱している参照ルールを削除しました。これらのサイトを参照として使用するように、構文解析に関するステートメントを変更しました。
エッジの効いた

回答:


3

Python、スコア= 426508バイト

圧縮関数:(m + 1)⋅log 2 (b + 1)

mは移動数、bは分岐係数です

試み1:

最小チェス盤の圧縮で質問に答えたので、ここに投稿するように修正しようとしています。

素朴に私はそれぞれの動きを12ビットでエンコードできると思った。4つのトリプレット形式(開始x、開始y、終了x、終了y)で、それぞれ3ビットである。

開始位置を想定し、白を最初にして、そこから駒を移動します。ボードは、(0、0)が白の左下隅になるように配置されています。

たとえばゲーム:

  e4    e5
 Nf3    f6
Nxe5  fxe5
...    ...

次のようにエンコードされます:

100001 100010 100110 100100
110000 101010 101110 101101
101010 100100 101101 100100
...

これにより、12 mビットのエンコードが行われます。が行われます。mは行われた移動の数です。

試み2:

前の試みの各動きは多くの違法な動きをエンコードしていることに気付きました。だから私は合法的な動きだけをエンコードすることにしました。次のように可能な移動を列挙します。各正方形に(0、0)→0、(1、0)→1、(x、y)→x + 8 yのように番号を付けます。タイルを繰り返し処理して、ピースがそこにあるかどうか、および移動できるかどうかを確認します。もしそうなら、それはリストに行くことができるポジションを追加します。移動したいリストインデックスを選択します。その数を、1と可能な移動の数で加重した移動の現在の合計に追加します。

上記の例:開始位置から移動できる最初の駒は、正方形1の騎士で、正方形16または18に移動できるので、それらをリストに追加します[(1,16),(1,18)]。次は、スクエア6の騎士です。動きを追加します。全体的に次のようになります。

[(1,16),(1,18),(6,21),(6,23),(8,16),(8,24),(9,17),(9,25),(10,18),(10,26),(11,19),(11,27),(12,20),(12,28),(13,21),(13,29),(14,22),(14,30),(15,23),(15,31)]

移動(12、28)が必要なため、20の可能な移動があるため、これをベース20で13としてエンコードします。

したがって、ゲーム番号g 0 = 13 を取得します

次に、黒に対して同じことを行いますが、移動のリストを取得するためにタイルを逆に番号付けします(簡単にするために、必須ではありません)。

[(1,16),(1,18),(6,21),(6,23),(8,16),(8,24),(9,17),(9,25),(10,18),(10,26),(11,19),(11,27),(12,20),(12,28),(13,21),(13,29),(14,22),(14,30),(15,23),(15,31)]

移動(11、27)が必要なので、20の可能な移動があるため、これを底20で11としてエンコードします。

したがって、ゲーム番号g 1 =(11⋅20)+ 13 = 233 を取得します

次に、白の次の動きのリストを取得します。

[(1,16),(1,18),(3,12),(3,21),(3,30),(3,39),(4,12),(5,12),(5,19),(5,26),(5,33),(5,40),(6,12),(6,21),(6,23),(8,16),(8,24),(9,17),(9,25),(10,18),(10,26),(11,19),(11,27)(13,21),(13,29),(14,22),(14,30),(15,23),(15,31)]

移動(6、21)が必要なので、29の可能な移動があるので、これをベース29で13としてエンコードします。

したがって、ゲーム番号g 2 =((13⋅20)+ 11)20 + 13 = 5433を取得します

次に、黒の次の移動のリストを取得します。 [(1,11),(1,16),(1,18),(2,11),(2,20),(2,29),(2,38),(2,47),(3,11),(4,11),(4,18),(4,25),(4,32),(6,21),(6,23),(8,16),(8,24),(9,17),(9,25),(10,18),(10,26),(12,20),(12,28),(13,21),(13,29),(14,22),(14,30),(15,23),(15,31)]

移動(10、18)が必要なため、29の可能な移動があるため、これをベース29で19としてエンコードします。

したがって、ゲーム番号g 3 =(((19⋅29 + 13)20)+ 11)20 + 13 = 225833を取得します

そして、残りのすべての動きについてこのプロセスを続けます。gは、関数g(x、y、z)= x y + z と考えることができます。したがって、g 0 = g(1、1、13)、g 1 = g(g(1、1、11)、20、13)、g 2 = g(g(g(1、1、13)、20、 11)、20、13)、g 3 = g(g(g(g(1、1、19)

ゲーム番号g 0をデコードするには、最初の位置から開始し、可能なすべての動きを列挙します。次に、g 1 = g 0 // lm 0 = g 0lを計算します。ここで、lは可能な移動の数、 '//'は整数除算演算子、 '%'はモジュラス演算子です。その保持する必要があり、G 0 = G 1 + M 0。次に、m 0の移動を行い、繰り返します。

上記の例から、g 0 = 225833の場合、g 1 = 225833 // 20 = 11291およびm 0 = 225833%20 = 13となります。次のg 2 = 11291 // 20 = 564およびm 1 = 11291%20 = 11.次に、g 3 = 11291 // 20 = 564およびm 2 = 11291%20 = 11.したがって、g 4 = 564 // 29 = 19 and_m_ 3 = 564%29 = 13.最後にg 5 = 19 // 29 = 0およびm 4 = 19%29 = 19。

では、このようにゲームをエンコードするために何ビットが使用されるのでしょうか。

簡単にするために、毎ターン35の移動があるとしましょう(https://en.wikipedia.org/wiki/Branching_factor)。最悪のシナリオでは、常に最大の34を選択します。取得する数は34です⋅ 35 m + 34⋅35 m-1 + 34⋅35 m-2 +⋯+ 34⋅35 + 34 = 35 m + 1 − 1ここで、_mは移動回数です。35 m + 1 − 1 をエンコードするには、約log 2 (35 m + 1 )ビットが必要です。これは、約(m + 1) ⋅log 2(35)= 5.129⋅(m + 1)です。

平均してm = 80(プレーヤーあたり40移動)なので、これをエンコードするには416ビットかかります。多くのゲームを記録する場合、各数値に必要なビット数がわからないため、ユニバーサルエンコーディングが必要になります。

最悪の場合、m = 11741の場合、エンコードに60229ビットかかります。

その他の注意事項:

なお、G = 0、有効なゲーム、いずれかを表すことができる最小二乗に最低平方移動にピース。

ゲームの特定の位置を参照したい場合は、インデックスをエンコードする必要があるかもしれません。これは手動で追加することができます。たとえば、インデックスをゲームに連結するか、または各ターンの最後の可能な移動として、追加の「終了」移動を追加します。これは、認めたプレーヤー、または引き分けに同意したプレーヤーを示す2行連続で説明できます。これは、ゲームがポジションに基づいてチェックメイトまたはステイルメイトで終了しなかった場合にのみ必要です。この場合、それは暗示されます。この場合、平均で必要なビット数は419になり、最悪の場合は60706になります。

what-ifシナリオでフォークを処理する方法は、ゲームをフォークまでエンコードしてから、初期位置ではなくフォークされた位置から始まる各ブランチをエンコードすることです。

実装:

コードの私のgithubリポジトリを見てくださいhttps : //github.com/edggy/ChessCompress


3

Python、スコア= 418581バイト

これは、非対称数値システムの全単射変形を使用します。全単射であるため、チェスゲームの有効なリストをファイルに圧縮して、同じリストに戻すだけでなく、ファイルをチェスゲームの有効なリストに拡張して、同じファイルに圧縮することもできます。ポルノコレクションを非表示にするのに最適です。

python-chessが必要です。python script.py compressまたはpython script.py expandで実行します。どちらも標準入力から読み取り、標準出力に書き込みます。

import chess
import sys

RESULTS = ['1-0\n', '1/2-1/2\n', '0-1\n', '*\n']
BITS = 24

def get_moves(board):
    if board.is_insufficient_material() or not board.legal_moves:
        return [board.result() + '\n']
    else:
        return RESULTS + sorted(
            board.legal_moves,
            key=lambda move: (move.from_square, move.to_square, move.promotion))

def read_bijective():
    buf = bytearray(getattr(sys.stdin, 'buffer', sys.stdin).read())
    carry = 0
    for i in range(len(buf)):
        carry += buf[i] + 1
        buf[i] = carry & 0xff
        carry >>= 8
    if carry:
        buf.append(carry)
    return buf

def write_bijective(buf):
    carry = 0
    for i in range(len(buf)):
        carry += buf[i] - 1
        buf[i] = carry & 0xff
        carry >>= 8
    while carry:
        carry = (carry << 8) + buf.pop() + 1
    getattr(sys.stdout, 'buffer', sys.stdout).write(buf)

def add_carry(buf, carry):
    for i in range(len(buf)):
        if carry == 0:
            break
        carry += buf[i]
        buf[i] = carry & 0xff
        carry >>= 8
    return carry

def do_compress():
    board = chess.Board()
    state = 0
    buf = bytearray()

    games = []
    for sans in sys.stdin:
        game = []
        for san in sans.split(' '):
            move = san if san in RESULTS else board.parse_san(san)
            moves = get_moves(board)
            game.append((len(moves), moves.index(move)))
            if move in RESULTS:
                board.reset()
            else:
                board.push(move)
        games.append(game)

    for game in reversed(games):
        for (n, i) in reversed(game):
            q = ((1 << BITS) - 1 - i) // n + 1
            while state >= q << 8:
                buf.append(state & 0xff)
                state >>= 8
            hi, j = divmod(state, q)
            lo = n * j + i
            state = hi << BITS | lo
        state += add_carry(buf, 1)

    while state:
        buf.append(state & 0xff)
        state >>= 8
    write_bijective(buf)

def do_expand():
    board = chess.Board()
    state = 0
    buf = read_bijective()

    while True:
        while buf and state < 1 << BITS:
            state = state << 8 | buf.pop()
        if state == 0:
            break
        state += add_carry(buf, -1)

        while True:
            moves = get_moves(board)
            while buf and state < 1 << BITS:
                state = state << 8 | buf.pop()
            n = len(moves)
            hi, lo = divmod(state, 1 << BITS)
            j, i = divmod(lo, n)
            q = ((1 << BITS) - 1 - i) // n + 1
            state = j + q * hi
            move = moves[i]
            if move in RESULTS:
                sys.stdout.write(move)
                board.reset()
                break
            else:
                sys.stdout.write(board.san(move).rstrip('+#') + ' ')
                board.push(move)

if __name__ == '__main__':
    {'compress': do_compress, 'expand': do_expand}[sys.argv[1]]()

1
Python 2.7.13を使用してUbuntu 17.04で検証し、各コマンドを個別に実行しました。
エッジーな2017

これは本当に素晴らしいです!ポルノチェスのゲームが実際にどのようになるかにも興味をそそられます。
アヌッシュ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.