为自己打造一个自动造数据机

· · 科技·工程

项目背景

众所周知,现在造数据的常用方法是写一个 C++ 脚本或者使用 cyaron。

既然这个云 Generator 没出来,本蒟蒻只好自己写一个自己用的了。

注意:本项目建议自己使用,项目并没有安全防御功能。

项目结构

本蒟蒻计划使用 python3.8 的 flask 框架搭建。目录如下:

KittenGen
│  LICENSE
│  MANIFEST.in
│  README.md
│  requirements.txt
│  setup.py
│
└─KittenGen
    │  app.py
    │  lib.py
    │  __init__.py
    │  __main__.py
    │
    ├─static
    │  ├─css
    │  │      bootstrap.min.css
    │  │      codemirror.min.css
    │  │
    │  └─js
    │          bootstrap.min.js
    │          clike.min.js
    │          codemirror.min.js
    │          jquery.min.js
    │          python.min.js
    │
    └─templates
           help.html
           index.html

这里 app.py 实现服务器端,templates 中的东西是前端。

代码

前端

这里本蒟蒻使用 Deepseek 帮忙设计了网页。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>KittenGen</title>
    <link href="/static/css/bootstrap.min.css" rel="stylesheet">
    <script src="/static/js/jquery.min.js"></script>
    <script src="/static/js/bootstrap.min.js"></script>

    <link href="/static/css/codemirror.min.css" rel="stylesheet" >
    <script src="/static/js/codemirror.min.js"></script>
    <script src="/static/js/clike.min.js"></script>
    <script src="/static/js/python.min.js"></script>

    <style type="text/css">
        .CodeMirror {
            min-height: 150px;
            border-radius: 4px;
        }
    </style>
</head>
<body class="bg-light">
<div class="container py-5">
    <form method="post" action="/" class="card shadow" onsubmit="syncCodeEditors()">
        <div class="card-body">
            {% if error %}
            <div class="alert alert-danger" id="errAlert" role="alert">
                <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
                <h5 class="alert-heading">错误!</h5>
                {{ error }}
            </div>
            {% endif %}

            <!-- Std.cpp 输入区域 -->
            <div class="mb-4">
                <label class="form-label fw-bold">程序 Std(std.cpp)</label>
                <div id="std-editor" class="border rounded overflow-hidden"></div>
                <textarea class="d-none" id="std" name="std"></textarea>
            </div>

            <!-- Maker.py 输入区域 -->
            <div class="mb-4">
                <label class="form-label fw-bold">
                    数据生成器(maker.py)
                    <a href="/help" class="fs-6 text-decoration-none ms-2">查看帮助说明?</a>
                </label>
                <div id="maker-editor" class="border rounded overflow-hidden"></div>
                <textarea class="d-none" id="maker" name="maker"></textarea>
            </div>

            <!-- 数据组数 -->
            <div class="mb-4">
                <label for="num" class="form-label fw-bold">数据组数</label>
                <input type="number" class="form-control" id="num" name="num"
                    min="1" value="10" required>
            </div>

            <!-- 编译选项 -->
            <div class="mb-4">
                <label for="options" class="form-label fw-bold">C++ 编译选项</label>
                <input type="text" class="form-control" id="options" name="options"
                    value="-O2 -std=c++14 -Wl,--stack=204800000" required>
            </div>

            <!-- SPJ 开关 -->
            <div class="mb-3 form-check form-switch">
                <input type="checkbox" class="form-check-input" id="useSpj"
                    name="useSpj" role="switch">
                <label class="form-check-label fw-bold" for="useSpj">是否使用SPJ</label>
            </div>

            <!-- SPJ 输入区域(默认隐藏) -->
            <div id="spjSection" style="display: none;">
                <div class="mb-4">
                    <label class="form-label fw-bold">SPJ(checker.cpp)</label>
                    <div id="checker-editor" class="border rounded overflow-hidden"></div>
                    <textarea class="d-none" id="checker" name="checker"></textarea>
                </div>
            </div>

            <!-- Config 开关 -->
            <div class="mb-3 form-check form-switch">
                <input type="checkbox" class="form-check-input" id="useCfg"
                    name="useCfg" role="switch">
                <label class="form-check-label fw-bold" for="useCfg">是否使用配置文件</label>
            </div>

            <!-- config.yml 输入区域(默认隐藏) -->
            <div id="cfgSection" style="display: none;">
                <div class="mb-4">
                    <label class="form-label fw-bold">配置文件(config.yml)</label>
                    <div id="config-editor" class="border rounded overflow-hidden"></div>
                    <textarea class="d-none" id="config" name="config"></textarea>
                </div>
            </div>

            <!-- 提交按钮 -->
            <input type="submit" class="btn btn-primary btn-lg w-100 mt-3" id="submit" value="提交">
        </div>
    </form>
</div>

<script>
    // 初始化代码编辑器
    const editors = {
        std: CodeMirror(document.getElementById('std-editor'), {
            mode: 'text/x-c++src',
            lineNumbers: true,
            theme: 'default',
            indentUnit: 4,
            extraKeys: {"Ctrl-Space": "autocomplete"},
            value: document.getElementById('std').value
        }),
        maker: CodeMirror(document.getElementById('maker-editor'), {
            mode: 'python',
            lineNumbers: true,
            theme: 'default',
            indentUnit: 4,
            value: document.getElementById('maker').value
        }),
        checker: CodeMirror(document.getElementById('checker-editor'), {
            mode: 'text/x-c++src',
            lineNumbers: true,
            theme: 'default',
            indentUnit: 4,
            value: document.getElementById('checker').value
        }),
        checker: CodeMirror(document.getElementById('config-editor'), {
            lineNumbers: true,
            theme: 'default',
            indentUnit: 4,
            value: document.getElementById('config').value
        })
    };

    // 同步编辑器内容到表单
    function syncCodeEditors() {
        document.getElementById('std').value = editors.std.getValue();
        document.getElementById('maker').value = editors.maker.getValue();
        document.getElementById('checker').value = editors.checker.getValue();
    }

    // SPJ 切换逻辑
    document.getElementById('useSpj').addEventListener('change', function() {
        const spjSection = document.getElementById('spjSection');
        spjSection.style.display = this.checked ? 'block' : 'none';
    });
    // Config 切换逻辑
    document.getElementById('useCfg').addEventListener('change', function() {
        const cfgSection = document.getElementById('cfgSection');
        cfgSection.style.display = this.checked ? 'block' : 'none';
    });
</script>
</body>

</html>

基于 bootstrapcodemirror 美化前端,效果如下:

对于 help.html,使用 <ul> 给出提示。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>KittenGen | 帮助</title>
    <link href="/static/css/bootstrap.min.css" rel="stylesheet">
    <script src="/static/js/jquery.min.js"></script>
    <script src="/static/js/bootstrap.min.js"></script>
</head>
<body class="bg-light">
<div class="container py-5">
    <div class="card shadow">
        <div class="card-body">
            <h2 class="mb-4">数据生成器使用说明</h2>

            <h4 class="mb-3 text-primary">基本规范</h4>
            <ul class="list-group mb-4">
                <li class="list-group-item">
                    <strong>代码中可以使用全局变量 <code>num: int</code></strong> -
                    表示当前测试点编号(从1开始)
                </li>
                <li class="list-group-item">
                    <strong>random 库已自动预导入</strong> -
                    可直接使用 randint/choice 等函数
                </li>
                <li class="list-group-item">
                    <strong>使用 print 函数直接输出</strong> -
                    内容会自动写入输入文件,无需文件操作
                </li>
            </ul>

            <h4 class="mb-3 text-primary">示例代码</h4>
            <strong>一个单点修改,区间查询的数据结构题</strong>
            <pre class="bg-dark text-light p-4 rounded mb-4">
                <code class="language-python">
N = [0, 100, 2000, 100000, 100000, 200000, 200000, 300000, 400000, 500000, 500000]
Q = [0, 100, 2000, 100000, 100000, 200000, 200000, 300000, 400000, 500000, 500000]
E9 = 10 ** 9

n, q = N[num], Q[num]
print(n, q)
for i in range(n):
    print(randint(-E9, E9), end=' ')
print()
for i in range(q):
    opt = choice([1, 2])
    l = randint(1, n)
    if opt == 1:
        print(opt, l, randint(-E9, E9))
    else:
        r = randint(1, n)
        if l > r:
            l, r = r, l
        print(opt, l, r)
                </code>
            </pre>

            <div class="text-center">
                <a href="/" class="btn btn-primary btn-lg px-5">返回</a>
            </div>
        </div>
    </div>
</div>
</body>
</html>

效果图:

服务器

先把 std 和 maker 都编译一下。为了方便,本蒟蒻选择直接进入 ./data 路径,用 maker.py 生成数据以后跑 std.exeapp.py 代码如下:

import os
import zipfile

from flask import Flask, render_template, request, send_file

from .lib import global_vars   # 默认变量

app = Flask(__name__)

GCC_PATH = 'g++.exe'   # 这里填写你的g++路径

@app.route('/', methods=['GET', 'POST'])
def index():   # 主网页
    if request.method == 'GET':
        return render_template('index.html', error=None)
    cwd = os.getcwd()  # 记录当前工作路径
    data_path = os.path.join(cwd, 'data')
    if not os.path.exists(data_path):
        os.mkdir(data_path)
    os.chdir(data_path)  # 进入数据工作路径

    form = request.form
    std, maker, num, options = form.get('std'), form.get('maker'), int(form.get('num')), form.get('options')
    try:
        maker_complied = compile(maker, 'maker.py', 'exec')   # 先编译生成器
    except (Exception, SystemExit) as err:
        os.chdir(cwd)  # 切换回原工作路径
        return render_template('index.html', error=str(err))

    use_spj, use_cfg = form.get('useSpj'), form.get('useCfg')
    if use_spj:
        checker = form.get('checker')
        with open('checker.cpp', 'w', encoding='utf-8') as f:
            f.write(checker)
    if use_cfg:
        config = form.get('config')
        with open('config.yml', 'w', encoding='utf-8') as f:
            f.write(config)

    with open('std.cpp', 'w', encoding='utf-8') as f:
        f.write(std)   # 写入std.cpp
    os.system(f'{GCC_PATH} {options} -o std.exe std.cpp')    # 编译 std

    for i in range(1, num + 1):   # 循环并生成数据
        local_vars = global_vars.copy()
        in_file = open(f'{i}.in', 'w', encoding='utf-8')
        local_vars['num'], local_vars['print'] = i, lambda *args, **kwargs: print(*args, **kwargs, file=in_file)  # 设置变量
        try:
            exec(maker_complied, local_vars)
        except (Exception, SystemExit) as err:
            os.chdir(cwd)  # 切换回原工作路径
            return render_template('index.html', error=str(err))
        finally:
            in_file.close()
        os.system(f'std.exe < {i}.in > {i}.out')   # 使用 std 生成答案

    with zipfile.ZipFile('data.zip', 'w', zipfile.ZIP_DEFLATED) as zf:   # 打包 zip 文件
        for i in range(1, num + 1):
            zf.write(f'{i}.in')
            zf.write(f'{i}.out')
        if use_spj:
            zf.write('checker.cpp')
        if use_cfg:
            zf.write('config.yml')

    os.chdir(cwd)   # 切换回原工作路径
    return send_file(os.path.join(data_path, 'data.zip'), as_attachment=True)   # 返回答案 zip 文件

@app.route('/help/')
def show_help():
    return render_template('help.html')

其中,lib.py 代码如下。你可以根据需要自行添加函数或变量。

import random
import math
import string

global_vars = {
    'random': random.random,
    'randint': random.randint,
    'uniform': random.uniform,
    'choice': random.choice,
    'sample': random.sample,
    'seed': random.seed,
    'randrange': random.randrange,
    'Random': random,
    'Math': math,
    'PI': math.pi,
    'E': math.e,
    'ASCII_LOWERCASE': string.ascii_lowercase,
    'ASCII_UPPERCASE': string.ascii_uppercase,
    'DIGITS': string.digits,
    'ASCII_LETTERS': string.ascii_letters,
    'SENTENCE_SEPARATORS': ',,,,,,,;;:',
    'SENTENCE_TERMINATORS': '....!',
}

上传

写完代码以后,为了让大家用的方便,本蒟蒻将它上传到 pip。

使用 setup.py 打包:

from setuptools import setup, find_packages

from KittenGen import version

with open('README.md', 'r', encoding='utf-8') as f:
    long_description = f.read()

setup(
    name='KittenGen',
    packages=find_packages(exclude=(), include=('*',)),
    author='stripe-python',
    author_email='[email protected]',
    maintainer='stripe-python',
    maintainer_email='[email protected]',
    license='MIT License',
    install_requires=['flask~=3.0.3'],
    classifiers=[
        'Programming Language :: Python :: 3',
        'License :: OSI Approved :: MIT License',
        'Operating System :: OS Independent',
    ],
    include_package_data=True,
    long_description=long_description,
    long_description_content_type='text/markdown',
    version=version,
)

在 cmd 中运行:

python setup.py sdist bdist_wheel
twine upload dist/*

注:pypi 搞了个恶心的 token,如果你也想上传包,请搜索教程。

即上传至 pip。

便捷使用

你只需要安装好 python,并下载这个包:

pip install KittenGen

然后输入

python -m KittenGen

就可以到 http://127.0.0.1:2025/ 体验。

除此之外,本蒟蒻在 Github 也传了一份,你也可以打开 https://github.com/stripepython/KittenGen 访问。