<!-* Also automatically generates (Python) source code to receive input->
Not only those who want to use the automatic generation function, but also those who want to know about programming that goes one step further from the competition pro. I think this content can be read by those who are studying object-oriented programming. The entire source code can be found at [here] 1.
If you prepare such an input macro →
Int(N,2,5)
Float(a,1,100,5) Perm(1,N+4)
Str([a-z]{3,2*N})
*N(2)
Input will come out →
output:
4
54.81887 3 2 4 7 1 8 5 6
yyl
4.32497 4 1 6 5 3 8 7 2
yziuqiac
42.84603 3 2 4 7 8 6 5 1
vsjajs
65.07176 7 5 8 3 4 6 1 2
rbq
Various other things
Weighted graph:
Int(N,3,5) Int(M,N-1,N*(N-1)//2)
Graph1(N,M,0,1000)
↓
output:
4 5
1 2 392
1 3 328
1 4 891
2 3 264
3 4 227
String:
Int(N,4,6) Int(M,4,6)
Str([.#]{M})
*N(1)
↓
output:
4 5
###.#
#.#.#
#..##
.###.
etc.
If you are interested in the function itself, you can find a list in [here] 1.
<!-## What you can do: Automatically generate source code to receive input
Instead of spitting input, you can also use Python's input () function to generate source code that receives input:
``` -->
* Since we have just defined the inner class so far, if you want to generate a lot of test cases at once or write it to a file, you need to code Python using this class.
## Realization method
It has an object-oriented implementation.
Roughly
1. Class responsible for reading the entire macro (LineCollection)
1. Class (Line) responsible for reading macros line by line
1. The class that is responsible for the space delimiter in one line of the macro (Item and its subclasses)
It is divided into. It is an image that i manages i + 1 and throws the necessary work (i = 1,2).
All classes have the following methods in common:
1. from_str (): Parses the input macro string and initializes the class.
1. generate (): Generates a random input test case for the range it manages.
In summary, it looks like the table below:
||LineCollection|Line|Item|
|---|---|---|---|
|from_str()|Loading the entire macro|Read one line|Reading one word|
|generate()|Output generation for the entire macro|Output generation for one line|Output generation for one word|
<!-- |generate()/generate_source()|Output generation for the entire macro|Output generation for one line|Output generation for one word| -->
The point is
* To use the whole, LineCollection.from_str (input macro) → generate ()
<!-* To use the whole, LineCollection.from_str (input macro) → generate () / generate_source ()->
* If you want to increase the number of new macro types, just define a subclass of the Item class that has the above three methods (no need to change Line or LineCollection).
What a place, such as.
## Source code
Specifically, we will check the source code.
LineCollection
||<font color=green> LineCollection|Line|Item|
|---|---|---|---|
|from_str()|<font color=green>Loading the entire macro</font>|Read one line|Reading one word|
|generate()|<font color=green>Output generation for the entire macro|Output generation for one line|Output generation for one word|
<!-- |generate()/generate_source()|<font color=green>Output generation for the entire macro|Output generation for one line|Output generation for one word| -->
I have a list of Lines for each line of the macro in self.ls.
It's a bit complicated to support macros like "* N (1)", but what we're doing is
* from_str () = Read entire macro: Initialize the Line corresponding to each line and put it in self.ls
* generate () = Generate output for the entire macro: Generate output for each line in self.ls and return it with a newline character
only.
```python
class LineCollection:
def __init__(self, ls, s=None):
"""ls: list of Line
"""
self.ls = ls
self.s = s
@classmethod
def from_str(cls, s):
lines = s.split("\n")
i = 0
ls = []
for i in range(len(lines)):
if lines[i].startswith("*"):
name, num = lines[i][1:].split("(",1)
num = int(num[:-1])
ls.append((name, num))
else:
l = Line.from_str(lines[i])
ls.append(l)
return cls(ls, s)
def generate(self):
i = 0
prv = 0
output = []
while i<len(self.ls):
while i<len(self.ls) and not isinstance(self.ls[i], tuple):
i += 1
if i<len(self.ls) and isinstance(self.ls[i], tuple):
m, num = self.ls[i]
i += 1
else:
m = 0
num = 0
for j in range(prv, i-num-1):
if isinstance(self.ls[j], tuple):
continue
output.append(self.ls[j].generate())
if num!=0:
try:
m = Item.names[m]
except KeyError:
raise ValueError("Undefined number of test cases:Isn't the specification of how many lines to see above wrong?")
for _ in range(m):
for j in range(i-num-1, i-1):
if isinstance(self.ls[j], tuple):
continue
output.append(self.ls[j].generate())
prv = i
return "\n".join(output)
Line
LineCollection | Line | Item | |
---|---|---|---|
from_str() | Loading the entire macro | Read one line | Reading one word |
generate() | Output generation for the entire macro | Output generation for one line | Output generation for one word |
self.l manages a list of Items that exist in the line you are looking at. Similar to the LineCollection,
I am doing that. Where we read the class name and initialize the Item, we get the class object from Python's built-in function globals ().
def evaluate_item(ss):
cc, tmp = ss.split("(", 1)
vv = tmp[:-1]
return globals()[cc].from_str(vv)
class Line:
def __init__(self, l, s=None):
"""
correspond to a line of input file
l: list of Item
"""
self.l = l
self.s = s
@classmethod
def from_str(cls, s):
l = []
for ss in s.split():
l.append(evaluate_item(ss))
return cls(l)
def generate(self):
return " ".join([item.generate() for item in self.l])
Item
LineCollection | Line | Item | |
---|---|---|---|
from_str() | Loading the entire macro | Read one line | Reading one word |
generate() | Output generation for the entire macro | Output generation for one line | Output generation for one word |
Finally, Item, which is defined for each macro you want to support. In such a case, creating one base class and inheriting it is superior in terms of readability, maintainability, and coding efficiency. We have prepared the Item class as a base class as follows:
class Item:
names = {}
def __init__(self, s=None, **keys):
self.s = s
@classmethod
def from_str(cls, s):
pass
def evaluate(self, s):
for k in Item.names.keys():
if k in s:
s = s.replace(k, str(Item.names[k]))
return eval(s)
def generate(self):
pass
def __str__(self):
if hasattr(self, "s"):
return self.s
Item is a class that cannot be used as it is, so it is better to put a magic spell to represent it, but this time it is omitted. Just declaring that you have these methods has great benefits, such as making it easier for others to understand when adding features.
Under this, create a class for each macro you want to realize. For example, Int
class Int(Item):
def __init__(self, name, low, high, s=None, **keys):
"""
correspond to the input value between two spaces
name: str
name of variable
low/high : str
min / max (inclusive)
"""
self.name = name
self.low = low
self.high = high
self.keys = keys
Item.__init__(self, s)
@classmethod
def from_str(cls, s):
name, low, high = s.split(",")
return cls(name, low, high, s=s)
def generate(self):
low, high = self.evaluate(self.low), self.evaluate(self.high)
value = utils.rng.integers(low, high+1)
Item.names[self.name] = value
return str(value)
If it is a permutation of 1 ... N
class Perm(Item):
"""permutation of [low, low+1, ..., high-1, high]
"""
def __init__(self, low, high, s=None):
self.low = low
self.high = high
self.s = s
@classmethod
def from_str(cls, s):
low, high = s.split(",")
return cls(low, high, s=s)
def generate(self):
low, high = self.evaluate(self.low), self.evaluate(self.high)
return " ".join(map(str, (utils.rng.permutation(high-low+1) + low).tolist()))
It is shaped like. Where random numbers are needed, they are thrown into an externally defined random number generator:
utils.py
import numpy as np
SEED = 0
rng = np.random.default_rng(SEED)
When using random numbers, it is correct not to call the function each time, but to prepare random number correctors (multiple if necessary) and use them properly according to the scope. However, in this case, it depends on whether it is desirable to fix the random number (to obtain reproducibility) or to change the result for each execution, so it may be necessary to rewrite it depending on the use case. ..
Other macros will be supported one by one in the same way. For example, if it is a graph, it is realized by using functions such as networkx random graph generation, and if it is a character string, a character string that matches the regular expression pattern by rstr is randomly generated. What to do anyway
It's just a matter of implementing, so the effort to add functions is reduced!
<!-* Also automatically generates (Python) source code to receive input->
TODO