Skip to content

Commit 81abe0f

Browse files
authored
Merge pull request #3 from luogu-dev/master
Follow the upstream
2 parents 63993ae + 825f2ff commit 81abe0f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1596
-112
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,4 +128,7 @@ coverage.xml
128128
docs/_build/
129129

130130
# PyBuilder
131-
target/
131+
target/
132+
133+
# Pycharm
134+
venv

.travis.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
language: python
22
python:
33
- "2.7"
4-
- "3.5"
54
- "3.6"
5+
- "3.7"
6+
- "3.8"
67
- "pypy"
78
- "pypy3"
8-
script: python unit_test.py
9-
9+
install: pip install tox-travis
10+
script: tox

README.md

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,71 @@
11
# Project CYaRon
2-
CYaRon: Yet Another Random Olympic-iNformatics test data generator
2+
**CYaRon** **Y**et **A**nother **R**andom **O**lympic-i**N**formatics test data generator
3+
4+
**By Luogu** 项目地址: [https://github.com/luogu-dev/cyaron](https://github.com/luogu-dev/cyaron)
5+
36

4-
By Luogu
57
[![](https://travis-ci.org/luogu-dev/cyaron.svg?branch=master)](https://travis-ci.org/luogu-dev/cyaron)
68

79
你是否遇到以下情况:
8-
- 希望在5分钟内写出一组随机数据
10+
- 希望在5分钟内写出一组随机数据,并方便地使用它们对拍几个程序
911
- 希望生成一个合适的随机图或者树,且有一定强度
1012
- 希望生成一组随机数列或者向量,且不能重复。
1113

12-
那么,你可以借助CYaRon和Python,来快速生成一组数据。目前支持的特性有:
14+
那么,你可以借助 CYaRon 和 Python ,来快速生成一组数据。目前支持的特性有:
1315

1416
- 建一个随机图(简单图或者非简单图,有向图或无向图,带权图或者无权图)
1517
- 建一个随机树(链状、随机树、或者菊花图,而且可以设定树的强弱)
1618
- 生成一组允许相同或者互相不同的多维向量(可以较快速度生成10^6组、范围到10^9的向量或者数列)
1719
- 根据函数解析式生成数列
1820
- 生成一些随机多边形,并且可以求面积、周长等
1921
- 从字典生成随机字符串、单词、句子、段落
22+
- 使用以上功能生成的数据和您其他地方下载的测试数据方便地进行程序对拍
2023

2124
**快速上手指南**
2225

23-
你可以下载github源代码 https://github.com/luogu-dev/cyaron ,或者`pip install cyaron`。在此之前,需要准备好python2/3。
26+
稳定版本可以从pip获取: `pip install cyaron`,在此之前,需要准备好Python。
27+
28+
最新开发版可以克隆GitHub源代码: `git clone https://github.com/luogu-dev/cyaron.git`
29+
30+
请您查看[CYaRon文档](https://github.com/luogu-dev/cyaron/wiki)[CYaRon基本入门](https://github.com/luogu-dev/cyaron/wiki/%E5%9F%BA%E6%9C%AC%E5%85%A5%E9%97%A8)来学习如何使用CYaRon。
31+
32+
若您发现文档中有缺漏,请提出Issue并暂时根据`examples`和源代码进行YY。
33+
34+
CYaRon基于Python。若您对Python不熟悉,可看[快速入门教程](https://github.com/luogu-dev/cyaron/wiki/Python-30%E5%88%86%E9%92%9F%E5%85%A5%E9%97%A8%E6%8C%87%E5%8D%97)
35+
36+
之后计划实现云Generator,即只需提供写好的脚本以及std,上传到服务器,即可下载一个测试数据的压缩包,真正实现5分钟生成一个测试数据!
37+
38+
希望各位大佬一起来协助改进这个项目。希望这个项目可以帮助大家节省时间!
39+
40+
**使用范例**
41+
```python
42+
#!/usr/bin/env python
43+
44+
from cyaron import * # 引入CYaRon的库
45+
46+
_n = ati([0, 7, 50, 1E4]) # ati函数将数组中的每一个元素转换为整形,方便您可以使用1E4一类的数来表示数据大小
47+
_m = ati([0, 11, 100, 1E4])
48+
49+
# 这是一个图论题的数据生成器,该题目在洛谷的题号为P1339
50+
for i in range(1, 4): # 即在[1, 4)范围内循环,也就是从1到3
51+
test_data = IO(file_prefix="heat", data_id=i) # 生成 heat[1|2|3].in/out 三组测试数据
52+
53+
n = _n[i] # 点数
54+
m = _m[i] # 边数
55+
s = randint(1, n) # 源点,随机选取一个
56+
t = randint(1, n) # 汇点,随机选取一个
57+
test_data.input_writeln(n, m, s, t) # 写入到输入文件里,自动以空格分割并换行
58+
59+
graph = Graph.graph(n, m, weight_limit=5) # 生成一个n点,m边的随机图,边权限制为5
60+
test_data.input_writeln(graph) # 自动写入到输入文件里,默认以一行一组u v w的形式输出
2461

25-
若您对Python不熟悉,可看[快速入门教程](https://github.com/luogu-dev/cyaron/wiki/Python-30%E5%88%86%E9%92%9F%E5%85%A5%E9%97%A8%E6%8C%87%E5%8D%97)
62+
test_data.output_gen("D:\\std_binary.exe") # 标程编译后的可执行文件,不需要freopen等,CYaRon自动给该程序输入并获得输出作为.out
63+
```
2664

27-
[文档](https://github.com/luogu-dev/cyaron/wiki/%E9%A6%96%E9%A1%B5)已经大体完整,若有缺漏,请提出Issue并暂时根据`examples`和源代码进行YY。
65+
**贡献**
2866

29-
首批贡献者 @fjzzq2002 @lin_toto @kkksc03
67+
所有的贡献者请查看[光荣榜](https://github.com/luogu-dev/cyaron/wiki/光荣榜)页面,衷心感谢他们对CYaRon项目的付出。
3068

31-
之后计划实现云Generator,即只需提供写好的python脚本以及std,上传到服务器,即可下载一个测试数据的压缩包,真正实现5分钟生成一个测试数据!
69+
欢迎您对 CYaRon 做出贡献。若您有希望加入的功能,可以给我们提出 Issue ,或者自己动手实现,然后发起 Pull Request。
3270

33-
目前CYaRon的功能还比较初级,希望各位大佬一起来协助改进这个项目。希望这个项目可以帮助大家节省时间!
71+
有关于如何做出贡献的更详细内容,请查看[如何做出贡献](https://github.com/luogu-dev/cyaron/wiki/%E5%A6%82%E4%BD%95%E5%81%9A%E5%87%BA%E8%B4%A1%E7%8C%AE)

cyaron/__init__.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,18 @@
44
It has tools for graphs and IO files.
55
"""
66

7+
from __future__ import absolute_import
78
from .io import IO
89
from .graph import Graph, Edge
9-
from .str import String
10+
from .string import String
1011
from .sequence import Sequence
1112
from .utils import *
1213
from .consts import *
1314
from .vector import Vector
1415
from .polygon import Polygon
16+
from .compare import Compare
17+
from .math import *
18+
from .merger import Merger
19+
#from .visual import visualize
20+
from . import log
1521
from random import randint, randrange, uniform, choice, random
16-

cyaron/compare.py

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
from __future__ import absolute_import, print_function
2+
from cyaron import IO, log
3+
from cyaron.utils import *
4+
from cyaron.consts import *
5+
from cyaron.graders import CYaRonGraders
6+
import subprocess
7+
import multiprocessing
8+
import sys
9+
from io import open
10+
import os
11+
12+
13+
class CompareMismatch(ValueError):
14+
def __init__(self, name, mismatch):
15+
super(CompareMismatch, self).__init__(name, mismatch)
16+
self.name = name
17+
self.mismatch = mismatch
18+
19+
def __str__(self):
20+
return 'In program: \'{}\'. {}'.format(self.name,self.mismatch)
21+
22+
23+
class Compare:
24+
@staticmethod
25+
def __compare_two(name, content, std, grader):
26+
(result, info) = CYaRonGraders.invoke(grader, content, std)
27+
status = "Correct" if result else "!!!INCORRECT!!!"
28+
info = info if info is not None else ""
29+
log.debug("{}: {} {}".format(name, status, info))
30+
if not result:
31+
raise CompareMismatch(name, info)
32+
33+
@staticmethod
34+
def __process_file(file):
35+
if isinstance(file, IO):
36+
file.flush_buffer()
37+
file.output_file.seek(0)
38+
return file.output_filename, file.output_file.read()
39+
else:
40+
with open(file, "r", newline='\n') as f:
41+
return file, f.read()
42+
43+
@staticmethod
44+
def __normal_max_workers(workers):
45+
if workers is None:
46+
if sys.version_info < (3, 5):
47+
cpu = multiprocessing.cpu_count()
48+
return cpu * 5 if cpu is not None else 1
49+
return workers
50+
51+
@classmethod
52+
def output(cls, *files, **kwargs):
53+
kwargs = unpack_kwargs('output', kwargs, ('std', ('grader', DEFAULT_GRADER), ('max_workers', -1),
54+
('job_pool', None), ('stop_on_incorrect', None)))
55+
std = kwargs['std']
56+
grader = kwargs['grader']
57+
max_workers = kwargs['max_workers']
58+
job_pool = kwargs['job_pool']
59+
if kwargs['stop_on_incorrect'] is not None:
60+
log.warn("parameter stop_on_incorrect is deprecated and has no effect.")
61+
62+
if (max_workers is None or max_workers >= 0) and job_pool is None:
63+
max_workers = cls.__normal_max_workers(max_workers)
64+
try:
65+
from concurrent.futures import ThreadPoolExecutor
66+
with ThreadPoolExecutor(max_workers=max_workers) as job_pool:
67+
return cls.output(*files, std=std, grader=grader, max_workers=max_workers, job_pool=job_pool)
68+
except ImportError:
69+
pass
70+
71+
def get_std():
72+
return cls.__process_file(std)[1]
73+
if job_pool is not None:
74+
std = job_pool.submit(get_std).result()
75+
else:
76+
std = get_std()
77+
78+
def do(file):
79+
(file_name, content) = cls.__process_file(file)
80+
cls.__compare_two(file_name, content, std, grader)
81+
82+
if job_pool is not None:
83+
job_pool.map(do, files)
84+
else:
85+
[x for x in map(do, files)]
86+
87+
@classmethod
88+
def program(cls, *programs, **kwargs):
89+
kwargs = unpack_kwargs('program', kwargs, ('input', ('std', None), ('std_program', None),
90+
('grader', DEFAULT_GRADER), ('max_workers', -1),
91+
('job_pool', None), ('stop_on_incorrect', None)))
92+
input = kwargs['input']
93+
std = kwargs['std']
94+
std_program = kwargs['std_program']
95+
grader = kwargs['grader']
96+
max_workers = kwargs['max_workers']
97+
job_pool = kwargs['job_pool']
98+
if kwargs['stop_on_incorrect'] is not None:
99+
log.warn("parameter stop_on_incorrect is deprecated and has no effect.")
100+
101+
if (max_workers is None or max_workers >= 0) and job_pool is None:
102+
max_workers = cls.__normal_max_workers(max_workers)
103+
try:
104+
from concurrent.futures import ThreadPoolExecutor
105+
with ThreadPoolExecutor(max_workers=max_workers) as job_pool:
106+
return cls.program(*programs, input=input, std=std, std_program=std_program, grader=grader, max_workers=max_workers, job_pool=job_pool)
107+
except ImportError:
108+
pass
109+
110+
if not isinstance(input, IO):
111+
raise TypeError("expect {}, got {}".format(type(IO).__name__, type(input).__name__))
112+
input.flush_buffer()
113+
input.input_file.seek(0)
114+
115+
if std_program is not None:
116+
def get_std():
117+
with open(os.dup(input.input_file.fileno()), 'r', newline='\n') as input_file:
118+
content = make_unicode(subprocess.check_output(std_program, shell=(not list_like(std_program)), stdin=input.input_file, universal_newlines=True))
119+
input_file.seek(0)
120+
return content
121+
if job_pool is not None:
122+
std = job_pool.submit(get_std).result()
123+
else:
124+
std = get_std()
125+
elif std is not None:
126+
def get_std():
127+
return cls.__process_file(std)[1]
128+
if job_pool is not None:
129+
std = job_pool.submit(get_std).result()
130+
else:
131+
std = get_std()
132+
else:
133+
raise TypeError('program() missing 1 required non-None keyword-only argument: \'std\' or \'std_program\'')
134+
135+
def do(program_name):
136+
timeout = None
137+
if list_like(program_name) and len(program_name) == 2 and int_like(program_name[-1]):
138+
program_name, timeout = program_name
139+
with open(os.dup(input.input_file.fileno()), 'r', newline='\n') as input_file:
140+
if timeout is None:
141+
content = make_unicode(subprocess.check_output(program_name, shell=(not list_like(program_name)), stdin=input_file, universal_newlines=True))
142+
else:
143+
content = make_unicode(subprocess.check_output(program_name, shell=(not list_like(program_name)), stdin=input_file, universal_newlines=True, timeout=timeout))
144+
input_file.seek(0)
145+
cls.__compare_two(program_name, content, std, grader)
146+
147+
if job_pool is not None:
148+
job_pool.map(do, programs)
149+
else:
150+
[x for x in map(do, programs)]

cyaron/consts.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
from __future__ import absolute_import
12
import math
23
import string
34

45
"""Constants Package.
56
Constants:
67
ALPHABET_SMALL -> All the lower ascii letters
78
ALPHABET_CAPITAL -> All the upper ascii letters
8-
ALPHABET -> All the upper ascii letters
9+
ALPHABET -> All the ascii letters
910
NUMBERS -> All the numbers(0-9)
1011
SENTENCE_SEPARATORS -> Includes 70% ",", 20% ";" and 10% ":"
1112
SENTENCE_TERMINATORS -> Includes 80% "." and 20% "!"
@@ -15,7 +16,9 @@
1516

1617
ALPHABET_SMALL = string.ascii_lowercase
1718
ALPHABET_CAPITAL = string.ascii_uppercase
18-
ALPHABET = string.ascii_uppercase
19+
ALPHABET = ALPHABET_SMALL + ALPHABET_CAPITAL
1920
NUMBERS = string.digits
2021
SENTENCE_SEPARATORS = ',,,,,,,;;:' # 70% ',' 20% ';' 10% ':'
2122
SENTENCE_TERMINATORS = '....!' # 80% '.' 20% '!'
23+
24+
DEFAULT_GRADER = "NOIPStyle"

cyaron/graders/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .graderregistry import CYaRonGraders
2+
3+
from .fulltext import fulltext
4+
from .noipstyle import noipstyle

cyaron/graders/fulltext.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import hashlib
2+
from .graderregistry import CYaRonGraders
3+
from .mismatch import HashMismatch
4+
5+
@CYaRonGraders.grader("FullText")
6+
def fulltext(content, std):
7+
content_hash = hashlib.sha256(content.encode('utf-8')).hexdigest()
8+
std_hash = hashlib.sha256(std.encode('utf-8')).hexdigest()
9+
return (True, None) if content_hash == std_hash else (False, HashMismatch(content, std, content_hash, std_hash))
10+

cyaron/graders/graderregistry.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
class GraderRegistry:
2+
_registry = dict()
3+
4+
def grader(self, name):
5+
def wrapper(func):
6+
self._registry[name] = func
7+
return func
8+
9+
return wrapper
10+
11+
def invoke(self, name, content, std):
12+
return self._registry[name](content, std)
13+
14+
def check(self, name):
15+
return name in self._registry
16+
17+
18+
CYaRonGraders = GraderRegistry()

cyaron/graders/mismatch.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
class Mismatch(ValueError):
2+
"""exception for content mismatch"""
3+
def __init__(self, content, std, *args):
4+
"""
5+
content -> content got
6+
std -> content expected
7+
"""
8+
super(Mismatch, self).__init__(content, std, *args)
9+
self.content = content
10+
self.std = std
11+
12+
class HashMismatch(Mismatch):
13+
"""exception for hash mismatch"""
14+
def __init__(self, content, std, content_hash, std_hash):
15+
"""
16+
content -> content got
17+
std -> content expected
18+
content_hash -> hash of content
19+
std_hash -> hash of std
20+
"""
21+
super(HashMismatch, self).__init__(content, std, content_hash, std_hash)
22+
self.content_hash = content_hash
23+
self.std_hash = std_hash
24+
25+
def __str__(self):
26+
return "Hash mismatch: read %s, expected %s" % (self.content_hash, self.std_hash)
27+
28+
class TextMismatch(Mismatch):
29+
"""exception for text mismatch"""
30+
def __init__(self, content, std, err_msg, lineno=None, colno=None, content_token=None, std_token=None):
31+
"""
32+
content -> content got
33+
std -> content expected
34+
err_msg -> error message template like "wrong on line {} col {} read {} expected {}"
35+
lineno -> line number
36+
colno -> column number
37+
content_token -> the token of content mismatch
38+
std_token -> the token of std
39+
"""
40+
super(TextMismatch, self).__init__(content, std, err_msg, lineno, colno, content_token, std_token)
41+
self.err_msg = err_msg.format(lineno, colno, content_token, std_token)
42+
self.lineno = lineno
43+
self.colno = colno
44+
self.content_token = content_token
45+
self.std_token = std_token
46+
47+
def __str__(self):
48+
return self.err_msg

0 commit comments

Comments
 (0)