You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何高效实现从大型PGN数据库提取随机大师棋局的函数?

问题描述

你需要实现函数从百万级的caissabase国际象棋PGN数据库中提取随机大师棋局,当前使用的两种方法存在明显问题:

  • 水塘抽样法(extract_random_game):遍历全部棋局耗时极久,且容易因单个坏棋局导致程序崩溃,调试困难。
  • 按索引提取法(extract_game_by_index):小索引提取正常,但提取大索引(如第1000000局)时需要从头遍历,耗时过长。
解决方案

1. 预生成棋局位置索引文件(推荐)

核心思路是先遍历一次PGN文件,记录每个棋局在文件中的起始偏移量,保存为索引文件。后续提取时直接通过偏移量跳转读取,无需从头遍历,大幅提升速度。

生成索引文件

import os
import chess.pgn
import pickle

def build_pgn_index(pgn_file, index_file):
    index = []
    with open(pgn_file, 'rb') as f:
        offset = 0
        while True:
            try:
                game = chess.pgn.read_game(f)
                if game is None:
                    break
                index.append(offset)
                offset = f.tell()
            except Exception as e:
                print(f"跳过损坏的棋局,错误信息: {e}")
                offset = f.tell()
                continue
    with open(index_file, 'wb') as idx_f:
        pickle.dump(index, idx_f)
    print(f"索引生成完成,共记录{len(index)}个棋局")

提取随机棋局

import random
import pickle
import chess.pgn

def extract_random_game_with_index(pgn_file, index_file, output_file):
    with open(index_file, 'rb') as idx_f:
        index = pickle.load(idx_f)
    
    if not index:
        print("索引文件中无有效棋局记录")
        return
    
    random_offset = random.choice(index)
    with open(pgn_file, 'rb') as f:
        f.seek(random_offset)
        try:
            game = chess.pgn.read_game(f)
            if game:
                with open(output_file, 'w') as out_f:
                    out_f.write(str(game))
                print(f"随机棋局已保存至: {output_file}")
            else:
                print("读取选中的棋局失败")
        except Exception as e:
            print(f"读取棋局出错: {e}")

按索引提取指定棋局

import pickle
import chess.pgn

def extract_game_by_index_with_index(pgn_file, index_file, game_index, output_file):
    with open(index_file, 'rb') as idx_f:
        index = pickle.load(idx_f)
    
    total_games = len(index)
    if game_index < 1 or game_index > total_games:
        print(f"索引超出范围,当前数据库共有{total_games}个棋局")
        return
    
    target_offset = index[game_index - 1]  # 索引文件为0-based,用户输入为1-based
    with open(pgn_file, 'rb') as f:
        f.seek(target_offset)
        try:
            game = chess.pgn.read_game(f)
            if game:
                with open(output_file, 'w') as out_f:
                    out_f.write(str(game))
                print(f"第{game_index}局已保存至: {output_file}")
            else:
                print("读取选中的棋局失败")
        except Exception as e:
            print(f"读取棋局出错: {e}")

2. 优化流式读取的稳定性

如果不想生成额外索引文件,可以通过异常捕获避免程序崩溃,同时优化读取逻辑:

import random
import chess.pgn

def extract_random_game_safe(pgn_file, output_file):
    random_game = None
    num_games = 0
    with open(pgn_file) as pgn:
        while True:
            try:
                game = chess.pgn.read_game(pgn)
                if game is None:
                    break
                num_games += 1
                if random.randint(1, num_games) == 1:
                    random_game = game
            except Exception as e:
                print(f"跳过损坏的棋局: {e}")
                continue
    
    if num_games == 0:
        print("PGN文件中未找到任何棋局")
        return
    
    with open(output_file, 'w') as new_pgn:
        new_pgn.write(str(random_game))
    print("随机棋局已保存至:", output_file)

3. 导入数据库管理(适合频繁查询场景)

将PGN导入SQLite或PostgreSQL,利用数据库的索引和查询能力快速获取棋局:

导入PGN到SQLite

import sqlite3
import chess.pgn

def import_pgn_to_sqlite(pgn_file, db_file):
    conn = sqlite3.connect(db_file)
    cursor = conn.cursor()
    cursor.execute('''CREATE TABLE IF NOT EXISTS games
                      (id INTEGER PRIMARY KEY AUTOINCREMENT, pgn_text TEXT)''')
    
    with open(pgn_file) as pgn:
        count = 0
        while True:
            try:
                game = chess.pgn.read_game(pgn)
                if game is None:
                    break
                cursor.execute("INSERT INTO games (pgn_text) VALUES (?)", (str(game),))
                count += 1
                if count % 10000 == 0:
                    conn.commit()
                    print(f"已导入{count}个棋局")
            except Exception as e:
                print(f"跳过损坏的棋局: {e}")
                continue
        conn.commit()
    print(f"导入完成,共{count}个棋局")
    conn.close()

从数据库提取随机棋局

import sqlite3

def extract_random_game_from_db(db_file, output_file):
    conn = sqlite3.connect(db_file)
    cursor = conn.cursor()
    cursor.execute("SELECT pgn_text FROM games ORDER BY RANDOM() LIMIT 1")
    result = cursor.fetchone()
    
    if result:
        with open(output_file, 'w') as out_f:
            out_f.write(result[0])
        print(f"随机棋局已保存至: {output_file}")
    else:
        print("数据库中无有效棋局")
    conn.close()

方案对比

方案优点缺点
预生成索引文件提取速度极快,无需重复遍历原文件需要额外存储空间,首次生成索引耗时
流式优化无需额外文件,实现简单提取大索引棋局仍需从头遍历,速度慢
数据库管理支持复杂查询,数据管理灵活导入耗时,占用存储空间更大

内容的提问来源于stack exchange,提问作者Curtis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 14:23:11