-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
acb203f
commit c8c8060
Showing
8 changed files
with
467 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,131 @@ | ||
# testcase-generator | ||
# Testcase Generator | ||
|
||
A testcase generator for easily creating testcases for online judges. | ||
|
||
## Installation | ||
``` | ||
$ pip install testcase-generator | ||
``` | ||
|
||
Alternatively, just clone this repository! | ||
|
||
## Usage | ||
```python | ||
from testcase_generator import Constraint, Case, Batch, Generator, ConstraintParser | ||
|
||
def set_constraints(self): | ||
## Write main constraints here ## | ||
# Sets the constraint of N to be between 1 and 10^3 inclusive. | ||
self.N = Constraint(1, 10**3) | ||
|
||
def generate_input(self): | ||
## Write generator here ## | ||
# Generates a value for N | ||
yield self.N.next | ||
|
||
|
||
Case.SET_CONSTRAINTS = set_constraints | ||
Case.SET_INPUT = generate_input | ||
|
||
|
||
# Using the yaml config to create the batches: | ||
config_yaml = """ | ||
- batch: 1 | ||
constraints: {N: 1~10**2} | ||
cases: | ||
- {N: MIN} | ||
- {N: MAX} | ||
- {N: 2~10} | ||
- {N: 10**2-1~} | ||
- batch: 2 | ||
constraints: {} | ||
cases: | ||
- {} | ||
- {N: ~2} | ||
""" | ||
|
||
p = ConstraintParser(data=config_yaml) | ||
p.parse() | ||
batches = p.batches | ||
|
||
|
||
# creating the batches manually | ||
batches = [ | ||
Batch(num=1, cases=[Case() for i in range(4)]), | ||
Batch(num=2, cases=[Case(N=Constraint(1,10)) for i in range(2)]), | ||
] | ||
|
||
|
||
Generator(batches=batches, exe='COMMAND_TO_GENERATE_OUTPUT').start() | ||
``` | ||
|
||
The generator features a `GraphGenerator`, which generates a variety of graph types: | ||
```python | ||
from testcase_generator import Constraint, Case, Batch, Generator, ConstraintParser, GraphGenerator | ||
|
||
""" | ||
| initialize(self, N, graph_type, *args, **kwargs) | ||
| N: number of nodes | ||
| graph_type: | ||
| 1: normal graph | ||
| 2: connected graph | ||
| 3: complete graph | ||
| 4: circle | ||
| 10: line | ||
| 11: normal tree | ||
| 12: tree, all nodes connected to one node | ||
| 13: caterpillar tree | ||
| 14: binary tree | ||
| kwargs: | ||
| M: number of edges, leave blank if it is a tree | ||
| duplicates: allow for duplicate edges between nodes | ||
| self_loops: allow for edges between the same node | ||
""" | ||
|
||
def set_constraints(self): | ||
## Write main constraints here ## | ||
# Sets the constraint of N to be between 1 and 10^3 inclusive. | ||
# In this case, this is a graph with N nodes. | ||
self.N = Constraint(1, 10**3) | ||
# creates the graph generator | ||
self.ee = GraphGenerator() | ||
# Creates the variable that returns the next edge in the graph. | ||
# The 1s are filler values. | ||
self.E = Constraint(1, 1, self.ee.next_edge) | ||
# Sets the graph type to be some graph type between 10 and 14. | ||
# Please read the initialize method doc for details. | ||
# In this case, the graph type is some form of a tree. | ||
self.graph_type = Constraint(10, 14) | ||
|
||
def generate_input(self): | ||
## Write generator here ## | ||
n = self.N.next | ||
yield n | ||
self.ee.initialize(n, self.graph_type.next) | ||
for i in range(n-1): | ||
yield self.E.next | ||
|
||
|
||
Case.SET_CONSTRAINTS = set_constraints | ||
Case.SET_INPUT = generate_input | ||
|
||
|
||
# Using the yaml config to create the batches: | ||
config_yaml = """ | ||
- batch: 1 | ||
constraints: {N: 1~10**3-1} | ||
cases: | ||
- {} | ||
- {} | ||
- {} | ||
- {} | ||
- {} | ||
- {} | ||
""" | ||
|
||
p = ConstraintParser(data=config_yaml) | ||
p.parse() | ||
batches = p.batches | ||
|
||
Generator(batches=batches, exe='COMMAND_TO_GENERATE_OUTPUT').start() | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
from setuptools import find_packages, setup | ||
|
||
with open('README.md') as f: | ||
readme = f.read() | ||
|
||
setup( | ||
name='testcase-generator', | ||
version='0.0.1', | ||
author='Evan Zhang', | ||
description='A testcase generator for creating testcases for online judges.', | ||
long_description=readme, | ||
long_description_content_type="text/markdown", | ||
url='https://github.com/Ninjaclasher/testcase-generator', | ||
packages=find_packages(), | ||
classifiers=[ | ||
'Development Status :: 3 - Alpha', | ||
'Environment :: Console', | ||
'Intended Audience :: Developers', | ||
'License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)', | ||
'Operating System :: POSIX :: Linux', | ||
'Programming Language :: Python', | ||
'Programming Language :: Python :: 3.7', | ||
], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
""" | ||
Test case generator for online judges. | ||
Created by Evan Zhang (Ninjaclasher) | ||
""" | ||
from .models import Constraint, Case, Batch, Generator | ||
from .parser import ConstraintParser | ||
from .generators import GraphGenerator |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .graph_generator import GraphGenerator |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
from collections import Counter | ||
from random import Random | ||
|
||
|
||
class GraphGenerator: | ||
def __init__(self): | ||
self.edges = Counter() | ||
self.random = Random() | ||
self.nodes = [] | ||
self.is_initialized = False | ||
|
||
def next_edge(self, a, b): | ||
try: | ||
u, v = self.edges.pop() | ||
except: | ||
return None | ||
if self.random.randint(0, 1) == 0: | ||
return u, v | ||
else: | ||
return v, u | ||
|
||
@property | ||
def _node(self): | ||
return self.random.choice(self.nodes) | ||
|
||
def _generate_nodes(self): | ||
self.nodes = list(range(1, self.N+1)) | ||
self.random.shuffle(self.nodes) | ||
|
||
def _clean(self, a, b): | ||
return (a, b) if a < b else (b, a) | ||
|
||
def _get_node_pair(self): | ||
return self._node, self._node | ||
|
||
def _validate_node_pair(self, a, b): | ||
return (self.self_loops or a != b) and (self.duplicates or self.edges[self._clean(a,b)] == 0) | ||
|
||
def _get_pair(self): | ||
a, b = self._get_node_pair() | ||
while not self._validate_node_pair(a, b): | ||
a, b = self._get_node_pair() | ||
return a, b | ||
|
||
def _add_edge(self, a, b): | ||
self.edges[self._clean(a, b)] += 1 | ||
|
||
def _generate_tree(self): | ||
for i in range(1, self.N): | ||
u = self.random.randint(0, i-1) | ||
self._add_edge(self.nodes[u], self.nodes[i]) | ||
|
||
def _validate(self): | ||
if self.M is None and self.type in (1, 2): | ||
raise ValueError('M must be specified.') | ||
if self.type == 2 and self.M < self.N-1: | ||
raise ValueError('Impossible graph.') | ||
if self.type == 3 and self.N > 10**4: | ||
raise ValueError('Do you want me to TLE?') | ||
|
||
def _generate_edges(self): | ||
N = self.N | ||
|
||
if self.type == 1: | ||
for i in range(self.M): | ||
self._add_edge(*self._get_pair()) | ||
elif self.type == 2: | ||
self._generate_tree() | ||
for i in range(self.M-N+1): | ||
self._add_edge(*self._get_pair()) | ||
elif self.type == 3: | ||
for i in self.nodes: | ||
for j in self.nodes[i + (not self.self_loops):]: | ||
self._add_edge(i, j) | ||
elif self.type == 4: | ||
self.nodes.append(self.nodes[0]) | ||
for i in range(self.N): | ||
self._add_edge(self.nodes[i], self.nodes[i+1]) | ||
elif self.type == 10: | ||
for i in range(self.N-1): | ||
self._add_edge(self.nodes[i], self.nodes[i+1]) | ||
elif self.type == 11: | ||
self._generate_tree() | ||
elif self.type == 12: | ||
special = self._node | ||
for i in self.nodes: | ||
if i != special: | ||
self._add_edge(i, special) | ||
elif self.type == 13: | ||
main_len = self.random.randint(self.N//2, self.N-1) | ||
for i in range(main_len): | ||
self._add_edge(self.nodes[i], self.nodes[i+1]) | ||
for j in range(main_len+1, self.N): | ||
u = self.random.randint(0, j-1) | ||
self._add_edge(self.nodes[u], self.nodes[j]) | ||
elif self.type == 14: | ||
self.nodes = [0] + self.nodes | ||
for i in range(2, self.N+1): | ||
self._add_edge(self.nodes[i], self.nodes[i//2]) | ||
edges = [] | ||
for edge, cnt in self.edges.items(): | ||
edges += [edge] * cnt | ||
self.edges = edges | ||
self.random.shuffle(self.edges) | ||
|
||
def initialize(self, N, graph_type, *args, **kwargs): | ||
""" | ||
N: number of nodes | ||
graph_type: | ||
1: normal graph | ||
2: connected graph | ||
3: complete graph | ||
4: circle | ||
10: line | ||
11: normal tree | ||
12: tree, all nodes connected to one node | ||
13: caterpillar tree | ||
14: binary tree | ||
kwargs: | ||
M: number of edges, leave blank if it is a tree | ||
duplicates: allow for duplicate edges between nodes | ||
self_loops: allow for edges between the same node | ||
""" | ||
if self.is_initialized: | ||
raise ValueError('Cannot intialize twice.') | ||
self.N = N | ||
self.type = int(graph_type) | ||
self.M = kwargs.get('M', None) | ||
self.duplicates = kwargs.get('duplicates', False) | ||
self.self_loops = kwargs.get('self_loops', False) | ||
|
||
self.is_initialized = True | ||
|
||
self._generate_nodes() | ||
self._validate() | ||
self._generate_edges() |
Oops, something went wrong.