From 6db76b1496d3ab755acd3c8bd3228255685d4a80 Mon Sep 17 00:00:00 2001 From: FredB-mine Date: Thu, 7 Mar 2024 20:05:23 +0800 Subject: [PATCH 01/18] Formatted code --- cyaron/compare.py | 135 +++++++++--- cyaron/consts.py | 4 +- cyaron/graders/fulltext.py | 12 +- cyaron/graders/graderregistry.py | 2 +- cyaron/graders/mismatch.py | 25 ++- cyaron/graders/noipstyle.py | 44 ++-- cyaron/graph.py | 312 +++++++++++++++------------- cyaron/io.py | 124 ++++++----- cyaron/log.py | 82 +++++--- cyaron/math.py | 342 +++++++++++++++++++------------ cyaron/merger.py | 52 ++--- cyaron/output_capture.py | 4 +- cyaron/polygon.py | 73 ++++--- cyaron/sequence.py | 10 +- cyaron/string.py | 13 +- cyaron/tests/compare_test.py | 76 ++++--- cyaron/tests/graph_test.py | 72 ++++--- cyaron/tests/io_test.py | 30 ++- cyaron/tests/polygon_test.py | 20 +- cyaron/tests/sequence_test.py | 1 - cyaron/tests/str_test.py | 4 +- cyaron/utils.py | 21 +- cyaron/vector.py | 22 +- cyaron/visual.py | 24 +-- 24 files changed, 931 insertions(+), 573 deletions(-) diff --git a/cyaron/compare.py b/cyaron/compare.py index ad613a0..66f76a7 100644 --- a/cyaron/compare.py +++ b/cyaron/compare.py @@ -17,7 +17,7 @@ def __init__(self, name, mismatch): self.mismatch = mismatch def __str__(self): - return 'In program: \'{}\'. {}'.format(self.name,self.mismatch) + return "In program: '{}'. {}".format(self.name, self.mismatch) class Compare: @@ -37,7 +37,7 @@ def __process_file(file): file.output_file.seek(0) return file.output_filename, file.output_file.read() else: - with open(file, "r", newline='\n') as f: + with open(file, "r", newline="\n") as f: return file, f.read() @staticmethod @@ -50,26 +50,43 @@ def __normal_max_workers(workers): @classmethod def output(cls, *files, **kwargs): - kwargs = unpack_kwargs('output', kwargs, ('std', ('grader', DEFAULT_GRADER), ('max_workers', -1), - ('job_pool', None), ('stop_on_incorrect', None))) - std = kwargs['std'] - grader = kwargs['grader'] - max_workers = kwargs['max_workers'] - job_pool = kwargs['job_pool'] - if kwargs['stop_on_incorrect'] is not None: + kwargs = unpack_kwargs( + "output", + kwargs, + ( + "std", + ("grader", DEFAULT_GRADER), + ("max_workers", -1), + ("job_pool", None), + ("stop_on_incorrect", None), + ), + ) + std = kwargs["std"] + grader = kwargs["grader"] + max_workers = kwargs["max_workers"] + job_pool = kwargs["job_pool"] + if kwargs["stop_on_incorrect"] is not None: log.warn("parameter stop_on_incorrect is deprecated and has no effect.") if (max_workers is None or max_workers >= 0) and job_pool is None: max_workers = cls.__normal_max_workers(max_workers) try: from concurrent.futures import ThreadPoolExecutor + with ThreadPoolExecutor(max_workers=max_workers) as job_pool: - return cls.output(*files, std=std, grader=grader, max_workers=max_workers, job_pool=job_pool) + return cls.output( + *files, + std=std, + grader=grader, + max_workers=max_workers, + job_pool=job_pool + ) except ImportError: pass def get_std(): return cls.__process_file(std)[1] + if job_pool is not None: std = job_pool.submit(get_std).result() else: @@ -86,61 +103,121 @@ def do(file): @classmethod def program(cls, *programs, **kwargs): - kwargs = unpack_kwargs('program', kwargs, ('input', ('std', None), ('std_program', None), - ('grader', DEFAULT_GRADER), ('max_workers', -1), - ('job_pool', None), ('stop_on_incorrect', None))) - input = kwargs['input'] - std = kwargs['std'] - std_program = kwargs['std_program'] - grader = kwargs['grader'] - max_workers = kwargs['max_workers'] - job_pool = kwargs['job_pool'] - if kwargs['stop_on_incorrect'] is not None: + kwargs = unpack_kwargs( + "program", + kwargs, + ( + "input", + ("std", None), + ("std_program", None), + ("grader", DEFAULT_GRADER), + ("max_workers", -1), + ("job_pool", None), + ("stop_on_incorrect", None), + ), + ) + input = kwargs["input"] + std = kwargs["std"] + std_program = kwargs["std_program"] + grader = kwargs["grader"] + max_workers = kwargs["max_workers"] + job_pool = kwargs["job_pool"] + time_limit = kwargs["time_limit"] + memory_limit = kwargs["memory_limit"] + + if kwargs["stop_on_incorrect"] is not None: log.warn("parameter stop_on_incorrect is deprecated and has no effect.") if (max_workers is None or max_workers >= 0) and job_pool is None: max_workers = cls.__normal_max_workers(max_workers) try: from concurrent.futures import ThreadPoolExecutor + with ThreadPoolExecutor(max_workers=max_workers) as job_pool: - return cls.program(*programs, input=input, std=std, std_program=std_program, grader=grader, max_workers=max_workers, job_pool=job_pool) + return cls.program( + *programs, + input=input, + std=std, + std_program=std_program, + grader=grader, + max_workers=max_workers, + job_pool=job_pool + ) except ImportError: pass if not isinstance(input, IO): - raise TypeError("expect {}, got {}".format(type(IO).__name__, type(input).__name__)) + raise TypeError( + "expect {}, got {}".format(type(IO).__name__, type(input).__name__) + ) input.flush_buffer() input.input_file.seek(0) if std_program is not None: + def get_std(): - with open(os.dup(input.input_file.fileno()), 'r', newline='\n') as input_file: - content = make_unicode(subprocess.check_output(std_program, shell=(not list_like(std_program)), stdin=input.input_file, universal_newlines=True)) + with open( + os.dup(input.input_file.fileno()), "r", newline="\n" + ) as input_file: + content = make_unicode( + subprocess.check_output( + std_program, + shell=(not list_like(std_program)), + stdin=input.input_file, + universal_newlines=True, + ) + ) input_file.seek(0) return content + if job_pool is not None: std = job_pool.submit(get_std).result() else: std = get_std() elif std is not None: + def get_std(): return cls.__process_file(std)[1] + if job_pool is not None: std = job_pool.submit(get_std).result() else: std = get_std() else: - raise TypeError('program() missing 1 required non-None keyword-only argument: \'std\' or \'std_program\'') + raise TypeError( + "program() missing 1 required non-None keyword-only argument: 'std' or 'std_program'" + ) def do(program_name): timeout = None - if list_like(program_name) and len(program_name) == 2 and int_like(program_name[-1]): + if ( + list_like(program_name) + and len(program_name) == 2 + and int_like(program_name[-1]) + ): program_name, timeout = program_name - with open(os.dup(input.input_file.fileno()), 'r', newline='\n') as input_file: + with open( + os.dup(input.input_file.fileno()), "r", newline="\n" + ) as input_file: if timeout is None: - content = make_unicode(subprocess.check_output(program_name, shell=(not list_like(program_name)), stdin=input_file, universal_newlines=True)) + content = make_unicode( + subprocess.check_output( + program_name, + shell=(not list_like(program_name)), + stdin=input_file, + universal_newlines=True, + ) + ) else: - content = make_unicode(subprocess.check_output(program_name, shell=(not list_like(program_name)), stdin=input_file, universal_newlines=True, timeout=timeout)) + content = make_unicode( + subprocess.check_output( + program_name, + shell=(not list_like(program_name)), + stdin=input_file, + universal_newlines=True, + timeout=timeout, + ) + ) input_file.seek(0) cls.__compare_two(program_name, content, std, grader) diff --git a/cyaron/consts.py b/cyaron/consts.py index c838ef3..e6fab00 100644 --- a/cyaron/consts.py +++ b/cyaron/consts.py @@ -18,7 +18,7 @@ ALPHABET_CAPITAL = string.ascii_uppercase ALPHABET = ALPHABET_SMALL + ALPHABET_CAPITAL NUMBERS = string.digits -SENTENCE_SEPARATORS = ',,,,,,,;;:' # 70% ',' 20% ';' 10% ':' -SENTENCE_TERMINATORS = '....!' # 80% '.' 20% '!' +SENTENCE_SEPARATORS = ",,,,,,,;;:" # 70% ',' 20% ';' 10% ':' +SENTENCE_TERMINATORS = "....!" # 80% '.' 20% '!' DEFAULT_GRADER = "NOIPStyle" diff --git a/cyaron/graders/fulltext.py b/cyaron/graders/fulltext.py index 8460b6f..b5dbb07 100644 --- a/cyaron/graders/fulltext.py +++ b/cyaron/graders/fulltext.py @@ -2,9 +2,13 @@ from .graderregistry import CYaRonGraders from .mismatch import HashMismatch + @CYaRonGraders.grader("FullText") def fulltext(content, std): - content_hash = hashlib.sha256(content.encode('utf-8')).hexdigest() - std_hash = hashlib.sha256(std.encode('utf-8')).hexdigest() - return (True, None) if content_hash == std_hash else (False, HashMismatch(content, std, content_hash, std_hash)) - + content_hash = hashlib.sha256(content.encode("utf-8")).hexdigest() + std_hash = hashlib.sha256(std.encode("utf-8")).hexdigest() + return ( + (True, None) + if content_hash == std_hash + else (False, HashMismatch(content, std, content_hash, std_hash)) + ) diff --git a/cyaron/graders/graderregistry.py b/cyaron/graders/graderregistry.py index 2fd1419..702e39d 100644 --- a/cyaron/graders/graderregistry.py +++ b/cyaron/graders/graderregistry.py @@ -15,4 +15,4 @@ def check(self, name): return name in self._registry -CYaRonGraders = GraderRegistry() \ No newline at end of file +CYaRonGraders = GraderRegistry() diff --git a/cyaron/graders/mismatch.py b/cyaron/graders/mismatch.py index 70c2dfc..7c8eb21 100644 --- a/cyaron/graders/mismatch.py +++ b/cyaron/graders/mismatch.py @@ -1,5 +1,6 @@ class Mismatch(ValueError): """exception for content mismatch""" + def __init__(self, content, std, *args): """ content -> content got @@ -9,8 +10,10 @@ def __init__(self, content, std, *args): self.content = content self.std = std + class HashMismatch(Mismatch): """exception for hash mismatch""" + def __init__(self, content, std, content_hash, std_hash): """ content -> content got @@ -23,11 +26,25 @@ def __init__(self, content, std, content_hash, std_hash): self.std_hash = std_hash def __str__(self): - return "Hash mismatch: read %s, expected %s" % (self.content_hash, self.std_hash) + return "Hash mismatch: read %s, expected %s" % ( + self.content_hash, + self.std_hash, + ) + class TextMismatch(Mismatch): """exception for text mismatch""" - def __init__(self, content, std, err_msg, lineno=None, colno=None, content_token=None, std_token=None): + + def __init__( + self, + content, + std, + err_msg, + lineno=None, + colno=None, + content_token=None, + std_token=None, + ): """ content -> content got std -> content expected @@ -37,7 +54,9 @@ def __init__(self, content, std, err_msg, lineno=None, colno=None, content_token content_token -> the token of content mismatch std_token -> the token of std """ - super(TextMismatch, self).__init__(content, std, err_msg, lineno, colno, content_token, std_token) + super(TextMismatch, self).__init__( + content, std, err_msg, lineno, colno, content_token, std_token + ) self.err_msg = err_msg.format(lineno, colno, content_token, std_token) self.lineno = lineno self.colno = colno diff --git a/cyaron/graders/noipstyle.py b/cyaron/graders/noipstyle.py index bf6fa21..679e059 100644 --- a/cyaron/graders/noipstyle.py +++ b/cyaron/graders/noipstyle.py @@ -5,28 +5,46 @@ @CYaRonGraders.grader("NOIPStyle") def noipstyle(content, std): - content_lines = strtolines(content.replace('\r\n', '\n')) - std_lines = strtolines(std.replace('\r\n', '\n')) + content_lines = strtolines(content.replace("\r\n", "\n")) + std_lines = strtolines(std.replace("\r\n", "\n")) if len(content_lines) != len(std_lines): - return False, TextMismatch(content, std, 'Too many or too few lines.') + return False, TextMismatch(content, std, "Too many or too few lines.") for i in range(len(content_lines)): if std_lines[i] != content_lines[i]: for j in range(min(len(std_lines[i]), len(content_lines[i]))): if std_lines[i][j] != content_lines[i][j]: - return (False, - TextMismatch( - content, std, - 'On line {} column {}, read {}, expected {}.', - i + 1, j + 1, content_lines[i][j:j + 5], - std_lines[i][j:j + 5])) + return ( + False, + TextMismatch( + content, + std, + "On line {} column {}, read {}, expected {}.", + i + 1, + j + 1, + content_lines[i][j : j + 5], + std_lines[i][j : j + 5], + ), + ) if len(std_lines[i]) > len(content_lines[i]): return False, TextMismatch( - content, std, 'Too short on line {}.', i + 1, j + 1, - content_lines[i][j:j + 5], std_lines[i][j:j + 5]) + content, + std, + "Too short on line {}.", + i + 1, + j + 1, + content_lines[i][j : j + 5], + std_lines[i][j : j + 5], + ) if len(std_lines[i]) < len(content_lines[i]): return False, TextMismatch( - content, std, 'Too long on line {}.', i + 1, j + 1, - content_lines[i][j:j + 5], std_lines[i][j:j + 5]) + content, + std, + "Too long on line {}.", + i + 1, + j + 1, + content_lines[i][j : j + 5], + std_lines[i][j : j + 5], + ) return True, None diff --git a/cyaron/graph.py b/cyaron/graph.py index 34add04..c95e8b5 100644 --- a/cyaron/graph.py +++ b/cyaron/graph.py @@ -4,12 +4,13 @@ class Edge: """Class Edge: A class of the edge in the graph""" + def __init__(self, u, v, w): """__init__(self, u, v, w) -> None - Initialize a edge. - int u -> the start vertex - int v -> the end vertex - int w -> the weight. + Initialize a edge. + int u -> the start vertex + int v -> the end vertex + int w -> the weight. """ self.start = u self.end = v @@ -17,35 +18,36 @@ def __init__(self, u, v, w): def __str__(self): """__str__(self) -> str - Return a string to output the edge. The string contains the start vertex, end vertex and weight(u,v,w) and splits with space. + Return a string to output the edge. The string contains the start vertex, end vertex and weight(u,v,w) and splits with space. """ return "%d %d %d" % (self.start, self.end, self.weight) @staticmethod def unweighted_edge(edge): """unweighted_edge(edge) -> str - Return a string to output the edge without weight. The string contains the start vertex, end vertex(u,v) and splits with space. + Return a string to output the edge without weight. The string contains the start vertex, end vertex(u,v) and splits with space. """ - return '%d %d'%(edge.start,edge.end) + return "%d %d" % (edge.start, edge.end) + class Graph: - """Class Graph: A class of the graph - """ + """Class Graph: A class of the graph""" + def __init__(self, point_count, directed=False): """__init__(self, point_count) -> None - Initialize a graph. - int point_count -> the count of the vertexes in the graph. - bool directed = False -> whether the graph is directed(true:directed,false:not directed) + Initialize a graph. + int point_count -> the count of the vertexes in the graph. + bool directed = False -> whether the graph is directed(true:directed,false:not directed) """ self.directed = directed self.edges = [[] for i in range(point_count + 1)] def to_str(self, **kwargs): """to_str(self, **kwargs) -> str - Convert the graph to string with format. Splits with "\n" - **kwargs(Keyword args): - bool shuffle = False -> whether shuffle the output or not - str output(Edge) = str -> the convert function which converts object Edge to str. the default way is to use str() + Convert the graph to string with format. Splits with "\n" + **kwargs(Keyword args): + bool shuffle = False -> whether shuffle the output or not + str output(Edge) = str -> the convert function which converts object Edge to str. the default way is to use str() """ shuffle = kwargs.get("shuffle", False) output = kwargs.get("output", str) @@ -57,7 +59,8 @@ def to_str(self, **kwargs): edge_buf = [] for edge in self.iterate_edges(): edge_buf.append( - Edge(new_node_id[edge.start], new_node_id[edge.end], edge.weight)) + Edge(new_node_id[edge.start], new_node_id[edge.end], edge.weight) + ) random.shuffle(edge_buf) for edge in edge_buf: if not self.directed and random.randint(0, 1) == 0: @@ -70,13 +73,13 @@ def to_str(self, **kwargs): def __str__(self): """__str__(self) -> str - Return a string to output the graph. The string contains all the edges of the graph, splits with "\n". + Return a string to output the graph. The string contains all the edges of the graph, splits with "\n". """ return self.to_str() def iterate_edges(self): """iterate_edges(self) -> Edge - Iter the graph. Order by the start vertex. + Iter the graph. Order by the start vertex. """ for node in self.edges: for edge in node: @@ -85,16 +88,16 @@ def iterate_edges(self): def __add_edge(self, x, y, w): """__add_edge(self, x, y, w) -> None - Add an edge to the graph. + Add an edge to the graph. """ self.edges[x].append(Edge(x, y, w)) def add_edge(self, x, y, **kwargs): """add_edge(self, x, y, **kwargs) -> None - int x -> the start vertex - int y -> the end vertex - **kwargs(Keyword args): - int weight = 1 -> the weight + int x -> the start vertex + int y -> the end vertex + **kwargs(Keyword args): + int weight = 1 -> the weight """ weight = kwargs.get("weight", 1) self.__add_edge(x, y, weight) @@ -104,56 +107,56 @@ def add_edge(self, x, y, **kwargs): @staticmethod def chain(point_count, **kwargs): """chain(point_count, **kwargs) -> Graph - Factory method. Return a chain graph with point_count vertexes. - int point_count -> the count of vertexes - **kwargs(Keyword args): - bool directed = True -> whether the chain is directed(true:directed,false:not directed) - (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) - int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) - int/float weight_gen() - = lambda: random.randint(weight_limit[0], weight_limit[1]) - -> the generator of the weights. It should return the weight. The default way is to use the random.randint() + Factory method. Return a chain graph with point_count vertexes. + int point_count -> the count of vertexes + **kwargs(Keyword args): + bool directed = True -> whether the chain is directed(true:directed,false:not directed) + (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) + int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) + int/float weight_gen() + = lambda: random.randint(weight_limit[0], weight_limit[1]) + -> the generator of the weights. It should return the weight. The default way is to use the random.randint() """ return Graph.tree(point_count, 1, 0, **kwargs) @staticmethod def flower(point_count, **kwargs): """flower(point_count, **kwargs) -> Graph - Factory method. Return a flower graph with point_count vertexes. - int point_count -> the count of vertexes - **kwargs(Keyword args): - bool directed = True -> whether the chain is directed(true:directed,false:not directed) - (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) - int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) - int/float weight_gen() - = lambda: random.randint(weight_limit[0], weight_limit[1]) - -> the generator of the weights. It should return the weight. The default way is to use the random.randint() + Factory method. Return a flower graph with point_count vertexes. + int point_count -> the count of vertexes + **kwargs(Keyword args): + bool directed = True -> whether the chain is directed(true:directed,false:not directed) + (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) + int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) + int/float weight_gen() + = lambda: random.randint(weight_limit[0], weight_limit[1]) + -> the generator of the weights. It should return the weight. The default way is to use the random.randint() """ return Graph.tree(point_count, 0, 1, **kwargs) @staticmethod def tree(point_count, chain=0, flower=0, **kwargs): """tree(point_count, chain=0, flower=0, **kwargs) -> Graph - Factory method. Return a tree with point_count vertexes. - int point_count -> the count of vertexes - float chain = 0 -> 1 means the tree is a chain - float flower = 0 -> 1 means the tree is a flower - NOTICE:only either chain or flower can be True - **kwargs(Keyword args): - bool directed = False -> whether the chain is directed(true:directed,false:not directed) - (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) - int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) - int/float weight_gen() - = lambda: random.randint(weight_limit[0], weight_limit[1]) - -> the generator of the weights. It should return the weight. The default way is to use the random.randint() + Factory method. Return a tree with point_count vertexes. + int point_count -> the count of vertexes + float chain = 0 -> 1 means the tree is a chain + float flower = 0 -> 1 means the tree is a flower + NOTICE:only either chain or flower can be True + **kwargs(Keyword args): + bool directed = False -> whether the chain is directed(true:directed,false:not directed) + (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) + int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) + int/float weight_gen() + = lambda: random.randint(weight_limit[0], weight_limit[1]) + -> the generator of the weights. It should return the weight. The default way is to use the random.randint() """ directed = kwargs.get("directed", False) weight_limit = kwargs.get("weight_limit", (1, 1)) if not list_like(weight_limit): weight_limit = (1, weight_limit) weight_gen = kwargs.get( - "weight_gen", lambda: random.randint( - weight_limit[0], weight_limit[1])) + "weight_gen", lambda: random.randint(weight_limit[0], weight_limit[1]) + ) if not 0 <= chain <= 1 or not 0 <= flower <= 1: raise Exception("chain and flower must be between 0 and 1") @@ -182,51 +185,51 @@ def tree(point_count, chain=0, flower=0, **kwargs): @staticmethod def binary_tree(point_count, left=0, right=0, **kwargs): """binary_tree(point_count, left=0, right=0, **kwargs) -> Graph - Factory method. Return a binary tree with point_count vertexes. - int point_count -> the count of vertexes - float left = 0 -> random arg. should be in [0,1] - float right = 0 -> random arg. should be in [0,1] - NOTICE:left+right mustn't be greater than 1 - **kwargs(Keyword args): - bool directed = False -> whether the binary tree is directed(true:directed,false:not directed) - (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) - int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) - int/float weight_gen() - = lambda: random.randint(weight_limit[0], weight_limit[1]) - -> the generator of the weights. It should return the weight. The default way is to use the random.randint() + Factory method. Return a binary tree with point_count vertexes. + int point_count -> the count of vertexes + float left = 0 -> random arg. should be in [0,1] + float right = 0 -> random arg. should be in [0,1] + NOTICE:left+right mustn't be greater than 1 + **kwargs(Keyword args): + bool directed = False -> whether the binary tree is directed(true:directed,false:not directed) + (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) + int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) + int/float weight_gen() + = lambda: random.randint(weight_limit[0], weight_limit[1]) + -> the generator of the weights. It should return the weight. The default way is to use the random.randint() """ directed = kwargs.get("directed", False) weight_limit = kwargs.get("weight_limit", (1, 1)) if not list_like(weight_limit): weight_limit = (1, weight_limit) weight_gen = kwargs.get( - "weight_gen", lambda: random.randint( - weight_limit[0], weight_limit[1])) + "weight_gen", lambda: random.randint(weight_limit[0], weight_limit[1]) + ) if not 0 <= left <= 1 or not 0 <= right <= 1: raise Exception("left and right must be between 0 and 1") if left + right > 1: raise Exception("left plus right must be smaller than 1") - - can_left=[1] - can_right=[1] + + can_left = [1] + can_right = [1] graph = Graph(point_count, directed) for i in range(2, point_count + 1): edge_pos = random.random() node = 0 # Left if edge_pos < left or left + right < edge_pos <= (1.0 - left - right) / 2: - point_index = random.randint(0,len(can_left)-1) + point_index = random.randint(0, len(can_left) - 1) node = can_left[point_index] - del_last_node = can_left.pop() # Save a copy of the last element + del_last_node = can_left.pop() # Save a copy of the last element if point_index < len(can_left): # If the chosen element isn't the last one, # Copy the last one to the position of the chosen one can_left[point_index] = del_last_node # Right else: - # elif left <= edge_pos <= left + right or (1.0 - left - right) / 2 < edge_pos < 1: - point_index = random.randint(0,len(can_right)-1) + # elif left <= edge_pos <= left + right or (1.0 - left - right) / 2 < edge_pos < 1: + point_index = random.randint(0, len(can_right) - 1) node = can_right[point_index] del_last_node = can_right.pop() if point_index < len(can_right): @@ -240,18 +243,18 @@ def binary_tree(point_count, left=0, right=0, **kwargs): @staticmethod def graph(point_count, edge_count, **kwargs): """graph(point_count, edge_count, **kwargs) -> Graph - Factory method. Return a graph with point_count vertexes and edge_count edges. - int point_count -> the count of vertexes - int edge_count -> the count of edges - **kwargs(Keyword args): - bool self_loop = True -> whether to allow self loops or not - bool repeated_edges = True -> whether to allow repeated edges or not - bool directed = False -> whether the chain is directed(true:directed,false:not directed) - (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) - int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) - int/float weight_gen() - = lambda: random.randint(weight_limit[0], weight_limit[1]) - -> the generator of the weights. It should return the weight. The default way is to use the random.randint() + Factory method. Return a graph with point_count vertexes and edge_count edges. + int point_count -> the count of vertexes + int edge_count -> the count of edges + **kwargs(Keyword args): + bool self_loop = True -> whether to allow self loops or not + bool repeated_edges = True -> whether to allow repeated edges or not + bool directed = False -> whether the chain is directed(true:directed,false:not directed) + (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) + int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) + int/float weight_gen() + = lambda: random.randint(weight_limit[0], weight_limit[1]) + -> the generator of the weights. It should return the weight. The default way is to use the random.randint() """ directed = kwargs.get("directed", False) self_loop = kwargs.get("self_loop", True) @@ -260,8 +263,8 @@ def graph(point_count, edge_count, **kwargs): if not list_like(weight_limit): weight_limit = (1, weight_limit) weight_gen = kwargs.get( - "weight_gen", lambda: random.randint( - weight_limit[0], weight_limit[1])) + "weight_gen", lambda: random.randint(weight_limit[0], weight_limit[1]) + ) graph = Graph(point_count, directed) used_edges = set() i = 0 @@ -269,7 +272,9 @@ def graph(point_count, edge_count, **kwargs): u = random.randint(1, point_count) v = random.randint(1, point_count) - if (not self_loop and u == v) or (not repeated_edges and (u, v) in used_edges): + if (not self_loop and u == v) or ( + not repeated_edges and (u, v) in used_edges + ): # Then we generate a new pair of nodes continue @@ -286,32 +291,34 @@ def graph(point_count, edge_count, **kwargs): @staticmethod def DAG(point_count, edge_count, **kwargs): """DAG(point_count, edge_count, **kwargs) -> Graph - Factory method. Return a graph with point_count vertexes and edge_count edges. - int point_count -> the count of vertexes - int edge_count -> the count of edges - **kwargs(Keyword args): - bool self_loop = False -> whether to allow self loops or not - bool repeated_edges = True -> whether to allow repeated edges or not - bool loop = False -> whether to allow loops or not - (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) - int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) - int/float weight_gen() - = lambda: random.randint(weight_limit[0], weight_limit[1]) - -> the generator of the weights. It should return the weight. The default way is to use the random.randint() + Factory method. Return a graph with point_count vertexes and edge_count edges. + int point_count -> the count of vertexes + int edge_count -> the count of edges + **kwargs(Keyword args): + bool self_loop = False -> whether to allow self loops or not + bool repeated_edges = True -> whether to allow repeated edges or not + bool loop = False -> whether to allow loops or not + (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) + int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) + int/float weight_gen() + = lambda: random.randint(weight_limit[0], weight_limit[1]) + -> the generator of the weights. It should return the weight. The default way is to use the random.randint() """ if edge_count < point_count - 1: - raise Exception("the number of edges of connected graph must more than the number of nodes - 1") + raise Exception( + "the number of edges of connected graph must more than the number of nodes - 1" + ) - self_loop = kwargs.get("self_loop", False) # DAG default has no loop + self_loop = kwargs.get("self_loop", False) # DAG default has no loop repeated_edges = kwargs.get("repeated_edges", True) loop = kwargs.get("loop", False) weight_limit = kwargs.get("weight_limit", (1, 1)) if not list_like(weight_limit): weight_limit = (1, weight_limit) weight_gen = kwargs.get( - "weight_gen", lambda: random.randint( - weight_limit[0], weight_limit[1])) - + "weight_gen", lambda: random.randint(weight_limit[0], weight_limit[1]) + ) + used_edges = set() edge_buf = list(Graph.tree(point_count, weight_gen=weight_gen).iterate_edges()) graph = Graph(point_count, directed=True) @@ -320,10 +327,10 @@ def DAG(point_count, edge_count, **kwargs): if loop and random.randint(1, 2) == 1: edge.start, edge.end = edge.end, edge.start graph.add_edge(edge.start, edge.end, weight=edge.weight) - + if not repeated_edges: used_edges.add((edge.start, edge.end)) - + i = point_count - 1 while i < edge_count: u = random.randint(1, point_count) @@ -332,7 +339,9 @@ def DAG(point_count, edge_count, **kwargs): if not loop and u > v: u, v = v, u - if (not self_loop and u == v) or (not repeated_edges and (u, v) in used_edges): + if (not self_loop and u == v) or ( + not repeated_edges and (u, v) in used_edges + ): # Then we generate a new pair of nodes continue @@ -348,20 +357,22 @@ def DAG(point_count, edge_count, **kwargs): @staticmethod def UDAG(point_count, edge_count, **kwargs): """UDAG(point_count, edge_count, **kwargs) -> Graph - Factory method. Return a graph with point_count vertexes and edge_count edges. - int point_count -> the count of vertexes - int edge_count -> the count of edges - **kwargs(Keyword args): - bool self_loop = True -> whether to allow self loops or not - bool repeated_edges = True -> whether to allow repeated edges or not - (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) - int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) - int/float weight_gen() - = lambda: random.randint(weight_limit[0], weight_limit[1]) - -> the generator of the weights. It should return the weight. The default way is to use the random.randint() + Factory method. Return a graph with point_count vertexes and edge_count edges. + int point_count -> the count of vertexes + int edge_count -> the count of edges + **kwargs(Keyword args): + bool self_loop = True -> whether to allow self loops or not + bool repeated_edges = True -> whether to allow repeated edges or not + (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) + int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) + int/float weight_gen() + = lambda: random.randint(weight_limit[0], weight_limit[1]) + -> the generator of the weights. It should return the weight. The default way is to use the random.randint() """ - if edge_count < point_count - 1: - raise Exception("the number of edges of connected graph must more than the number of nodes - 1") + if edge_count < point_count - 1: + raise Exception( + "the number of edges of connected graph must more than the number of nodes - 1" + ) self_loop = kwargs.get("self_loop", True) repeated_edges = kwargs.get("repeated_edges", True) @@ -369,9 +380,9 @@ def UDAG(point_count, edge_count, **kwargs): if not list_like(weight_limit): weight_limit = (1, weight_limit) weight_gen = kwargs.get( - "weight_gen", lambda: random.randint( - weight_limit[0], weight_limit[1])) - + "weight_gen", lambda: random.randint(weight_limit[0], weight_limit[1]) + ) + used_edges = set() graph = Graph.tree(point_count, weight_gen=weight_gen, directed=False) @@ -379,13 +390,15 @@ def UDAG(point_count, edge_count, **kwargs): if not repeated_edges: used_edges.add((edge.start, edge.end)) used_edges.add((edge.end, edge.start)) - + i = point_count - 1 while i < edge_count: u = random.randint(1, point_count) v = random.randint(1, point_count) - if (not self_loop and u == v) or (not repeated_edges and (u, v) in used_edges): + if (not self_loop and u == v) or ( + not repeated_edges and (u, v) in used_edges + ): # Then we generate a new pair of nodes continue @@ -402,16 +415,16 @@ def UDAG(point_count, edge_count, **kwargs): @staticmethod def hack_spfa(point_count, **kwargs): """hack_spfa(point_count, **kwargs) -> None - Factory method. Return a spfa graph with point_count vertexes - int point_count -> the count of vertexes - **kwargs(Keyword args): - bool directed = False -> whether the chain is directed(true:directed,false:not directed) - (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) - int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) - int extra_edge = 2 -> the number of extra edges - int/float weight_gen() - = lambda: random.randint(weight_limit[0], weight_limit[1]) - -> the generator of the weights. It should return the weight. The default way is to use the random.randint() + Factory method. Return a spfa graph with point_count vertexes + int point_count -> the count of vertexes + **kwargs(Keyword args): + bool directed = False -> whether the chain is directed(true:directed,false:not directed) + (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) + int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) + int extra_edge = 2 -> the number of extra edges + int/float weight_gen() + = lambda: random.randint(weight_limit[0], weight_limit[1]) + -> the generator of the weights. It should return the weight. The default way is to use the random.randint() """ directed = kwargs.get("directed", False) extraedg = kwargs.get("extra_edge", 2) @@ -419,8 +432,8 @@ def hack_spfa(point_count, **kwargs): if not list_like(weight_limit): weight_limit = (1, weight_limit) weight_gen = kwargs.get( - "weight_gen", lambda: random.randint( - weight_limit[0], weight_limit[1])) + "weight_gen", lambda: random.randint(weight_limit[0], weight_limit[1]) + ) point_to_skip = point_count + 3 graph = Graph(point_count, directed) @@ -430,15 +443,18 @@ def hack_spfa(point_count, **kwargs): for i in range(1, half): (x, y) = (i, i + 1) - graph.add_edge(x + (x >= point_to_skip), y + - (y >= point_to_skip), weight=weight_gen()) + graph.add_edge( + x + (x >= point_to_skip), y + (y >= point_to_skip), weight=weight_gen() + ) (x, y) = (i + half, i + half + 1) - graph.add_edge(x + (x >= point_to_skip), y + - (y >= point_to_skip), weight=weight_gen()) + graph.add_edge( + x + (x >= point_to_skip), y + (y >= point_to_skip), weight=weight_gen() + ) for i in range(1, half + 1): (x, y) = (i, i + half) - graph.add_edge(x + (x >= point_to_skip), y + - (y >= point_to_skip), weight=weight_gen()) + graph.add_edge( + x + (x >= point_to_skip), y + (y >= point_to_skip), weight=weight_gen() + ) for i in range(extraedg): u = random.randint(1, point_count) diff --git a/cyaron/io.py b/cyaron/io.py index 1880143..2c00ca7 100644 --- a/cyaron/io.py +++ b/cyaron/io.py @@ -10,39 +10,53 @@ class IO(object): """Class IO: IO tool class. It will process the input and output files.""" - def __init__(self, input_file=None, output_file=None, data_id=None, file_prefix=None, input_suffix='.in', output_suffix='.out', disable_output=False): + + def __init__( + self, + input_file=None, + output_file=None, + data_id=None, + file_prefix=None, + input_suffix=".in", + output_suffix=".out", + disable_output=False, + ): """__init__(self, input_file=None, output_file=None, data_id=None, file_prefix=None, input_suffix='.in', output_suffix='.out', disable_output=False) -> None - input_file, output_file overload: - None -> make a temp file (if file_prefix is None) - file object -> treat the file-like object as in/output file - int -> open file by file descriptor - str -> a filename or filename template like 'awd{}.in'. ``{}`` will be replaced by ``data_id`` - int data_id -> the id of the data. if it's None, the file names will not contain the id. - legacy argumants: - str file_prefix -> the prefix for the input and output files - str input_suffix = ".in" -> the suffix of the input file - str output_suffix = ".out" -> the suffix of the output file - disable_output -> bool, set to True to disable output - Examples: - IO("a","b") -> create input file "a" and output file "b" - IO("a.in","b.out") -> create input file "a.in" and output file "b.out" - IO(file_prefix="data") -> create input file "data.in" and output file "data.out" - IO(file_prefix="data",data_id=1) -> create input file "data1.in" and output file "data1.out" - IO(file_prefix="data",input_suffix=".input") -> create input file "data.input" and output file "data.out" - IO(file_prefix="data",output_suffix=".output") -> create input file "data.in" and output file "data.output" - IO(file_prefix="data",data_id=2,input_suffix=".input") -> create input file "data2.input" and output file "data2.out" - IO("data{}.in","data{}.out",data_id=2) -> create input file "data2.in" and output file "data2.out" - IO(open('data.in', 'w+'), open('data.out', 'w+')) -> input file "data.in" and output file "data.out" + input_file, output_file overload: + None -> make a temp file (if file_prefix is None) + file object -> treat the file-like object as in/output file + int -> open file by file descriptor + str -> a filename or filename template like 'awd{}.in'. ``{}`` will be replaced by ``data_id`` + int data_id -> the id of the data. if it's None, the file names will not contain the id. + legacy argumants: + str file_prefix -> the prefix for the input and output files + str input_suffix = ".in" -> the suffix of the input file + str output_suffix = ".out" -> the suffix of the output file + disable_output -> bool, set to True to disable output + Examples: + IO("a","b") -> create input file "a" and output file "b" + IO("a.in","b.out") -> create input file "a.in" and output file "b.out" + IO(file_prefix="data") -> create input file "data.in" and output file "data.out" + IO(file_prefix="data",data_id=1) -> create input file "data1.in" and output file "data1.out" + IO(file_prefix="data",input_suffix=".input") -> create input file "data.input" and output file "data.out" + IO(file_prefix="data",output_suffix=".output") -> create input file "data.in" and output file "data.output" + IO(file_prefix="data",data_id=2,input_suffix=".input") -> create input file "data2.input" and output file "data2.out" + IO("data{}.in","data{}.out",data_id=2) -> create input file "data2.in" and output file "data2.out" + IO(open('data.in', 'w+'), open('data.out', 'w+')) -> input file "data.in" and output file "data.out" """ if file_prefix is not None: # legacy mode - input_file = '{}{{}}{}'.format(self.__escape_format(file_prefix), self.__escape_format(input_suffix)) - output_file = '{}{{}}{}'.format(self.__escape_format(file_prefix), self.__escape_format(output_suffix)) + input_file = "{}{{}}{}".format( + self.__escape_format(file_prefix), self.__escape_format(input_suffix) + ) + output_file = "{}{{}}{}".format( + self.__escape_format(file_prefix), self.__escape_format(output_suffix) + ) self.input_filename, self.output_filename = None, None self.__input_temp, self.__output_temp = False, False - self.__init_file(input_file, data_id, 'i') + self.__init_file(input_file, data_id, "i") if not disable_output: - self.__init_file(output_file, data_id, 'o') + self.__init_file(output_file, data_id, "o") else: self.output_file = None self.__closed = False @@ -55,34 +69,34 @@ def __init_file(self, f, data_id, file_type): is_file = False if isinstance(f, IOBase) or is_file: # consider ``f`` as a file object - if file_type == 'i': + if file_type == "i": self.input_file = f else: self.output_file = f elif isinstance(f, int): # consider ``f`` as a file descor - self.__init_file(open(f, 'w+', newline='\n'), data_id, file_type) + self.__init_file(open(f, "w+", newline="\n"), data_id, file_type) elif f is None: # consider wanna temp file fd, self.input_filename = tempfile.mkstemp() self.__init_file(fd, data_id, file_type) - if file_type == 'i': + if file_type == "i": self.__input_temp = True else: self.__output_temp = True else: # consider ``f`` as filename template - filename = f.format(data_id or '') - if file_type == 'i': + filename = f.format(data_id or "") + if file_type == "i": self.input_filename = filename log.debug("Processing %s" % self.input_filename) else: self.output_filename = filename - self.__init_file(open(filename, 'w+', newline='\n'), data_id, file_type) + self.__init_file(open(filename, "w+", newline="\n"), data_id, file_type) def __escape_format(self, st): """replace "{}" to "{{}}" """ - return re.sub(r'\{', '{{', re.sub(r'\}', '}}', st)) + return re.sub(r"\{", "{{", re.sub(r"\}", "}}", st)) def __del_files(self): """delete files""" @@ -125,10 +139,10 @@ def __exit__(self, exc_type, exc_val, exc_tb): def __write(self, file, *args, **kwargs): """__write(self, file, *args, **kwargs) -> None - Write every element in *args into file. If the element isn't "\n", insert a space. It will convert every element into str - file file -> the file object to write - **kwargs: - str separator = " " -> a string used to separate every element + Write every element in *args into file. If the element isn't "\n", insert a space. It will convert every element into str + file file -> the file object to write + **kwargs: + str separator = " " -> a string used to separate every element """ separator = kwargs.get("separator", " ") for arg in args: @@ -144,17 +158,17 @@ def __write(self, file, *args, **kwargs): def input_write(self, *args, **kwargs): """input_write(self, *args, **kwargs) -> None - Write every element in *args into the input file. Splits with spaces. It will convert every element into string - **kwargs: - str separator = " " -> a string used to separate every element + Write every element in *args into the input file. Splits with spaces. It will convert every element into string + **kwargs: + str separator = " " -> a string used to separate every element """ self.__write(self.input_file, *args, **kwargs) def input_writeln(self, *args, **kwargs): """input_writeln(self, *args, **kwargs) -> None - Write every element in *args into the input file and turn to a new line. Splits with spaces. It will convert every element into string - **kwargs: - str separator = " " -> a string used to separate every element + Write every element in *args into the input file and turn to a new line. Splits with spaces. It will convert every element into string + **kwargs: + str separator = " " -> a string used to separate every element """ args = list(args) args.append("\n") @@ -162,30 +176,36 @@ def input_writeln(self, *args, **kwargs): def output_gen(self, shell_cmd): """output_gen(self, shell_cmd) -> None - Run the command shell_cmd(usually the std programme) and send it the input file as stdin. Write its output to the output file. - str shell_cmd -> the command to run, usually the std programme + Run the command shell_cmd(usually the std programme) and send it the input file as stdin. Write its output to the output file. + str shell_cmd -> the command to run, usually the std programme """ self.flush_buffer() origin_pos = self.input_file.tell() self.input_file.seek(0) - subprocess.check_call(shell_cmd, shell=True, stdin=self.input_file, stdout=self.output_file, universal_newlines=True) + subprocess.check_call( + shell_cmd, + shell=True, + stdin=self.input_file, + stdout=self.output_file, + universal_newlines=True, + ) self.input_file.seek(origin_pos) log.debug(self.output_filename, " done") def output_write(self, *args, **kwargs): """output_write(self, *args, **kwargs) -> None - Write every element in *args into the output file. Splits with spaces. It will convert every element into string - **kwargs: - str separator = " " -> a string used to separate every element + Write every element in *args into the output file. Splits with spaces. It will convert every element into string + **kwargs: + str separator = " " -> a string used to separate every element """ self.__write(self.output_file, *args, **kwargs) def output_writeln(self, *args, **kwargs): """output_writeln(self, *args, **kwargs) -> None - Write every element in *args into the output file and turn to a new line. Splits with spaces. It will convert every element into string - **kwargs: - str separator = " " -> a string used to separate every element + Write every element in *args into the output file and turn to a new line. Splits with spaces. It will convert every element into string + **kwargs: + str separator = " " -> a string used to separate every element """ args = list(args) args.append("\n") diff --git a/cyaron/log.py b/cyaron/log.py index 33b3b0c..2dbb922 100644 --- a/cyaron/log.py +++ b/cyaron/log.py @@ -2,24 +2,30 @@ from functools import partial import sys from threading import Lock + try: import colorful except ImportError: + class colorful: def __getattr__(self, attr): return lambda st: st + colorful = colorful() from .utils import make_unicode __print = print + + def _print(*args, **kwargs): flush = False - if 'flush' in kwargs: - flush = kwargs['flush'] - del kwargs['flush'] + if "flush" in kwargs: + flush = kwargs["flush"] + del kwargs["flush"] __print(*args, **kwargs) if flush: - kwargs.get('file', sys.stdout).flush() + kwargs.get("file", sys.stdout).flush() + def _join_dict(a, b): """join two dict""" @@ -28,8 +34,11 @@ def _join_dict(a, b): c[k] = v return c + _log_funcs = {} _log_lock = Lock() + + def log(funcname, *args, **kwargs): """log with log function specified by ``funcname``""" _log_lock.acquire() @@ -37,6 +46,7 @@ def log(funcname, *args, **kwargs): _log_lock.release() return rv + """5 log levels 1. debug: debug info 2. info: common info @@ -45,11 +55,12 @@ def log(funcname, *args, **kwargs): 5. error: errors """ -debug = partial(log, 'debug') -info = partial(log, 'info') -print = partial(log, 'print') -warn = partial(log, 'warn') -error = partial(log, 'error') +debug = partial(log, "debug") +info = partial(log, "info") +print = partial(log, "print") +warn = partial(log, "warn") +error = partial(log, "error") + def register_logfunc(funcname, func): """register logfunc @@ -64,10 +75,21 @@ def register_logfunc(funcname, func): except KeyError: pass -_nb_print = lambda *args, **kwargs: _print(*args, **_join_dict(kwargs, {'flush': True})) -_nb_print_e = lambda *args, **kwargs: _print(*args, **_join_dict(kwargs, {'file': sys.stderr, 'flush': True})) -_cl_print = lambda color, *args, **kwargs: _nb_print(*[color(make_unicode(item)) for item in args], **kwargs) if sys.stdout.isatty() else _nb_print(*args, **kwargs) -_cl_print_e = lambda color, *args, **kwargs: _nb_print_e(*[color(make_unicode(item)) for item in args], **kwargs) if sys.stderr.isatty() else _nb_print_e(*args, **kwargs) + +_nb_print = lambda *args, **kwargs: _print(*args, **_join_dict(kwargs, {"flush": True})) +_nb_print_e = lambda *args, **kwargs: _print( + *args, **_join_dict(kwargs, {"file": sys.stderr, "flush": True}) +) +_cl_print = lambda color, *args, **kwargs: ( + _nb_print(*[color(make_unicode(item)) for item in args], **kwargs) + if sys.stdout.isatty() + else _nb_print(*args, **kwargs) +) +_cl_print_e = lambda color, *args, **kwargs: ( + _nb_print_e(*[color(make_unicode(item)) for item in args], **kwargs) + if sys.stderr.isatty() + else _nb_print_e(*args, **kwargs) +) _default_debug = partial(_cl_print, colorful.cyan) _default_info = partial(_cl_print, colorful.blue) @@ -75,28 +97,32 @@ def register_logfunc(funcname, func): _default_warn = partial(_cl_print_e, colorful.yellow) _default_error = partial(_cl_print_e, colorful.red) + def set_quiet(): """set log mode to "quiet" """ - register_logfunc('debug', None) - register_logfunc('info', None) - register_logfunc('print', _default_print) - register_logfunc('warn', None) - register_logfunc('error', _default_error) + register_logfunc("debug", None) + register_logfunc("info", None) + register_logfunc("print", _default_print) + register_logfunc("warn", None) + register_logfunc("error", _default_error) + def set_normal(): """set log mode to "normal" """ - register_logfunc('debug', None) - register_logfunc('info', _default_info) - register_logfunc('print', _default_print) - register_logfunc('warn', _default_warn) - register_logfunc('error', _default_error) + register_logfunc("debug", None) + register_logfunc("info", _default_info) + register_logfunc("print", _default_print) + register_logfunc("warn", _default_warn) + register_logfunc("error", _default_error) + def set_verbose(): """set log mode to "verbose" """ - register_logfunc('debug', _default_debug) - register_logfunc('info', _default_info) - register_logfunc('print', _default_print) - register_logfunc('warn', _default_warn) - register_logfunc('error', _default_error) + register_logfunc("debug", _default_debug) + register_logfunc("info", _default_info) + register_logfunc("print", _default_print) + register_logfunc("warn", _default_warn) + register_logfunc("error", _default_error) + set_normal() diff --git a/cyaron/math.py b/cyaron/math.py index eb7379d..a23d332 100644 --- a/cyaron/math.py +++ b/cyaron/math.py @@ -1,7 +1,7 @@ -#coding=utf8 -''' +# coding=utf8 +""" forked from https://blog.dreamshire.com/common-functions-routines-project-euler/ -''' +""" from __future__ import absolute_import from math import sqrt, ceil, gcd from functools import reduce @@ -10,8 +10,10 @@ fact = (1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880) -def help(): #Give help informations - help_txt=""" + + +def help(): # Give help informations + help_txt = """ Welcome to CYaRon/math.py help! Functions are: | factorial(n) - The factorial of n @@ -40,49 +42,73 @@ def help(): #Give help informations """ print(help_txt) -def factorial(n): return reduce(lambda x,y:x*y,range(1,n+1),1) -def is_perm(a,b): return sorted(str(a))==sorted(str(b)) +def factorial(n): + return reduce(lambda x, y: x * y, range(1, n + 1), 1) + + +def is_perm(a, b): + return sorted(str(a)) == sorted(str(b)) + -def is_palindromic(n): n=str(n); return n==n[::-1] +def is_palindromic(n): + n = str(n) + return n == n[::-1] -def is_pandigital(n, s=9): n=str(n); return len(n)==s and not '1234567890'[:s].strip(n) -#--- Calculate the sum of proper divisors for n-------------------------------------------------- +def is_pandigital(n, s=9): + n = str(n) + return len(n) == s and not "1234567890"[:s].strip(n) + + +# --- Calculate the sum of proper divisors for n-------------------------------------------------- def d(n): s = 1 t = sqrt(n) - for i in range(2, int(t)+1): - if n % i == 0: s += i + n/i - if t == int(t): s -= t #correct s if t is a perfect square + for i in range(2, int(t) + 1): + if n % i == 0: + s += i + n / i + if t == int(t): + s -= t # correct s if t is a perfect square return s -#--- Create a list of all palindromic numbers with k digits-------------------------------------- + +# --- Create a list of all palindromic numbers with k digits-------------------------------------- def pal_list(k): if k == 1: return [1, 2, 3, 4, 5, 6, 7, 8, 9] - return [sum([n*(10**i) for i,n in enumerate(([x]+list(ys)+[z]+list(ys)[::-1]+[x]) if k%2 - else ([x]+list(ys)+list(ys)[::-1]+[x]))]) - for x in range(1,10) - for ys in itertools.product(range(10), repeat=int(k/2)-1) - for z in (range(10) if k%2 else (None,))] - - -#--- sum of factorial's digits------------------------------------------------------------------- + return [ + sum( + [ + n * (10**i) + for i, n in enumerate( + ([x] + list(ys) + [z] + list(ys)[::-1] + [x]) + if k % 2 + else ([x] + list(ys) + list(ys)[::-1] + [x]) + ) + ] + ) + for x in range(1, 10) + for ys in itertools.product(range(10), repeat=int(k / 2) - 1) + for z in (range(10) if k % 2 else (None,)) + ] + + +# --- sum of factorial's digits------------------------------------------------------------------- def sof_digits(n): - if n==0: return 1 + if n == 0: + return 1 s = 0 while n > 0: s, n = s + fact[n % 10], n // 10 return s - -#--- find the nth Fibonacci number--------------------------------------------------------------- +# --- find the nth Fibonacci number--------------------------------------------------------------- def fibonacci(n): """ Find the nth number in the Fibonacci series. Example: - + >>>fibonacci(100) 354224848179261915075 @@ -94,6 +120,7 @@ def fibonacci(n): raise ValueError("Negative arguments not implemented") return _fib(n)[0] + # Returns a tuple (F(n), F(n+1)) def _fib(n): if n == 0: @@ -108,38 +135,40 @@ def _fib(n): return (d, c + d) -#--- sum of squares of digits------------------------------------------------------------------- +# --- sum of squares of digits------------------------------------------------------------------- def sos_digits(n): s = 0 while n > 0: - s, n = s + (n % 10)**2, n // 10 + s, n = s + (n % 10) ** 2, n // 10 return s -#--- sum of the digits to a power e------------------------------------------------------------- + +# --- sum of the digits to a power e------------------------------------------------------------- def pow_digits(n, e): s = 0 while n > 0: - s, n = s + (n % 10)**e, n // 10 + s, n = s + (n % 10) ** e, n // 10 return s - -#--- check n for prime-------------------------------------------------------------------------- +# --- check n for prime-------------------------------------------------------------------------- def is_prime(n): - if n <= 1: return False - if n <= 3: return True - if n%2==0 or n%3 == 0: return False + if n <= 1: + return False + if n <= 3: + return True + if n % 2 == 0 or n % 3 == 0: + return False r = int(sqrt(n)) f = 5 while f <= r: - if n%f == 0 or n%(f+2) == 0: return False - f+= 6 + if n % f == 0 or n % (f + 2) == 0: + return False + f += 6 return True - - -#--- Miller-Rabin primality test---------------------------------------------------------------- +# --- Miller-Rabin primality test---------------------------------------------------------------- def miller_rabin(n): """ Check n for primalty: Example: @@ -163,33 +192,33 @@ def miller_rabin(n): return False return True + def miller_rabin_pass(a, s, d, n): a_to_power = pow(a, d, n) if a_to_power == 1: return True - for i in range(s-1): + for i in range(s - 1): if a_to_power == n - 1: return True a_to_power = (a_to_power * a_to_power) % n return a_to_power == n - 1 - -#--- factor a number into primes and frequency---------------------------------------------------- +# --- factor a number into primes and frequency---------------------------------------------------- def factor(n): """ - find the prime factors of n along with their frequencies. Example: + find the prime factors of n along with their frequencies. Example: - >>> factor(786456) - [(2,3), (3,3), (11,1), (331,1)] + >>> factor(786456) + [(2,3), (3,3), (11,1), (331,1)] - Source: Project Euler forums for problem #3 + Source: Project Euler forums for problem #3 """ f, factors, prime_gaps = 1, [], [2, 4, 2, 4, 6, 2, 6, 4] if n < 1: return [] while True: - for gap in ([1, 1, 2, 2, 4] if f < 11 else prime_gaps): + for gap in [1, 1, 2, 2, 4] if f < 11 else prime_gaps: f += gap if f * f > n: # If f > sqrt(n) if n == 1: @@ -205,7 +234,7 @@ def factor(n): factors.append((f, e)) -#--- generate permutations----------------------------------------------------------------------- +# --- generate permutations----------------------------------------------------------------------- def perm(n, s): """ requires function factorial() @@ -214,43 +243,41 @@ def perm(n, s): >>>perm(30, 'abcde') bcade """ - if len(s)==1: return s - q, r = divmod(n, factorial(len(s)-1)) - return s[q] + perm(r, s[:q] + s[q+1:]) - + if len(s) == 1: + return s + q, r = divmod(n, factorial(len(s) - 1)) + return s[q] + perm(r, s[:q] + s[q + 1 :]) - -#--- binomial coefficients----------------------------------------------------------------------- +# --- binomial coefficients----------------------------------------------------------------------- def binomial(n, k): """ Calculate C(n,k), the number of ways can k be chosen from n. Example: - + >>>binomial(30,12) 86493225 """ nt = 1 - for t in range(min(k, n-k)): - nt = nt * (n-t) // (t+1) + for t in range(min(k, n - k)): + nt = nt * (n - t) // (t + 1) return nt -#--- catalan number------------------------------------------------------------------------------ +# --- catalan number------------------------------------------------------------------------------ def catalan_number(n): """ Calculate the nth Catalan number. Example: - + >>>catalan_number(10) 16796 """ nm = dm = 1 - for k in range(2, n+1): - nm, dm = (nm*(n+k), dm*k) + for k in range(2, n + 1): + nm, dm = (nm * (n + k), dm * k) return nm / dm - -#--- generate prime numbers---------------------------------------------------------------------- +# --- generate prime numbers---------------------------------------------------------------------- def prime_sieve(n): """ Return a list of prime numbers from 2 to a prime < n. Very fast (n<10,000,000) in 0.4 sec. @@ -262,16 +289,15 @@ def prime_sieve(n): Algorithm & Python source: Robert William Hanks http://stackoverflow.com/questions/17773352/python-sieve-prime-numbers """ - sieve = [True] * (n//2) - for i in range(3,int(n**0.5)+1,2): - if sieve[i//2]: - sieve[i*i//2::i] = [False] * ((n-i*i-1)//(2*i)+1) - return [2] + [2*i+1 for i in range(1,n//2) if sieve[i]] + sieve = [True] * (n // 2) + for i in range(3, int(n**0.5) + 1, 2): + if sieve[i // 2]: + sieve[i * i // 2 :: i] = [False] * ((n - i * i - 1) // (2 * i) + 1) + return [2] + [2 * i + 1 for i in range(1, n // 2) if sieve[i]] - -#--- bezout coefficients-------------------------------------------------------------------------- -def exgcd(a,b): +# --- bezout coefficients-------------------------------------------------------------------------- +def exgcd(a, b): """ Bézout coefficients (u,v) of (a,b) as: @@ -287,85 +313,131 @@ def exgcd(a,b): Algorithm source: Pierre L. Douillet http://www.douillet.info/~douillet/working_papers/bezout/node2.html """ - u, v, s, t = 1, 0, 0, 1 - while b !=0: - q, r = divmod(a,b) + u, v, s, t = 1, 0, 0, 1 + while b != 0: + q, r = divmod(a, b) a, b = b, r - u, s = s, u - q*s - v, t = t, v - q*t + u, s = s, u - q * s + v, t = t, v - q * t return (u, v, a) - - -def mod_inverse(a,b): - x,y,z = exgcd(a,b) - return x; + +def mod_inverse(a, b): + x, y, z = exgcd(a, b) + return x + def phi(x): - if x==1: - return 1; - factors = factor(x); - ans = x; + if x == 1: + return 1 + factors = factor(x) + ans = x for prime in factors: - ans=int(ans / prime[0]*(prime[0]-1)) + ans = int(ans / prime[0] * (prime[0] - 1)) return ans + def miu(x): - if x==1: - return 1; + if x == 1: + return 1 factors = factor(x) for prime in factors: - if prime[1]>1: - return 0; - return 1-(len(factors) and 1)*2 - - -#--- number base conversion ------------------------------------------------------------------- -#source: http://interactivepython.org/runestone/static/pythonds/Recursion/pythondsConvertinganIntegertoaStringinAnyBase.html -def dec2base(n,base): - convertString = "0123456789ABCDEF" - if n < base: - return convertString[n] - else: - return dec2base(n//base,base) + convertString[n%base] - -#--- number to words ---------------------------------------------------------------------------- -#this function copied from stackoverflow user: Developer, Oct 5 '13 at 3:45 -def n2words(num,join=True): - '''words = {} convert an integer number into words''' - units = ['','One','Two','Three','Four','Five','Six','Seven','Eight','Nine'] - teens = ['','Eleven','Twelve','Thirteen','Fourteen','Fifteen','Sixteen', \ - 'Seventeen','Eighteen','Nineteen'] - tens = ['','Ten','Twenty','Thirty','Forty','Fifty','Sixty','Seventy', \ - 'Eighty','Ninety'] - thousands = ['','Thousand','Million','Billion','Trillion','Quadrillion', \ - 'Quintillion','Sextillion','Septillion','Octillion', \ - 'Nonillion','Decillion','Undecillion','Duodecillion', \ - 'Tredecillion','Quattuordecillion','Sexdecillion', \ - 'Septendecillion','Octodecillion','Novemdecillion', \ - 'Vigintillion'] + if prime[1] > 1: + return 0 + return 1 - (len(factors) and 1) * 2 + + +# --- number base conversion ------------------------------------------------------------------- +# source: http://interactivepython.org/runestone/static/pythonds/Recursion/pythondsConvertinganIntegertoaStringinAnyBase.html +def dec2base(n, base): + convertString = "0123456789ABCDEF" + if n < base: + return convertString[n] + else: + return dec2base(n // base, base) + convertString[n % base] + + +# --- number to words ---------------------------------------------------------------------------- +# this function copied from stackoverflow user: Developer, Oct 5 '13 at 3:45 +def n2words(num, join=True): + """words = {} convert an integer number into words""" + units = ["", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine"] + teens = [ + "", + "Eleven", + "Twelve", + "Thirteen", + "Fourteen", + "Fifteen", + "Sixteen", + "Seventeen", + "Eighteen", + "Nineteen", + ] + tens = [ + "", + "Ten", + "Twenty", + "Thirty", + "Forty", + "Fifty", + "Sixty", + "Seventy", + "Eighty", + "Ninety", + ] + thousands = [ + "", + "Thousand", + "Million", + "Billion", + "Trillion", + "Quadrillion", + "Quintillion", + "Sextillion", + "Septillion", + "Octillion", + "Nonillion", + "Decillion", + "Undecillion", + "Duodecillion", + "Tredecillion", + "Quattuordecillion", + "Sexdecillion", + "Septendecillion", + "Octodecillion", + "Novemdecillion", + "Vigintillion", + ] words = [] - if num==0: words.append('zero') + if num == 0: + words.append("zero") else: - numStr = '%d'%num + numStr = "%d" % num numStrLen = len(numStr) - groups = int((numStrLen+2)/3) - numStr = numStr.zfill(groups*3) - for i in range(0,groups*3,3): - h,t,u = int(numStr[i]),int(numStr[i+1]),int(numStr[i+2]) - g = groups-(int(i/3)+1) - if h>=1: + groups = int((numStrLen + 2) / 3) + numStr = numStr.zfill(groups * 3) + for i in range(0, groups * 3, 3): + h, t, u = int(numStr[i]), int(numStr[i + 1]), int(numStr[i + 2]) + g = groups - (int(i / 3) + 1) + if h >= 1: words.append(units[h]) - words.append('Hundred') - if t>1: + words.append("Hundred") + if t > 1: words.append(tens[t]) - if u>=1: words.append(units[u]) - elif t==1: - if u>=1: words.append(teens[u]) - else: words.append(tens[t]) + if u >= 1: + words.append(units[u]) + elif t == 1: + if u >= 1: + words.append(teens[u]) + else: + words.append(tens[t]) else: - if u>=1: words.append(units[u]) - if (g>=1) and ((h+t+u)>0): words.append(thousands[g]+'') - if join: return ' '.join(words) + if u >= 1: + words.append(units[u]) + if (g >= 1) and ((h + t + u) > 0): + words.append(thousands[g] + "") + if join: + return " ".join(words) return words diff --git a/cyaron/merger.py b/cyaron/merger.py index e7069b6..41847c3 100644 --- a/cyaron/merger.py +++ b/cyaron/merger.py @@ -1,55 +1,57 @@ from .graph import * + class Merger: def __init__(self, *graphs, **kwargs): """__init__(self, *graphs, **kwargs) -> None - put several graphs into one - list graphs -> the graphs that will be merged - list kwargs: - None + put several graphs into one + list graphs -> the graphs that will be merged + list kwargs: + None """ self.graphs = graphs self.G = Graph(sum([len(i.edges) - 1 for i in graphs]), graphs[0].directed) - - counter = 0 + + counter = 0 for graph in self.graphs: graph.offset = counter for edge in graph.iterate_edges(): - self.G.add_edge(edge.start + counter, - edge.end + counter, - weight=edge.weight) + self.G.add_edge( + edge.start + counter, edge.end + counter, weight=edge.weight + ) counter += len(graph.edges) - 1 def __add_edge(self, u, v, **kwargs): """__add_edge(self, u, v, **kwargs) - tuple u -> (graph_index, vertex) indicating the start point - tuple v -> (graph_index, vertex) indicating the end point - **kwargs: - int weight -> edge weight + tuple u -> (graph_index, vertex) indicating the start point + tuple v -> (graph_index, vertex) indicating the end point + **kwargs: + int weight -> edge weight """ - self.G.add_edge(self.graphs[ u[0] ].offset + u[1], - self.graphs[ v[0] ].offset + v[1], - weight=kwargs.get("weight", 1)) - + self.G.add_edge( + self.graphs[u[0]].offset + u[1], + self.graphs[v[0]].offset + v[1], + weight=kwargs.get("weight", 1), + ) + def add_edge(self, u, v, **kwargs): - """add_edge(self, u, v, **kwargs) -> None - """ + """add_edge(self, u, v, **kwargs) -> None""" self.__add_edge(u, v, **kwargs) def to_str(self, **kwargs): return self.G.to_str(**kwargs) - + def __str__(self): return self.to_str() @staticmethod def component(point_count, edge_count, **kwargs): """component(point_count, edge_count, **kwargs) - generate a graph with certain components - int point_count -> the number of vertices of each component - int edge_count -> the number of edges of each component - **kwargs: - int component_count -> indicating how many components there are + generate a graph with certain components + int point_count -> the number of vertices of each component + int edge_count -> the number of edges of each component + **kwargs: + int component_count -> indicating how many components there are """ component_count = kwargs.get("component_count", (2, 2)) if not list_like(component_count): diff --git a/cyaron/output_capture.py b/cyaron/output_capture.py index 4bcac58..5680526 100644 --- a/cyaron/output_capture.py +++ b/cyaron/output_capture.py @@ -1,10 +1,12 @@ from contextlib import contextmanager import sys + try: from StringIO import StringIO except ImportError: from io import StringIO + @contextmanager def captured_output(): new_out, new_err = StringIO(), StringIO() @@ -13,4 +15,4 @@ def captured_output(): sys.stdout, sys.stderr = new_out, new_err yield sys.stdout, sys.stderr finally: - sys.stdout, sys.stderr = old_out, old_err \ No newline at end of file + sys.stdout, sys.stderr = old_out, old_err diff --git a/cyaron/polygon.py b/cyaron/polygon.py index d0dfca8..d73fdec 100644 --- a/cyaron/polygon.py +++ b/cyaron/polygon.py @@ -6,7 +6,7 @@ class Polygon: - def __init__(self,points=[]): + def __init__(self, points=[]): if not list_like(points): raise Exception("polygon must be constructed by a list of points") self.points = points @@ -15,15 +15,16 @@ def __str__(self): buf = [] for point in self.points: buf.append(str(point[0]) + " " + str(point[1])) - return '\n'.join(buf) + return "\n".join(buf) def perimeter(self): ans = 0 for i in range(0, len(self.points)): a = self.points[i] b = self.points[(i + 1) % len(self.points)] - ans = ans + math.sqrt((a[0] - b[0]) * (a[0] - b[0]) + - (a[1] - b[1]) * (a[1] - b[1])) + ans = ans + math.sqrt( + (a[0] - b[0]) * (a[0] - b[0]) + (a[1] - b[1]) * (a[1] - b[1]) + ) return ans def area(self): @@ -37,8 +38,8 @@ def area(self): ans = ans / 2.0 return ans - #generate a convex hull with n points - #it's possible to have even edges + # generate a convex hull with n points + # it's possible to have even edges @staticmethod def convex_hull(n, **kwargs): # fx, fy are functions which map [0,1] to int or float @@ -75,8 +76,7 @@ def convex_hull(n, **kwargs): a = st[len(st) - 1] b = points[i] o = st[len(st) - 2] - tmp = (a[0] - o[0]) * (b[1] - o[1]) - \ - (a[1] - o[1]) * (b[0] - o[0]) + tmp = (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]) if tmp > 0 or (tmp == 0 and not strict): break st.pop() @@ -87,8 +87,7 @@ def convex_hull(n, **kwargs): a = st[len(st) - 1] b = points[i] o = st[len(st) - 2] - tmp = (a[0] - o[0]) * (b[1] - o[1]) - \ - (a[1] - o[1]) * (b[0] - o[0]) + tmp = (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]) if tmp > 0 or (tmp == 0 and not strict): break st.pop() @@ -110,21 +109,34 @@ def __conquer(points): if len(points) <= 2: return points if len(points) == 3: - (points[1],points[2])=(points[2],points[1]) + (points[1], points[2]) = (points[2], points[1]) return points divide_id = random.randint(2, len(points) - 1) divide_point1 = points[divide_id] divide_k = random.uniform(0.01, 0.99) - divide_point2 = [divide_k * (points[1][0] - points[0][0]) + points[0][0], - divide_k * (points[1][1] - points[0][1]) + points[0][1]] + divide_point2 = [ + divide_k * (points[1][0] - points[0][0]) + points[0][0], + divide_k * (points[1][1] - points[0][1]) + points[0][1], + ] # path: points[0]->points[divide]->points[1] # dividing line in the form Ax+By+C=0 - divide_line = [divide_point2[1] - divide_point1[1], - divide_point1[0] - divide_point2[0], - -divide_point1[0] * divide_point2[1] - + divide_point1[1] * divide_point2[0]] - p0 = (divide_line[0] * points[0][0] + divide_line[1] * points[0][1] + divide_line[2] >= 0) - p1 = (divide_line[0] * points[1][0] + divide_line[1] * points[1][1] + divide_line[2] >= 0) + divide_line = [ + divide_point2[1] - divide_point1[1], + divide_point1[0] - divide_point2[0], + -divide_point1[0] * divide_point2[1] + divide_point1[1] * divide_point2[0], + ] + p0 = ( + divide_line[0] * points[0][0] + + divide_line[1] * points[0][1] + + divide_line[2] + >= 0 + ) + p1 = ( + divide_line[0] * points[1][0] + + divide_line[1] * points[1][1] + + divide_line[2] + >= 0 + ) if p0 == p1: # the divide point isn't good enough... return Polygon.__conquer(points) s = [[], []] @@ -135,7 +147,12 @@ def __conquer(points): for i in range(2, len(points)): if i == divide_id: continue - pt = (divide_line[0] * points[i][0] + divide_line[1] * points[i][1] + divide_line[2] >= 0) + pt = ( + divide_line[0] * points[i][0] + + divide_line[1] * points[i][1] + + divide_line[2] + >= 0 + ) s[pt].append(points[i]) pa = Polygon.__conquer(s[p0]) pb = Polygon.__conquer(s[not p0]) @@ -152,17 +169,23 @@ def simple_polygon(points): if len(points) <= 3: return Polygon(points) # divide by points[0], points[1] - divide_line = [points[1][1] - points[0][1], - points[0][0] - points[1][0], - -points[0][0] * points[1][1] - + points[0][1] * points[1][0]] + divide_line = [ + points[1][1] - points[0][1], + points[0][0] - points[1][0], + -points[0][0] * points[1][1] + points[0][1] * points[1][0], + ] s = [[], []] s[0].append(points[0]) s[0].append(points[1]) s[1].append(points[1]) s[1].append(points[0]) for i in range(2, len(points)): - pt = (divide_line[0] * points[i][0] + divide_line[1] * points[i][1] + divide_line[2] >= 0) + pt = ( + divide_line[0] * points[i][0] + + divide_line[1] * points[i][1] + + divide_line[2] + >= 0 + ) s[pt].append(points[i]) pa = Polygon.__conquer(s[0]) pb = Polygon.__conquer(s[1]) diff --git a/cyaron/sequence.py b/cyaron/sequence.py index 625456e..c584a33 100644 --- a/cyaron/sequence.py +++ b/cyaron/sequence.py @@ -1,13 +1,13 @@ from .utils import * + class Sequence: - """Class Sequence: the tool class for sequences. - """ + """Class Sequence: the tool class for sequences.""" def __init__(self, formula, initial_values=()): """__init__(self, formula, initial_values=() -> None - Create a sequence object. - int formula(int, function) -> the formula function ... + Create a sequence object. + int formula(int, function) -> the formula function ... """ if not callable(formula): raise Exception("formula must be a function") @@ -30,4 +30,4 @@ def get(self, left_range, right_range=None): if right_range is None: return self.__get_one(left_range) - return [self.__get_one(i) for i in range(left_range, right_range+1)] + return [self.__get_one(i) for i in range(left_range, right_range + 1)] diff --git a/cyaron/string.py b/cyaron/string.py index 6af1e5c..2a5694e 100644 --- a/cyaron/string.py +++ b/cyaron/string.py @@ -53,7 +53,9 @@ def random_sentence(word_count_range, **kwargs): def random_paragraph(sentence_count_range, **kwargs): sentence_count = sentence_count_range if list_like(sentence_count_range): - sentence_count = random.randint(sentence_count_range[0], sentence_count_range[1]) + sentence_count = random.randint( + sentence_count_range[0], sentence_count_range[1] + ) word_count_range = kwargs.get("word_count_range", (6, 10)) @@ -95,15 +97,18 @@ def random_paragraph(sentence_count_range, **kwargs): sentences.append(string) - paragraph = reduce(lambda x, y: x + random.choice(sentence_joiners) + y, sentences) + paragraph = reduce( + lambda x, y: x + random.choice(sentence_joiners) + y, sentences + ) return paragraph @staticmethod def random_regular(*args, **kwargs): pattern = args limit_len = int(kwargs.get("limit", "10")) - if (limit_len <= 1): limit_len = 10 - if (list_like(args)): + if limit_len <= 1: + limit_len = 10 + if list_like(args): pattern = random.choice(args) _x = xeger.Xeger(limit=limit_len) return _x.xeger(pattern) diff --git a/cyaron/tests/compare_test.py b/cyaron/tests/compare_test.py index f820a9d..01d4354 100644 --- a/cyaron/tests/compare_test.py +++ b/cyaron/tests/compare_test.py @@ -11,6 +11,7 @@ log.set_verbose() + class TestCompare(unittest.TestCase): def setUp(self): @@ -51,16 +52,19 @@ def test_noipstyle_incorrect(self): with captured_output() as (out, err): Compare.output("test_another_incorrect.out", std=io) except CompareMismatch as e: - self.assertEqual(e.name, 'test_another_incorrect.out') + self.assertEqual(e.name, "test_another_incorrect.out") e = e.mismatch - self.assertEqual(e.content, 'test123\r\ntest124 ') - self.assertEqual(e.std, 'test123 \ntest123\n\n') - self.assertEqual(str(e), 'On line 2 column 7, read 4, expected 3.') + self.assertEqual(e.content, "test123\r\ntest124 ") + self.assertEqual(e.std, "test123 \ntest123\n\n") + self.assertEqual(str(e), "On line 2 column 7, read 4, expected 3.") else: self.assertTrue(False) result = out.getvalue().strip() - self.assertEqual(result, "test_another_incorrect.out: !!!INCORRECT!!! On line 2 column 7, read 4, expected 3.") + self.assertEqual( + result, + "test_another_incorrect.out: !!!INCORRECT!!! On line 2 column 7, read 4, expected 3.", + ) def test_fulltext_program(self): with open("correct.py", "w") as f: @@ -77,19 +81,31 @@ def test_fulltext_program(self): try: with captured_output() as (out, err): - Compare.program("python correct.py", "python incorrect.py", std=io, input=io, grader="FullText") + Compare.program( + "python correct.py", + "python incorrect.py", + std=io, + input=io, + grader="FullText", + ) except CompareMismatch as e: - self.assertEqual(e.name, 'python incorrect.py') + self.assertEqual(e.name, "python incorrect.py") e = e.mismatch - self.assertEqual(e.content, '2\n') - self.assertEqual(e.std, '1\n') - self.assertEqual(e.content_hash, '53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3') - self.assertEqual(e.std_hash, '4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865') + self.assertEqual(e.content, "2\n") + self.assertEqual(e.std, "1\n") + self.assertEqual( + e.content_hash, + "53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3", + ) + self.assertEqual( + e.std_hash, + "4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865", + ) else: self.assertTrue(False) result = out.getvalue().strip() - correct_out = 'python correct.py: Correct \npython incorrect.py: !!!INCORRECT!!! Hash mismatch: read 53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3, expected 4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865' + correct_out = "python correct.py: Correct \npython incorrect.py: !!!INCORRECT!!! Hash mismatch: read 53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3, expected 4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865" self.assertEqual(result, correct_out) def test_file_input(self): @@ -106,28 +122,38 @@ def test_file_input(self): io.input_writeln("233") with captured_output() as (out, err): - Compare.program("python correct.py", std_program="python std.py", input=io, grader="NOIPStyle") + Compare.program( + "python correct.py", + std_program="python std.py", + input=io, + grader="NOIPStyle", + ) result = out.getvalue().strip() - correct_out = 'python correct.py: Correct' + correct_out = "python correct.py: Correct" self.assertEqual(result, correct_out) def test_concurrent(self): - programs = ['test{}.py'.format(i) for i in range(16)] + programs = ["test{}.py".format(i) for i in range(16)] for fn in programs: - with open(fn, 'w') as f: - f.write('print({})'.format(16)) - with open('std.py', 'w') as f: - f.write('print({})'.format(16)) + with open(fn, "w") as f: + f.write("print({})".format(16)) + with open("std.py", "w") as f: + f.write("print({})".format(16)) with IO() as test: - Compare.program(*[(sys.executable, program) for program in programs], std_program=(sys.executable, 'std.py'), max_workers=None, input=test) + Compare.program( + *[(sys.executable, program) for program in programs], + std_program=(sys.executable, "std.py"), + max_workers=None, + input=test + ) ios = [IO() for i in range(16)] try: for f in ios: - f.output_write('16') + f.output_write("16") with IO() as std: - std.output_write('16') + std.output_write("16") Compare.output(*ios, std=std, max_workers=None) finally: for io in ios: @@ -137,7 +163,11 @@ def test_timeout(self): if sys.version_info >= (3, 3): with IO() as test: try: - Compare.program(((sys.executable, '-c', '__import__(\'time\').sleep(10)'), 1), std=test, input=test) + Compare.program( + ((sys.executable, "-c", "__import__('time').sleep(10)"), 1), + std=test, + input=test, + ) except subprocess.TimeoutExpired: pass else: diff --git a/cyaron/tests/graph_test.py b/cyaron/tests/graph_test.py index 976930a..57b3e07 100644 --- a/cyaron/tests/graph_test.py +++ b/cyaron/tests/graph_test.py @@ -24,14 +24,14 @@ def test_same(self, l, r): def tarjan(graph, n): def new_array(len, val=0): - return [val for _ in range(len+1)] + return [val for _ in range(len + 1)] instack = new_array(n, False) low = new_array(n) dfn = new_array(n, 0) stap = new_array(n) belong = new_array(n) - var = [0, 0, 0] # cnt, bc, stop + var = [0, 0, 0] # cnt, bc, stop # cnt = bc = stop = 0 def dfs(cur): @@ -49,7 +49,7 @@ def dfs(cur): low[cur] = min(low[cur], dfn[v.end]) if dfn[cur] == low[cur]: - v = cur + 1 # set v != cur + v = cur + 1 # set v != cur var[1] += 1 while v != cur: var[2] -= 1 @@ -58,8 +58,8 @@ def dfs(cur): belong[v] = var[1] for i in range(n): - if dfn[i+1] == 0: - dfs(i+1) + if dfn[i + 1] == 0: + dfs(i + 1) return belong @@ -69,20 +69,20 @@ class TestGraph(unittest.TestCase): def test_self_loop(self): graph_size = 20 for _ in range(20): - graph = Graph.graph(graph_size, int(graph_size*2), self_loop=True) + graph = Graph.graph(graph_size, int(graph_size * 2), self_loop=True) has_self_loop = max([e.start == e.end for e in graph.iterate_edges()]) if has_self_loop: break self.assertTrue(has_self_loop) for _ in range(10): - graph = Graph.graph(graph_size, int(graph_size*2), self_loop=False) + graph = Graph.graph(graph_size, int(graph_size * 2), self_loop=False) self.assertFalse(max([e.start == e.end for e in graph.iterate_edges()])) def test_repeated_edges(self): graph_size = 20 for _ in range(20): - graph = Graph.graph(graph_size, int(graph_size*2), repeated_edges=True) + graph = Graph.graph(graph_size, int(graph_size * 2), repeated_edges=True) edges = [(e.start, e.end) for e in graph.iterate_edges()] has_repeated_edges = len(edges) > len(set(edges)) if has_repeated_edges: @@ -90,7 +90,7 @@ def test_repeated_edges(self): self.assertTrue(has_repeated_edges) for _ in range(10): - graph = Graph.graph(graph_size, int(graph_size*2), repeated_edges=False) + graph = Graph.graph(graph_size, int(graph_size * 2), repeated_edges=False) edges = list(graph.iterate_edges()) self.assertEqual(len(edges), len(set(edges))) @@ -101,53 +101,69 @@ def test_tree_connected(self): tree = Graph.tree(graph_size) for edge in tree.iterate_edges(): ufs.merge(edge.start, edge.end) - for i in range(graph_size-1): - self.assertTrue(ufs.test_same(i+1, i+2)) - + for i in range(graph_size - 1): + self.assertTrue(ufs.test_same(i + 1, i + 2)) def test_DAG(self): graph_size = 20 - for _ in range(10): # test 10 times + for _ in range(10): # test 10 times ufs = UnionFindSet(graph_size) - graph = Graph.DAG(graph_size, int(graph_size*1.6), repeated_edges=False, self_loop=False, loop=True) + graph = Graph.DAG( + graph_size, + int(graph_size * 1.6), + repeated_edges=False, + self_loop=False, + loop=True, + ) - self.assertEqual(len(list(graph.iterate_edges())), int(graph_size*1.6)) + self.assertEqual(len(list(graph.iterate_edges())), int(graph_size * 1.6)) for edge in graph.iterate_edges(): ufs.merge(edge.start, edge.end) - for i in range(graph_size-1): - self.assertTrue(ufs.test_same(i+1, i+2)) + for i in range(graph_size - 1): + self.assertTrue(ufs.test_same(i + 1, i + 2)) def test_DAG_without_loop(self): graph_size = 20 - for _ in range(10): # test 10 times + for _ in range(10): # test 10 times ufs = UnionFindSet(graph_size) - graph = Graph.DAG(graph_size, int(graph_size*1.6), repeated_edges=False, self_loop=False, loop=False) + graph = Graph.DAG( + graph_size, + int(graph_size * 1.6), + repeated_edges=False, + self_loop=False, + loop=False, + ) - self.assertEqual(len(list(graph.iterate_edges())), int(graph_size*1.6)) + self.assertEqual(len(list(graph.iterate_edges())), int(graph_size * 1.6)) for edge in graph.iterate_edges(): ufs.merge(edge.start, edge.end) - for i in range(graph_size-1): - self.assertTrue(ufs.test_same(i+1, i+2)) + for i in range(graph_size - 1): + self.assertTrue(ufs.test_same(i + 1, i + 2)) belong = tarjan(graph, graph_size) self.assertEqual(max(belong), graph_size) def test_undirected_graph(self): graph_size = 20 - for _ in range(10): # test 10 times + for _ in range(10): # test 10 times ufs = UnionFindSet(graph_size) - graph = Graph.UDAG(graph_size, int(graph_size*1.6), repeated_edges=False, self_loop=False) + graph = Graph.UDAG( + graph_size, int(graph_size * 1.6), repeated_edges=False, self_loop=False + ) - self.assertEqual(len(list(graph.iterate_edges())), int(graph_size*1.6)) + self.assertEqual(len(list(graph.iterate_edges())), int(graph_size * 1.6)) for edge in graph.iterate_edges(): ufs.merge(edge.start, edge.end) - for i in range(graph_size-1): - self.assertTrue(ufs.test_same(i+1, i+2)) + for i in range(graph_size - 1): + self.assertTrue(ufs.test_same(i + 1, i + 2)) def test_DAG_boundary(self): - with self.assertRaises(Exception, msg="the number of edges of connected graph must more than the number of nodes - 1"): + with self.assertRaises( + Exception, + msg="the number of edges of connected graph must more than the number of nodes - 1", + ): Graph.DAG(8, 6) Graph.DAG(8, 7) diff --git a/cyaron/tests/io_test.py b/cyaron/tests/io_test.py index 1da50e4..e8d88a2 100644 --- a/cyaron/tests/io_test.py +++ b/cyaron/tests/io_test.py @@ -26,7 +26,12 @@ def test_create_files_simple(self): def test_create_files_prefix_id(self): with captured_output() as (out, err): - IO(file_prefix="test_prefix", data_id=233, input_suffix=".inp", output_suffix=".ans") + IO( + file_prefix="test_prefix", + data_id=233, + input_suffix=".inp", + output_suffix=".ans", + ) self.assertTrue(os.path.exists("test_prefix233.inp")) self.assertTrue(os.path.exists("test_prefix233.ans")) @@ -50,8 +55,8 @@ def test_write_stuff(self): input = f.read() with open("test_write.out") as f: output = f.read() - self.assertEqual(input.split(), ['1', '2', '3', '4', '5', '6', '7', '8', '9']) - self.assertEqual(output.split(), ['9', '8', '7', '6', '5', '4', '3', '2', '1']) + self.assertEqual(input.split(), ["1", "2", "3", "4", "5", "6", "7", "8", "9"]) + self.assertEqual(output.split(), ["9", "8", "7", "6", "5", "4", "3", "2", "1"]) self.assertEqual(input.count("\n"), 2) self.assertEqual(output.count("\n"), 2) @@ -64,15 +69,18 @@ def test_output_gen(self): output = f.read() self.assertEqual(output.strip("\n"), "233") + def test_output_gen_limits(self): + + def test_init_overload(self): - with IO(file_prefix='data{', data_id=5) as test: - self.assertEqual(test.input_filename, 'data{5.in') - self.assertEqual(test.output_filename, 'data{5.out') - with IO('data{}.in', 'data{}.out', 5) as test: - self.assertEqual(test.input_filename, 'data5.in') - self.assertEqual(test.output_filename, 'data5.out') - with open('data5.in', 'w+') as fin: - with open('data5.out', 'w+') as fout: + with IO(file_prefix="data{", data_id=5) as test: + self.assertEqual(test.input_filename, "data{5.in") + self.assertEqual(test.output_filename, "data{5.out") + with IO("data{}.in", "data{}.out", 5) as test: + self.assertEqual(test.input_filename, "data5.in") + self.assertEqual(test.output_filename, "data5.out") + with open("data5.in", "w+") as fin: + with open("data5.out", "w+") as fout: with IO(fin, fout) as test: self.assertEqual(test.input_file, fin) self.assertEqual(test.output_file, fout) diff --git a/cyaron/tests/polygon_test.py b/cyaron/tests/polygon_test.py index 7548f3d..cac0ada 100644 --- a/cyaron/tests/polygon_test.py +++ b/cyaron/tests/polygon_test.py @@ -4,7 +4,9 @@ class TestPolygon(unittest.TestCase): def test_convex_hull(self): - hull = Polygon.convex_hull(300, fx=lambda x: int(x * 100000), fy=lambda x: int(x * 100000)) + hull = Polygon.convex_hull( + 300, fx=lambda x: int(x * 100000), fy=lambda x: int(x * 100000) + ) points = hull.points points = sorted(points) # unique @@ -19,8 +21,7 @@ def test_convex_hull(self): a = st[len(st) - 1] b = points[i] o = st[len(st) - 2] - if (a[0] - o[0]) * (b[1] - o[1]) - \ - (a[1] - o[1]) * (b[0] - o[0]) >= 0: + if (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]) >= 0: break st.pop() st.append(points[i]) @@ -30,8 +31,7 @@ def test_convex_hull(self): a = st[len(st) - 1] b = points[i] o = st[len(st) - 2] - if (a[0] - o[0]) * (b[1] - o[1]) - \ - (a[1] - o[1]) * (b[0] - o[0]) >= 0: + if (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]) >= 0: break st.pop() st.append(points[i]) @@ -55,8 +55,10 @@ def test_simple_polygon(self): c = points[j] d = points[(j + 1) % len(points)] prod = lambda x, y: x[0] * y[1] - x[1] * y[0] - t1 = prod([c[0] - a[0], c[1] - a[1]], [d[0] - a[0], d[1] - a[1]]) \ - * prod([c[0] - b[0], c[1] - b[1]], [d[0] - b[0], d[1] - b[1]]) - t2 = prod([a[0] - c[0], a[1] - c[1]], [b[0] - c[0], b[1] - c[1]]) \ - * prod([a[0] - d[0], a[1] - d[1]], [b[0] - d[0], b[1] - d[1]]) + t1 = prod( + [c[0] - a[0], c[1] - a[1]], [d[0] - a[0], d[1] - a[1]] + ) * prod([c[0] - b[0], c[1] - b[1]], [d[0] - b[0], d[1] - b[1]]) + t2 = prod( + [a[0] - c[0], a[1] - c[1]], [b[0] - c[0], b[1] - c[1]] + ) * prod([a[0] - d[0], a[1] - d[1]], [b[0] - d[0], b[1] - d[1]]) self.assertFalse(t1 <= 1e-9 and t2 <= 1e-9) diff --git a/cyaron/tests/sequence_test.py b/cyaron/tests/sequence_test.py index 272a1d5..035fcb3 100644 --- a/cyaron/tests/sequence_test.py +++ b/cyaron/tests/sequence_test.py @@ -20,4 +20,3 @@ def test_func_get_one(self): def test_func_get_many(self): seq = Sequence(lambda i, f: 3 * i + 2 * f(i - 1), [0]) self.assertEqual(seq.get(3, 5), [33, 78, 171]) - diff --git a/cyaron/tests/str_test.py b/cyaron/tests/str_test.py index 3f12fab..517a7ef 100644 --- a/cyaron/tests/str_test.py +++ b/cyaron/tests/str_test.py @@ -25,6 +25,6 @@ def test_random_paragraph(self): String.random_paragraph(10) def test_random_regular(self): - pattern = r'[0-9]+\w_.{0,9}' + pattern = r"[0-9]+\w_.{0,9}" sentence = String.random_regular(pattern, limit=5) - self.assertTrue(re.match(pattern, sentence).group() == sentence) \ No newline at end of file + self.assertTrue(re.match(pattern, sentence).group() == sentence) diff --git a/cyaron/utils.py b/cyaron/utils.py index 4ef7358..11a438e 100644 --- a/cyaron/utils.py +++ b/cyaron/utils.py @@ -1,14 +1,14 @@ def ati(array): """ati(array) -> list - Convert all the elements in the array and return them in a list. + Convert all the elements in the array and return them in a list. """ return [int(i) for i in array] def list_like(data): """list_like(data) -> bool - Judge whether the object data is like a list or a tuple. - object data -> the data to judge + Judge whether the object data is like a list or a tuple. + object data -> the data to judge """ return isinstance(data, tuple) or isinstance(data, list) @@ -24,7 +24,7 @@ def int_like(data): def strtolines(str): - lines = str.split('\n') + lines = str.split("\n") for i in range(len(lines)): lines[i] = lines[i].rstrip() @@ -39,6 +39,7 @@ def make_unicode(data): except NameError: return str(data) + def unpack_kwargs(funcname, kwargs, arg_pattern): rv = {} kwargs = kwargs.copy() @@ -58,7 +59,15 @@ def unpack_kwargs(funcname, kwargs, arg_pattern): except KeyError as e: error = True if error: - raise TypeError('{}() missing 1 required keyword-only argument: \'{}\''.format(funcname, tp)) + raise TypeError( + "{}() missing 1 required keyword-only argument: '{}'".format( + funcname, tp + ) + ) if kwargs: - raise TypeError('{}() got an unexpected keyword argument \'{}\''.format(funcname, next(iter(kwargs.items()))[0])) + raise TypeError( + "{}() got an unexpected keyword argument '{}'".format( + funcname, next(iter(kwargs.items()))[0] + ) + ) return rv diff --git a/cyaron/vector.py b/cyaron/vector.py index 2620c59..3016883 100644 --- a/cyaron/vector.py +++ b/cyaron/vector.py @@ -14,7 +14,9 @@ class VectorRandomMode(Enum): class Vector: @staticmethod - def random(num: int = 5, position_range: list = None, mode: VectorRandomMode = 0, **kwargs): + def random( + num: int = 5, position_range: list = None, mode: VectorRandomMode = 0, **kwargs + ): """ brief : generating n random vectors in limited space param : @@ -47,16 +49,24 @@ def random(num: int = 5, position_range: list = None, mode: VectorRandomMode = 0 else: offset.append(0) length.append(position_range[i]) - vector_space *= (length[i] + 1) + vector_space *= length[i] + 1 if mode == VectorRandomMode.unique and num > vector_space: - raise Exception("1st param is so large that CYaRon can not generate unique vectors") + raise Exception( + "1st param is so large that CYaRon can not generate unique vectors" + ) result = [] if mode == VectorRandomMode.repeatable: - result = [[random.randint(x, y) for x, y in zip(offset, length)] for _ in range(num)] + result = [ + [random.randint(x, y) for x, y in zip(offset, length)] + for _ in range(num) + ] elif mode == VectorRandomMode.float: - result = [[random.uniform(x, y) for x, y in zip(offset, length)] for _ in range(num)] + result = [ + [random.uniform(x, y) for x, y in zip(offset, length)] + for _ in range(num) + ] elif mode == VectorRandomMode.unique and vector_space > 5 * num: # O(NlogN) num_set = set() @@ -87,5 +97,5 @@ def get_vector(dimension: int, position_range: list, hashcode: int): tmp = [] for i in range(0, dimension): tmp.append(hashcode % (position_range[i] + 1)) - hashcode //= (position_range[i] + 1) + hashcode //= position_range[i] + 1 return tmp diff --git a/cyaron/visual.py b/cyaron/visual.py index 7b5f122..3e0f63d 100644 --- a/cyaron/visual.py +++ b/cyaron/visual.py @@ -1,26 +1,26 @@ -from .graph import * +from .graph import * from .merger import Merger import pygraphviz as pgv + def visualize(graph, output_path="a.png"): """visualize(graph, **kwargs) -> None - Graph/Merger graph -> the graph/Merger that will be visualized - string output_path-> the output path of the image + Graph/Merger graph -> the graph/Merger that will be visualized + string output_path-> the output path of the image """ - if isinstance(graph, Merger): graph = Merger.G + if isinstance(graph, Merger): + graph = Merger.G G = pgv.AGraph(directed=graph.directed) G.add_nodes_from([i for i in xrange(1, len(graph.edges))]) for edge in graph.iterate_edges(): G.add_edge(edge.start, edge.end, label=edge.weight) - - G.node_attr['shape'] = 'egg' - G.node_attr['width'] = '0.25' - G.node_attr['height'] = '0.25' - G.edge_attr['arrowhead'] = 'open' - G.layout(prog='dot') - G.draw(output_path) - + G.node_attr["shape"] = "egg" + G.node_attr["width"] = "0.25" + G.node_attr["height"] = "0.25" + G.edge_attr["arrowhead"] = "open" + G.layout(prog="dot") + G.draw(output_path) From 0121c5933923099163fe48756172c21baa2f5c71 Mon Sep 17 00:00:00 2001 From: FredB-mine Date: Thu, 7 Mar 2024 20:46:13 +0800 Subject: [PATCH 02/18] Added time limit for io --- cyaron/compare.py | 135 +++--------- cyaron/consts.py | 4 +- cyaron/graders/fulltext.py | 12 +- cyaron/graders/graderregistry.py | 2 +- cyaron/graders/mismatch.py | 25 +-- cyaron/graders/noipstyle.py | 44 ++-- cyaron/graph.py | 312 +++++++++++++--------------- cyaron/io.py | 31 ++- cyaron/log.py | 82 +++----- cyaron/math.py | 342 ++++++++++++------------------- cyaron/merger.py | 52 +++-- cyaron/output_capture.py | 4 +- cyaron/polygon.py | 73 +++---- cyaron/sequence.py | 10 +- cyaron/string.py | 13 +- cyaron/tests/compare_test.py | 76 +++---- cyaron/tests/graph_test.py | 72 +++---- cyaron/tests/io_test.py | 31 ++- cyaron/tests/polygon_test.py | 20 +- cyaron/tests/sequence_test.py | 1 + cyaron/tests/str_test.py | 4 +- cyaron/utils.py | 21 +- cyaron/vector.py | 22 +- cyaron/visual.py | 24 +-- 24 files changed, 560 insertions(+), 852 deletions(-) diff --git a/cyaron/compare.py b/cyaron/compare.py index 66f76a7..ad613a0 100644 --- a/cyaron/compare.py +++ b/cyaron/compare.py @@ -17,7 +17,7 @@ def __init__(self, name, mismatch): self.mismatch = mismatch def __str__(self): - return "In program: '{}'. {}".format(self.name, self.mismatch) + return 'In program: \'{}\'. {}'.format(self.name,self.mismatch) class Compare: @@ -37,7 +37,7 @@ def __process_file(file): file.output_file.seek(0) return file.output_filename, file.output_file.read() else: - with open(file, "r", newline="\n") as f: + with open(file, "r", newline='\n') as f: return file, f.read() @staticmethod @@ -50,43 +50,26 @@ def __normal_max_workers(workers): @classmethod def output(cls, *files, **kwargs): - kwargs = unpack_kwargs( - "output", - kwargs, - ( - "std", - ("grader", DEFAULT_GRADER), - ("max_workers", -1), - ("job_pool", None), - ("stop_on_incorrect", None), - ), - ) - std = kwargs["std"] - grader = kwargs["grader"] - max_workers = kwargs["max_workers"] - job_pool = kwargs["job_pool"] - if kwargs["stop_on_incorrect"] is not None: + kwargs = unpack_kwargs('output', kwargs, ('std', ('grader', DEFAULT_GRADER), ('max_workers', -1), + ('job_pool', None), ('stop_on_incorrect', None))) + std = kwargs['std'] + grader = kwargs['grader'] + max_workers = kwargs['max_workers'] + job_pool = kwargs['job_pool'] + if kwargs['stop_on_incorrect'] is not None: log.warn("parameter stop_on_incorrect is deprecated and has no effect.") if (max_workers is None or max_workers >= 0) and job_pool is None: max_workers = cls.__normal_max_workers(max_workers) try: from concurrent.futures import ThreadPoolExecutor - with ThreadPoolExecutor(max_workers=max_workers) as job_pool: - return cls.output( - *files, - std=std, - grader=grader, - max_workers=max_workers, - job_pool=job_pool - ) + return cls.output(*files, std=std, grader=grader, max_workers=max_workers, job_pool=job_pool) except ImportError: pass def get_std(): return cls.__process_file(std)[1] - if job_pool is not None: std = job_pool.submit(get_std).result() else: @@ -103,121 +86,61 @@ def do(file): @classmethod def program(cls, *programs, **kwargs): - kwargs = unpack_kwargs( - "program", - kwargs, - ( - "input", - ("std", None), - ("std_program", None), - ("grader", DEFAULT_GRADER), - ("max_workers", -1), - ("job_pool", None), - ("stop_on_incorrect", None), - ), - ) - input = kwargs["input"] - std = kwargs["std"] - std_program = kwargs["std_program"] - grader = kwargs["grader"] - max_workers = kwargs["max_workers"] - job_pool = kwargs["job_pool"] - time_limit = kwargs["time_limit"] - memory_limit = kwargs["memory_limit"] - - if kwargs["stop_on_incorrect"] is not None: + kwargs = unpack_kwargs('program', kwargs, ('input', ('std', None), ('std_program', None), + ('grader', DEFAULT_GRADER), ('max_workers', -1), + ('job_pool', None), ('stop_on_incorrect', None))) + input = kwargs['input'] + std = kwargs['std'] + std_program = kwargs['std_program'] + grader = kwargs['grader'] + max_workers = kwargs['max_workers'] + job_pool = kwargs['job_pool'] + if kwargs['stop_on_incorrect'] is not None: log.warn("parameter stop_on_incorrect is deprecated and has no effect.") if (max_workers is None or max_workers >= 0) and job_pool is None: max_workers = cls.__normal_max_workers(max_workers) try: from concurrent.futures import ThreadPoolExecutor - with ThreadPoolExecutor(max_workers=max_workers) as job_pool: - return cls.program( - *programs, - input=input, - std=std, - std_program=std_program, - grader=grader, - max_workers=max_workers, - job_pool=job_pool - ) + return cls.program(*programs, input=input, std=std, std_program=std_program, grader=grader, max_workers=max_workers, job_pool=job_pool) except ImportError: pass if not isinstance(input, IO): - raise TypeError( - "expect {}, got {}".format(type(IO).__name__, type(input).__name__) - ) + raise TypeError("expect {}, got {}".format(type(IO).__name__, type(input).__name__)) input.flush_buffer() input.input_file.seek(0) if std_program is not None: - def get_std(): - with open( - os.dup(input.input_file.fileno()), "r", newline="\n" - ) as input_file: - content = make_unicode( - subprocess.check_output( - std_program, - shell=(not list_like(std_program)), - stdin=input.input_file, - universal_newlines=True, - ) - ) + with open(os.dup(input.input_file.fileno()), 'r', newline='\n') as input_file: + content = make_unicode(subprocess.check_output(std_program, shell=(not list_like(std_program)), stdin=input.input_file, universal_newlines=True)) input_file.seek(0) return content - if job_pool is not None: std = job_pool.submit(get_std).result() else: std = get_std() elif std is not None: - def get_std(): return cls.__process_file(std)[1] - if job_pool is not None: std = job_pool.submit(get_std).result() else: std = get_std() else: - raise TypeError( - "program() missing 1 required non-None keyword-only argument: 'std' or 'std_program'" - ) + raise TypeError('program() missing 1 required non-None keyword-only argument: \'std\' or \'std_program\'') def do(program_name): timeout = None - if ( - list_like(program_name) - and len(program_name) == 2 - and int_like(program_name[-1]) - ): + if list_like(program_name) and len(program_name) == 2 and int_like(program_name[-1]): program_name, timeout = program_name - with open( - os.dup(input.input_file.fileno()), "r", newline="\n" - ) as input_file: + with open(os.dup(input.input_file.fileno()), 'r', newline='\n') as input_file: if timeout is None: - content = make_unicode( - subprocess.check_output( - program_name, - shell=(not list_like(program_name)), - stdin=input_file, - universal_newlines=True, - ) - ) + content = make_unicode(subprocess.check_output(program_name, shell=(not list_like(program_name)), stdin=input_file, universal_newlines=True)) else: - content = make_unicode( - subprocess.check_output( - program_name, - shell=(not list_like(program_name)), - stdin=input_file, - universal_newlines=True, - timeout=timeout, - ) - ) + content = make_unicode(subprocess.check_output(program_name, shell=(not list_like(program_name)), stdin=input_file, universal_newlines=True, timeout=timeout)) input_file.seek(0) cls.__compare_two(program_name, content, std, grader) diff --git a/cyaron/consts.py b/cyaron/consts.py index e6fab00..c838ef3 100644 --- a/cyaron/consts.py +++ b/cyaron/consts.py @@ -18,7 +18,7 @@ ALPHABET_CAPITAL = string.ascii_uppercase ALPHABET = ALPHABET_SMALL + ALPHABET_CAPITAL NUMBERS = string.digits -SENTENCE_SEPARATORS = ",,,,,,,;;:" # 70% ',' 20% ';' 10% ':' -SENTENCE_TERMINATORS = "....!" # 80% '.' 20% '!' +SENTENCE_SEPARATORS = ',,,,,,,;;:' # 70% ',' 20% ';' 10% ':' +SENTENCE_TERMINATORS = '....!' # 80% '.' 20% '!' DEFAULT_GRADER = "NOIPStyle" diff --git a/cyaron/graders/fulltext.py b/cyaron/graders/fulltext.py index b5dbb07..8460b6f 100644 --- a/cyaron/graders/fulltext.py +++ b/cyaron/graders/fulltext.py @@ -2,13 +2,9 @@ from .graderregistry import CYaRonGraders from .mismatch import HashMismatch - @CYaRonGraders.grader("FullText") def fulltext(content, std): - content_hash = hashlib.sha256(content.encode("utf-8")).hexdigest() - std_hash = hashlib.sha256(std.encode("utf-8")).hexdigest() - return ( - (True, None) - if content_hash == std_hash - else (False, HashMismatch(content, std, content_hash, std_hash)) - ) + content_hash = hashlib.sha256(content.encode('utf-8')).hexdigest() + std_hash = hashlib.sha256(std.encode('utf-8')).hexdigest() + return (True, None) if content_hash == std_hash else (False, HashMismatch(content, std, content_hash, std_hash)) + diff --git a/cyaron/graders/graderregistry.py b/cyaron/graders/graderregistry.py index 702e39d..2fd1419 100644 --- a/cyaron/graders/graderregistry.py +++ b/cyaron/graders/graderregistry.py @@ -15,4 +15,4 @@ def check(self, name): return name in self._registry -CYaRonGraders = GraderRegistry() +CYaRonGraders = GraderRegistry() \ No newline at end of file diff --git a/cyaron/graders/mismatch.py b/cyaron/graders/mismatch.py index 7c8eb21..70c2dfc 100644 --- a/cyaron/graders/mismatch.py +++ b/cyaron/graders/mismatch.py @@ -1,6 +1,5 @@ class Mismatch(ValueError): """exception for content mismatch""" - def __init__(self, content, std, *args): """ content -> content got @@ -10,10 +9,8 @@ def __init__(self, content, std, *args): self.content = content self.std = std - class HashMismatch(Mismatch): """exception for hash mismatch""" - def __init__(self, content, std, content_hash, std_hash): """ content -> content got @@ -26,25 +23,11 @@ def __init__(self, content, std, content_hash, std_hash): self.std_hash = std_hash def __str__(self): - return "Hash mismatch: read %s, expected %s" % ( - self.content_hash, - self.std_hash, - ) - + return "Hash mismatch: read %s, expected %s" % (self.content_hash, self.std_hash) class TextMismatch(Mismatch): """exception for text mismatch""" - - def __init__( - self, - content, - std, - err_msg, - lineno=None, - colno=None, - content_token=None, - std_token=None, - ): + def __init__(self, content, std, err_msg, lineno=None, colno=None, content_token=None, std_token=None): """ content -> content got std -> content expected @@ -54,9 +37,7 @@ def __init__( content_token -> the token of content mismatch std_token -> the token of std """ - super(TextMismatch, self).__init__( - content, std, err_msg, lineno, colno, content_token, std_token - ) + super(TextMismatch, self).__init__(content, std, err_msg, lineno, colno, content_token, std_token) self.err_msg = err_msg.format(lineno, colno, content_token, std_token) self.lineno = lineno self.colno = colno diff --git a/cyaron/graders/noipstyle.py b/cyaron/graders/noipstyle.py index 679e059..bf6fa21 100644 --- a/cyaron/graders/noipstyle.py +++ b/cyaron/graders/noipstyle.py @@ -5,46 +5,28 @@ @CYaRonGraders.grader("NOIPStyle") def noipstyle(content, std): - content_lines = strtolines(content.replace("\r\n", "\n")) - std_lines = strtolines(std.replace("\r\n", "\n")) + content_lines = strtolines(content.replace('\r\n', '\n')) + std_lines = strtolines(std.replace('\r\n', '\n')) if len(content_lines) != len(std_lines): - return False, TextMismatch(content, std, "Too many or too few lines.") + return False, TextMismatch(content, std, 'Too many or too few lines.') for i in range(len(content_lines)): if std_lines[i] != content_lines[i]: for j in range(min(len(std_lines[i]), len(content_lines[i]))): if std_lines[i][j] != content_lines[i][j]: - return ( - False, - TextMismatch( - content, - std, - "On line {} column {}, read {}, expected {}.", - i + 1, - j + 1, - content_lines[i][j : j + 5], - std_lines[i][j : j + 5], - ), - ) + return (False, + TextMismatch( + content, std, + 'On line {} column {}, read {}, expected {}.', + i + 1, j + 1, content_lines[i][j:j + 5], + std_lines[i][j:j + 5])) if len(std_lines[i]) > len(content_lines[i]): return False, TextMismatch( - content, - std, - "Too short on line {}.", - i + 1, - j + 1, - content_lines[i][j : j + 5], - std_lines[i][j : j + 5], - ) + content, std, 'Too short on line {}.', i + 1, j + 1, + content_lines[i][j:j + 5], std_lines[i][j:j + 5]) if len(std_lines[i]) < len(content_lines[i]): return False, TextMismatch( - content, - std, - "Too long on line {}.", - i + 1, - j + 1, - content_lines[i][j : j + 5], - std_lines[i][j : j + 5], - ) + content, std, 'Too long on line {}.', i + 1, j + 1, + content_lines[i][j:j + 5], std_lines[i][j:j + 5]) return True, None diff --git a/cyaron/graph.py b/cyaron/graph.py index c95e8b5..34add04 100644 --- a/cyaron/graph.py +++ b/cyaron/graph.py @@ -4,13 +4,12 @@ class Edge: """Class Edge: A class of the edge in the graph""" - def __init__(self, u, v, w): """__init__(self, u, v, w) -> None - Initialize a edge. - int u -> the start vertex - int v -> the end vertex - int w -> the weight. + Initialize a edge. + int u -> the start vertex + int v -> the end vertex + int w -> the weight. """ self.start = u self.end = v @@ -18,36 +17,35 @@ def __init__(self, u, v, w): def __str__(self): """__str__(self) -> str - Return a string to output the edge. The string contains the start vertex, end vertex and weight(u,v,w) and splits with space. + Return a string to output the edge. The string contains the start vertex, end vertex and weight(u,v,w) and splits with space. """ return "%d %d %d" % (self.start, self.end, self.weight) @staticmethod def unweighted_edge(edge): """unweighted_edge(edge) -> str - Return a string to output the edge without weight. The string contains the start vertex, end vertex(u,v) and splits with space. + Return a string to output the edge without weight. The string contains the start vertex, end vertex(u,v) and splits with space. """ - return "%d %d" % (edge.start, edge.end) - + return '%d %d'%(edge.start,edge.end) class Graph: - """Class Graph: A class of the graph""" - + """Class Graph: A class of the graph + """ def __init__(self, point_count, directed=False): """__init__(self, point_count) -> None - Initialize a graph. - int point_count -> the count of the vertexes in the graph. - bool directed = False -> whether the graph is directed(true:directed,false:not directed) + Initialize a graph. + int point_count -> the count of the vertexes in the graph. + bool directed = False -> whether the graph is directed(true:directed,false:not directed) """ self.directed = directed self.edges = [[] for i in range(point_count + 1)] def to_str(self, **kwargs): """to_str(self, **kwargs) -> str - Convert the graph to string with format. Splits with "\n" - **kwargs(Keyword args): - bool shuffle = False -> whether shuffle the output or not - str output(Edge) = str -> the convert function which converts object Edge to str. the default way is to use str() + Convert the graph to string with format. Splits with "\n" + **kwargs(Keyword args): + bool shuffle = False -> whether shuffle the output or not + str output(Edge) = str -> the convert function which converts object Edge to str. the default way is to use str() """ shuffle = kwargs.get("shuffle", False) output = kwargs.get("output", str) @@ -59,8 +57,7 @@ def to_str(self, **kwargs): edge_buf = [] for edge in self.iterate_edges(): edge_buf.append( - Edge(new_node_id[edge.start], new_node_id[edge.end], edge.weight) - ) + Edge(new_node_id[edge.start], new_node_id[edge.end], edge.weight)) random.shuffle(edge_buf) for edge in edge_buf: if not self.directed and random.randint(0, 1) == 0: @@ -73,13 +70,13 @@ def to_str(self, **kwargs): def __str__(self): """__str__(self) -> str - Return a string to output the graph. The string contains all the edges of the graph, splits with "\n". + Return a string to output the graph. The string contains all the edges of the graph, splits with "\n". """ return self.to_str() def iterate_edges(self): """iterate_edges(self) -> Edge - Iter the graph. Order by the start vertex. + Iter the graph. Order by the start vertex. """ for node in self.edges: for edge in node: @@ -88,16 +85,16 @@ def iterate_edges(self): def __add_edge(self, x, y, w): """__add_edge(self, x, y, w) -> None - Add an edge to the graph. + Add an edge to the graph. """ self.edges[x].append(Edge(x, y, w)) def add_edge(self, x, y, **kwargs): """add_edge(self, x, y, **kwargs) -> None - int x -> the start vertex - int y -> the end vertex - **kwargs(Keyword args): - int weight = 1 -> the weight + int x -> the start vertex + int y -> the end vertex + **kwargs(Keyword args): + int weight = 1 -> the weight """ weight = kwargs.get("weight", 1) self.__add_edge(x, y, weight) @@ -107,56 +104,56 @@ def add_edge(self, x, y, **kwargs): @staticmethod def chain(point_count, **kwargs): """chain(point_count, **kwargs) -> Graph - Factory method. Return a chain graph with point_count vertexes. - int point_count -> the count of vertexes - **kwargs(Keyword args): - bool directed = True -> whether the chain is directed(true:directed,false:not directed) - (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) - int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) - int/float weight_gen() - = lambda: random.randint(weight_limit[0], weight_limit[1]) - -> the generator of the weights. It should return the weight. The default way is to use the random.randint() + Factory method. Return a chain graph with point_count vertexes. + int point_count -> the count of vertexes + **kwargs(Keyword args): + bool directed = True -> whether the chain is directed(true:directed,false:not directed) + (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) + int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) + int/float weight_gen() + = lambda: random.randint(weight_limit[0], weight_limit[1]) + -> the generator of the weights. It should return the weight. The default way is to use the random.randint() """ return Graph.tree(point_count, 1, 0, **kwargs) @staticmethod def flower(point_count, **kwargs): """flower(point_count, **kwargs) -> Graph - Factory method. Return a flower graph with point_count vertexes. - int point_count -> the count of vertexes - **kwargs(Keyword args): - bool directed = True -> whether the chain is directed(true:directed,false:not directed) - (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) - int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) - int/float weight_gen() - = lambda: random.randint(weight_limit[0], weight_limit[1]) - -> the generator of the weights. It should return the weight. The default way is to use the random.randint() + Factory method. Return a flower graph with point_count vertexes. + int point_count -> the count of vertexes + **kwargs(Keyword args): + bool directed = True -> whether the chain is directed(true:directed,false:not directed) + (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) + int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) + int/float weight_gen() + = lambda: random.randint(weight_limit[0], weight_limit[1]) + -> the generator of the weights. It should return the weight. The default way is to use the random.randint() """ return Graph.tree(point_count, 0, 1, **kwargs) @staticmethod def tree(point_count, chain=0, flower=0, **kwargs): """tree(point_count, chain=0, flower=0, **kwargs) -> Graph - Factory method. Return a tree with point_count vertexes. - int point_count -> the count of vertexes - float chain = 0 -> 1 means the tree is a chain - float flower = 0 -> 1 means the tree is a flower - NOTICE:only either chain or flower can be True - **kwargs(Keyword args): - bool directed = False -> whether the chain is directed(true:directed,false:not directed) - (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) - int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) - int/float weight_gen() - = lambda: random.randint(weight_limit[0], weight_limit[1]) - -> the generator of the weights. It should return the weight. The default way is to use the random.randint() + Factory method. Return a tree with point_count vertexes. + int point_count -> the count of vertexes + float chain = 0 -> 1 means the tree is a chain + float flower = 0 -> 1 means the tree is a flower + NOTICE:only either chain or flower can be True + **kwargs(Keyword args): + bool directed = False -> whether the chain is directed(true:directed,false:not directed) + (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) + int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) + int/float weight_gen() + = lambda: random.randint(weight_limit[0], weight_limit[1]) + -> the generator of the weights. It should return the weight. The default way is to use the random.randint() """ directed = kwargs.get("directed", False) weight_limit = kwargs.get("weight_limit", (1, 1)) if not list_like(weight_limit): weight_limit = (1, weight_limit) weight_gen = kwargs.get( - "weight_gen", lambda: random.randint(weight_limit[0], weight_limit[1]) - ) + "weight_gen", lambda: random.randint( + weight_limit[0], weight_limit[1])) if not 0 <= chain <= 1 or not 0 <= flower <= 1: raise Exception("chain and flower must be between 0 and 1") @@ -185,51 +182,51 @@ def tree(point_count, chain=0, flower=0, **kwargs): @staticmethod def binary_tree(point_count, left=0, right=0, **kwargs): """binary_tree(point_count, left=0, right=0, **kwargs) -> Graph - Factory method. Return a binary tree with point_count vertexes. - int point_count -> the count of vertexes - float left = 0 -> random arg. should be in [0,1] - float right = 0 -> random arg. should be in [0,1] - NOTICE:left+right mustn't be greater than 1 - **kwargs(Keyword args): - bool directed = False -> whether the binary tree is directed(true:directed,false:not directed) - (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) - int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) - int/float weight_gen() - = lambda: random.randint(weight_limit[0], weight_limit[1]) - -> the generator of the weights. It should return the weight. The default way is to use the random.randint() + Factory method. Return a binary tree with point_count vertexes. + int point_count -> the count of vertexes + float left = 0 -> random arg. should be in [0,1] + float right = 0 -> random arg. should be in [0,1] + NOTICE:left+right mustn't be greater than 1 + **kwargs(Keyword args): + bool directed = False -> whether the binary tree is directed(true:directed,false:not directed) + (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) + int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) + int/float weight_gen() + = lambda: random.randint(weight_limit[0], weight_limit[1]) + -> the generator of the weights. It should return the weight. The default way is to use the random.randint() """ directed = kwargs.get("directed", False) weight_limit = kwargs.get("weight_limit", (1, 1)) if not list_like(weight_limit): weight_limit = (1, weight_limit) weight_gen = kwargs.get( - "weight_gen", lambda: random.randint(weight_limit[0], weight_limit[1]) - ) + "weight_gen", lambda: random.randint( + weight_limit[0], weight_limit[1])) if not 0 <= left <= 1 or not 0 <= right <= 1: raise Exception("left and right must be between 0 and 1") if left + right > 1: raise Exception("left plus right must be smaller than 1") - - can_left = [1] - can_right = [1] + + can_left=[1] + can_right=[1] graph = Graph(point_count, directed) for i in range(2, point_count + 1): edge_pos = random.random() node = 0 # Left if edge_pos < left or left + right < edge_pos <= (1.0 - left - right) / 2: - point_index = random.randint(0, len(can_left) - 1) + point_index = random.randint(0,len(can_left)-1) node = can_left[point_index] - del_last_node = can_left.pop() # Save a copy of the last element + del_last_node = can_left.pop() # Save a copy of the last element if point_index < len(can_left): # If the chosen element isn't the last one, # Copy the last one to the position of the chosen one can_left[point_index] = del_last_node # Right else: - # elif left <= edge_pos <= left + right or (1.0 - left - right) / 2 < edge_pos < 1: - point_index = random.randint(0, len(can_right) - 1) + # elif left <= edge_pos <= left + right or (1.0 - left - right) / 2 < edge_pos < 1: + point_index = random.randint(0,len(can_right)-1) node = can_right[point_index] del_last_node = can_right.pop() if point_index < len(can_right): @@ -243,18 +240,18 @@ def binary_tree(point_count, left=0, right=0, **kwargs): @staticmethod def graph(point_count, edge_count, **kwargs): """graph(point_count, edge_count, **kwargs) -> Graph - Factory method. Return a graph with point_count vertexes and edge_count edges. - int point_count -> the count of vertexes - int edge_count -> the count of edges - **kwargs(Keyword args): - bool self_loop = True -> whether to allow self loops or not - bool repeated_edges = True -> whether to allow repeated edges or not - bool directed = False -> whether the chain is directed(true:directed,false:not directed) - (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) - int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) - int/float weight_gen() - = lambda: random.randint(weight_limit[0], weight_limit[1]) - -> the generator of the weights. It should return the weight. The default way is to use the random.randint() + Factory method. Return a graph with point_count vertexes and edge_count edges. + int point_count -> the count of vertexes + int edge_count -> the count of edges + **kwargs(Keyword args): + bool self_loop = True -> whether to allow self loops or not + bool repeated_edges = True -> whether to allow repeated edges or not + bool directed = False -> whether the chain is directed(true:directed,false:not directed) + (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) + int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) + int/float weight_gen() + = lambda: random.randint(weight_limit[0], weight_limit[1]) + -> the generator of the weights. It should return the weight. The default way is to use the random.randint() """ directed = kwargs.get("directed", False) self_loop = kwargs.get("self_loop", True) @@ -263,8 +260,8 @@ def graph(point_count, edge_count, **kwargs): if not list_like(weight_limit): weight_limit = (1, weight_limit) weight_gen = kwargs.get( - "weight_gen", lambda: random.randint(weight_limit[0], weight_limit[1]) - ) + "weight_gen", lambda: random.randint( + weight_limit[0], weight_limit[1])) graph = Graph(point_count, directed) used_edges = set() i = 0 @@ -272,9 +269,7 @@ def graph(point_count, edge_count, **kwargs): u = random.randint(1, point_count) v = random.randint(1, point_count) - if (not self_loop and u == v) or ( - not repeated_edges and (u, v) in used_edges - ): + if (not self_loop and u == v) or (not repeated_edges and (u, v) in used_edges): # Then we generate a new pair of nodes continue @@ -291,34 +286,32 @@ def graph(point_count, edge_count, **kwargs): @staticmethod def DAG(point_count, edge_count, **kwargs): """DAG(point_count, edge_count, **kwargs) -> Graph - Factory method. Return a graph with point_count vertexes and edge_count edges. - int point_count -> the count of vertexes - int edge_count -> the count of edges - **kwargs(Keyword args): - bool self_loop = False -> whether to allow self loops or not - bool repeated_edges = True -> whether to allow repeated edges or not - bool loop = False -> whether to allow loops or not - (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) - int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) - int/float weight_gen() - = lambda: random.randint(weight_limit[0], weight_limit[1]) - -> the generator of the weights. It should return the weight. The default way is to use the random.randint() + Factory method. Return a graph with point_count vertexes and edge_count edges. + int point_count -> the count of vertexes + int edge_count -> the count of edges + **kwargs(Keyword args): + bool self_loop = False -> whether to allow self loops or not + bool repeated_edges = True -> whether to allow repeated edges or not + bool loop = False -> whether to allow loops or not + (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) + int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) + int/float weight_gen() + = lambda: random.randint(weight_limit[0], weight_limit[1]) + -> the generator of the weights. It should return the weight. The default way is to use the random.randint() """ if edge_count < point_count - 1: - raise Exception( - "the number of edges of connected graph must more than the number of nodes - 1" - ) + raise Exception("the number of edges of connected graph must more than the number of nodes - 1") - self_loop = kwargs.get("self_loop", False) # DAG default has no loop + self_loop = kwargs.get("self_loop", False) # DAG default has no loop repeated_edges = kwargs.get("repeated_edges", True) loop = kwargs.get("loop", False) weight_limit = kwargs.get("weight_limit", (1, 1)) if not list_like(weight_limit): weight_limit = (1, weight_limit) weight_gen = kwargs.get( - "weight_gen", lambda: random.randint(weight_limit[0], weight_limit[1]) - ) - + "weight_gen", lambda: random.randint( + weight_limit[0], weight_limit[1])) + used_edges = set() edge_buf = list(Graph.tree(point_count, weight_gen=weight_gen).iterate_edges()) graph = Graph(point_count, directed=True) @@ -327,10 +320,10 @@ def DAG(point_count, edge_count, **kwargs): if loop and random.randint(1, 2) == 1: edge.start, edge.end = edge.end, edge.start graph.add_edge(edge.start, edge.end, weight=edge.weight) - + if not repeated_edges: used_edges.add((edge.start, edge.end)) - + i = point_count - 1 while i < edge_count: u = random.randint(1, point_count) @@ -339,9 +332,7 @@ def DAG(point_count, edge_count, **kwargs): if not loop and u > v: u, v = v, u - if (not self_loop and u == v) or ( - not repeated_edges and (u, v) in used_edges - ): + if (not self_loop and u == v) or (not repeated_edges and (u, v) in used_edges): # Then we generate a new pair of nodes continue @@ -357,22 +348,20 @@ def DAG(point_count, edge_count, **kwargs): @staticmethod def UDAG(point_count, edge_count, **kwargs): """UDAG(point_count, edge_count, **kwargs) -> Graph - Factory method. Return a graph with point_count vertexes and edge_count edges. - int point_count -> the count of vertexes - int edge_count -> the count of edges - **kwargs(Keyword args): - bool self_loop = True -> whether to allow self loops or not - bool repeated_edges = True -> whether to allow repeated edges or not - (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) - int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) - int/float weight_gen() - = lambda: random.randint(weight_limit[0], weight_limit[1]) - -> the generator of the weights. It should return the weight. The default way is to use the random.randint() + Factory method. Return a graph with point_count vertexes and edge_count edges. + int point_count -> the count of vertexes + int edge_count -> the count of edges + **kwargs(Keyword args): + bool self_loop = True -> whether to allow self loops or not + bool repeated_edges = True -> whether to allow repeated edges or not + (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) + int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) + int/float weight_gen() + = lambda: random.randint(weight_limit[0], weight_limit[1]) + -> the generator of the weights. It should return the weight. The default way is to use the random.randint() """ - if edge_count < point_count - 1: - raise Exception( - "the number of edges of connected graph must more than the number of nodes - 1" - ) + if edge_count < point_count - 1: + raise Exception("the number of edges of connected graph must more than the number of nodes - 1") self_loop = kwargs.get("self_loop", True) repeated_edges = kwargs.get("repeated_edges", True) @@ -380,9 +369,9 @@ def UDAG(point_count, edge_count, **kwargs): if not list_like(weight_limit): weight_limit = (1, weight_limit) weight_gen = kwargs.get( - "weight_gen", lambda: random.randint(weight_limit[0], weight_limit[1]) - ) - + "weight_gen", lambda: random.randint( + weight_limit[0], weight_limit[1])) + used_edges = set() graph = Graph.tree(point_count, weight_gen=weight_gen, directed=False) @@ -390,15 +379,13 @@ def UDAG(point_count, edge_count, **kwargs): if not repeated_edges: used_edges.add((edge.start, edge.end)) used_edges.add((edge.end, edge.start)) - + i = point_count - 1 while i < edge_count: u = random.randint(1, point_count) v = random.randint(1, point_count) - if (not self_loop and u == v) or ( - not repeated_edges and (u, v) in used_edges - ): + if (not self_loop and u == v) or (not repeated_edges and (u, v) in used_edges): # Then we generate a new pair of nodes continue @@ -415,16 +402,16 @@ def UDAG(point_count, edge_count, **kwargs): @staticmethod def hack_spfa(point_count, **kwargs): """hack_spfa(point_count, **kwargs) -> None - Factory method. Return a spfa graph with point_count vertexes - int point_count -> the count of vertexes - **kwargs(Keyword args): - bool directed = False -> whether the chain is directed(true:directed,false:not directed) - (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) - int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) - int extra_edge = 2 -> the number of extra edges - int/float weight_gen() - = lambda: random.randint(weight_limit[0], weight_limit[1]) - -> the generator of the weights. It should return the weight. The default way is to use the random.randint() + Factory method. Return a spfa graph with point_count vertexes + int point_count -> the count of vertexes + **kwargs(Keyword args): + bool directed = False -> whether the chain is directed(true:directed,false:not directed) + (int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included) + int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included) + int extra_edge = 2 -> the number of extra edges + int/float weight_gen() + = lambda: random.randint(weight_limit[0], weight_limit[1]) + -> the generator of the weights. It should return the weight. The default way is to use the random.randint() """ directed = kwargs.get("directed", False) extraedg = kwargs.get("extra_edge", 2) @@ -432,8 +419,8 @@ def hack_spfa(point_count, **kwargs): if not list_like(weight_limit): weight_limit = (1, weight_limit) weight_gen = kwargs.get( - "weight_gen", lambda: random.randint(weight_limit[0], weight_limit[1]) - ) + "weight_gen", lambda: random.randint( + weight_limit[0], weight_limit[1])) point_to_skip = point_count + 3 graph = Graph(point_count, directed) @@ -443,18 +430,15 @@ def hack_spfa(point_count, **kwargs): for i in range(1, half): (x, y) = (i, i + 1) - graph.add_edge( - x + (x >= point_to_skip), y + (y >= point_to_skip), weight=weight_gen() - ) + graph.add_edge(x + (x >= point_to_skip), y + + (y >= point_to_skip), weight=weight_gen()) (x, y) = (i + half, i + half + 1) - graph.add_edge( - x + (x >= point_to_skip), y + (y >= point_to_skip), weight=weight_gen() - ) + graph.add_edge(x + (x >= point_to_skip), y + + (y >= point_to_skip), weight=weight_gen()) for i in range(1, half + 1): (x, y) = (i, i + half) - graph.add_edge( - x + (x >= point_to_skip), y + (y >= point_to_skip), weight=weight_gen() - ) + graph.add_edge(x + (x >= point_to_skip), y + + (y >= point_to_skip), weight=weight_gen()) for i in range(extraedg): u = random.randint(1, point_count) diff --git a/cyaron/io.py b/cyaron/io.py index 2c00ca7..9e8a4bc 100644 --- a/cyaron/io.py +++ b/cyaron/io.py @@ -174,21 +174,32 @@ def input_writeln(self, *args, **kwargs): args.append("\n") self.input_write(*args, **kwargs) - def output_gen(self, shell_cmd): - """output_gen(self, shell_cmd) -> None + def output_gen(self, shell_cmd, time_limit=None): + """output_gen(self, shell_cmd, time_limit) -> None Run the command shell_cmd(usually the std programme) and send it the input file as stdin. Write its output to the output file. - str shell_cmd -> the command to run, usually the std programme + str shell_cmd -> the command to run, usually the std program. + int | None time_limit -> time limit (seconds) of the command to run. """ self.flush_buffer() origin_pos = self.input_file.tell() self.input_file.seek(0) - subprocess.check_call( - shell_cmd, - shell=True, - stdin=self.input_file, - stdout=self.output_file, - universal_newlines=True, - ) + if time_limit is not None: + subprocess.check_call( + shell_cmd, + shell=True, + timeout=time_limit, + stdin=self.input_file, + stdout=self.output_file, + universal_newlines=True, + ) + else: + subprocess.check_call( + shell_cmd, + shell=True, + stdin=self.input_file, + stdout=self.output_file, + universal_newlines=True, + ) self.input_file.seek(origin_pos) log.debug(self.output_filename, " done") diff --git a/cyaron/log.py b/cyaron/log.py index 2dbb922..33b3b0c 100644 --- a/cyaron/log.py +++ b/cyaron/log.py @@ -2,30 +2,24 @@ from functools import partial import sys from threading import Lock - try: import colorful except ImportError: - class colorful: def __getattr__(self, attr): return lambda st: st - colorful = colorful() from .utils import make_unicode __print = print - - def _print(*args, **kwargs): flush = False - if "flush" in kwargs: - flush = kwargs["flush"] - del kwargs["flush"] + if 'flush' in kwargs: + flush = kwargs['flush'] + del kwargs['flush'] __print(*args, **kwargs) if flush: - kwargs.get("file", sys.stdout).flush() - + kwargs.get('file', sys.stdout).flush() def _join_dict(a, b): """join two dict""" @@ -34,11 +28,8 @@ def _join_dict(a, b): c[k] = v return c - _log_funcs = {} _log_lock = Lock() - - def log(funcname, *args, **kwargs): """log with log function specified by ``funcname``""" _log_lock.acquire() @@ -46,7 +37,6 @@ def log(funcname, *args, **kwargs): _log_lock.release() return rv - """5 log levels 1. debug: debug info 2. info: common info @@ -55,12 +45,11 @@ def log(funcname, *args, **kwargs): 5. error: errors """ -debug = partial(log, "debug") -info = partial(log, "info") -print = partial(log, "print") -warn = partial(log, "warn") -error = partial(log, "error") - +debug = partial(log, 'debug') +info = partial(log, 'info') +print = partial(log, 'print') +warn = partial(log, 'warn') +error = partial(log, 'error') def register_logfunc(funcname, func): """register logfunc @@ -75,21 +64,10 @@ def register_logfunc(funcname, func): except KeyError: pass - -_nb_print = lambda *args, **kwargs: _print(*args, **_join_dict(kwargs, {"flush": True})) -_nb_print_e = lambda *args, **kwargs: _print( - *args, **_join_dict(kwargs, {"file": sys.stderr, "flush": True}) -) -_cl_print = lambda color, *args, **kwargs: ( - _nb_print(*[color(make_unicode(item)) for item in args], **kwargs) - if sys.stdout.isatty() - else _nb_print(*args, **kwargs) -) -_cl_print_e = lambda color, *args, **kwargs: ( - _nb_print_e(*[color(make_unicode(item)) for item in args], **kwargs) - if sys.stderr.isatty() - else _nb_print_e(*args, **kwargs) -) +_nb_print = lambda *args, **kwargs: _print(*args, **_join_dict(kwargs, {'flush': True})) +_nb_print_e = lambda *args, **kwargs: _print(*args, **_join_dict(kwargs, {'file': sys.stderr, 'flush': True})) +_cl_print = lambda color, *args, **kwargs: _nb_print(*[color(make_unicode(item)) for item in args], **kwargs) if sys.stdout.isatty() else _nb_print(*args, **kwargs) +_cl_print_e = lambda color, *args, **kwargs: _nb_print_e(*[color(make_unicode(item)) for item in args], **kwargs) if sys.stderr.isatty() else _nb_print_e(*args, **kwargs) _default_debug = partial(_cl_print, colorful.cyan) _default_info = partial(_cl_print, colorful.blue) @@ -97,32 +75,28 @@ def register_logfunc(funcname, func): _default_warn = partial(_cl_print_e, colorful.yellow) _default_error = partial(_cl_print_e, colorful.red) - def set_quiet(): """set log mode to "quiet" """ - register_logfunc("debug", None) - register_logfunc("info", None) - register_logfunc("print", _default_print) - register_logfunc("warn", None) - register_logfunc("error", _default_error) - + register_logfunc('debug', None) + register_logfunc('info', None) + register_logfunc('print', _default_print) + register_logfunc('warn', None) + register_logfunc('error', _default_error) def set_normal(): """set log mode to "normal" """ - register_logfunc("debug", None) - register_logfunc("info", _default_info) - register_logfunc("print", _default_print) - register_logfunc("warn", _default_warn) - register_logfunc("error", _default_error) - + register_logfunc('debug', None) + register_logfunc('info', _default_info) + register_logfunc('print', _default_print) + register_logfunc('warn', _default_warn) + register_logfunc('error', _default_error) def set_verbose(): """set log mode to "verbose" """ - register_logfunc("debug", _default_debug) - register_logfunc("info", _default_info) - register_logfunc("print", _default_print) - register_logfunc("warn", _default_warn) - register_logfunc("error", _default_error) - + register_logfunc('debug', _default_debug) + register_logfunc('info', _default_info) + register_logfunc('print', _default_print) + register_logfunc('warn', _default_warn) + register_logfunc('error', _default_error) set_normal() diff --git a/cyaron/math.py b/cyaron/math.py index a23d332..eb7379d 100644 --- a/cyaron/math.py +++ b/cyaron/math.py @@ -1,7 +1,7 @@ -# coding=utf8 -""" +#coding=utf8 +''' forked from https://blog.dreamshire.com/common-functions-routines-project-euler/ -""" +''' from __future__ import absolute_import from math import sqrt, ceil, gcd from functools import reduce @@ -10,10 +10,8 @@ fact = (1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880) - - -def help(): # Give help informations - help_txt = """ +def help(): #Give help informations + help_txt=""" Welcome to CYaRon/math.py help! Functions are: | factorial(n) - The factorial of n @@ -42,73 +40,49 @@ def help(): # Give help informations """ print(help_txt) +def factorial(n): return reduce(lambda x,y:x*y,range(1,n+1),1) -def factorial(n): - return reduce(lambda x, y: x * y, range(1, n + 1), 1) - - -def is_perm(a, b): - return sorted(str(a)) == sorted(str(b)) - +def is_perm(a,b): return sorted(str(a))==sorted(str(b)) -def is_palindromic(n): - n = str(n) - return n == n[::-1] +def is_palindromic(n): n=str(n); return n==n[::-1] +def is_pandigital(n, s=9): n=str(n); return len(n)==s and not '1234567890'[:s].strip(n) -def is_pandigital(n, s=9): - n = str(n) - return len(n) == s and not "1234567890"[:s].strip(n) - - -# --- Calculate the sum of proper divisors for n-------------------------------------------------- +#--- Calculate the sum of proper divisors for n-------------------------------------------------- def d(n): s = 1 t = sqrt(n) - for i in range(2, int(t) + 1): - if n % i == 0: - s += i + n / i - if t == int(t): - s -= t # correct s if t is a perfect square + for i in range(2, int(t)+1): + if n % i == 0: s += i + n/i + if t == int(t): s -= t #correct s if t is a perfect square return s - -# --- Create a list of all palindromic numbers with k digits-------------------------------------- +#--- Create a list of all palindromic numbers with k digits-------------------------------------- def pal_list(k): if k == 1: return [1, 2, 3, 4, 5, 6, 7, 8, 9] - return [ - sum( - [ - n * (10**i) - for i, n in enumerate( - ([x] + list(ys) + [z] + list(ys)[::-1] + [x]) - if k % 2 - else ([x] + list(ys) + list(ys)[::-1] + [x]) - ) - ] - ) - for x in range(1, 10) - for ys in itertools.product(range(10), repeat=int(k / 2) - 1) - for z in (range(10) if k % 2 else (None,)) - ] - - -# --- sum of factorial's digits------------------------------------------------------------------- + return [sum([n*(10**i) for i,n in enumerate(([x]+list(ys)+[z]+list(ys)[::-1]+[x]) if k%2 + else ([x]+list(ys)+list(ys)[::-1]+[x]))]) + for x in range(1,10) + for ys in itertools.product(range(10), repeat=int(k/2)-1) + for z in (range(10) if k%2 else (None,))] + + +#--- sum of factorial's digits------------------------------------------------------------------- def sof_digits(n): - if n == 0: - return 1 + if n==0: return 1 s = 0 while n > 0: s, n = s + fact[n % 10], n // 10 return s -# --- find the nth Fibonacci number--------------------------------------------------------------- + +#--- find the nth Fibonacci number--------------------------------------------------------------- def fibonacci(n): """ Find the nth number in the Fibonacci series. Example: - + >>>fibonacci(100) 354224848179261915075 @@ -120,7 +94,6 @@ def fibonacci(n): raise ValueError("Negative arguments not implemented") return _fib(n)[0] - # Returns a tuple (F(n), F(n+1)) def _fib(n): if n == 0: @@ -135,40 +108,38 @@ def _fib(n): return (d, c + d) -# --- sum of squares of digits------------------------------------------------------------------- +#--- sum of squares of digits------------------------------------------------------------------- def sos_digits(n): s = 0 while n > 0: - s, n = s + (n % 10) ** 2, n // 10 + s, n = s + (n % 10)**2, n // 10 return s - -# --- sum of the digits to a power e------------------------------------------------------------- +#--- sum of the digits to a power e------------------------------------------------------------- def pow_digits(n, e): s = 0 while n > 0: - s, n = s + (n % 10) ** e, n // 10 + s, n = s + (n % 10)**e, n // 10 return s -# --- check n for prime-------------------------------------------------------------------------- + +#--- check n for prime-------------------------------------------------------------------------- def is_prime(n): - if n <= 1: - return False - if n <= 3: - return True - if n % 2 == 0 or n % 3 == 0: - return False + if n <= 1: return False + if n <= 3: return True + if n%2==0 or n%3 == 0: return False r = int(sqrt(n)) f = 5 while f <= r: - if n % f == 0 or n % (f + 2) == 0: - return False - f += 6 + if n%f == 0 or n%(f+2) == 0: return False + f+= 6 return True -# --- Miller-Rabin primality test---------------------------------------------------------------- + + +#--- Miller-Rabin primality test---------------------------------------------------------------- def miller_rabin(n): """ Check n for primalty: Example: @@ -192,33 +163,33 @@ def miller_rabin(n): return False return True - def miller_rabin_pass(a, s, d, n): a_to_power = pow(a, d, n) if a_to_power == 1: return True - for i in range(s - 1): + for i in range(s-1): if a_to_power == n - 1: return True a_to_power = (a_to_power * a_to_power) % n return a_to_power == n - 1 -# --- factor a number into primes and frequency---------------------------------------------------- + +#--- factor a number into primes and frequency---------------------------------------------------- def factor(n): """ - find the prime factors of n along with their frequencies. Example: + find the prime factors of n along with their frequencies. Example: - >>> factor(786456) - [(2,3), (3,3), (11,1), (331,1)] + >>> factor(786456) + [(2,3), (3,3), (11,1), (331,1)] - Source: Project Euler forums for problem #3 + Source: Project Euler forums for problem #3 """ f, factors, prime_gaps = 1, [], [2, 4, 2, 4, 6, 2, 6, 4] if n < 1: return [] while True: - for gap in [1, 1, 2, 2, 4] if f < 11 else prime_gaps: + for gap in ([1, 1, 2, 2, 4] if f < 11 else prime_gaps): f += gap if f * f > n: # If f > sqrt(n) if n == 1: @@ -234,7 +205,7 @@ def factor(n): factors.append((f, e)) -# --- generate permutations----------------------------------------------------------------------- +#--- generate permutations----------------------------------------------------------------------- def perm(n, s): """ requires function factorial() @@ -243,41 +214,43 @@ def perm(n, s): >>>perm(30, 'abcde') bcade """ - if len(s) == 1: - return s - q, r = divmod(n, factorial(len(s) - 1)) - return s[q] + perm(r, s[:q] + s[q + 1 :]) + if len(s)==1: return s + q, r = divmod(n, factorial(len(s)-1)) + return s[q] + perm(r, s[:q] + s[q+1:]) + -# --- binomial coefficients----------------------------------------------------------------------- + +#--- binomial coefficients----------------------------------------------------------------------- def binomial(n, k): """ Calculate C(n,k), the number of ways can k be chosen from n. Example: - + >>>binomial(30,12) 86493225 """ nt = 1 - for t in range(min(k, n - k)): - nt = nt * (n - t) // (t + 1) + for t in range(min(k, n-k)): + nt = nt * (n-t) // (t+1) return nt -# --- catalan number------------------------------------------------------------------------------ +#--- catalan number------------------------------------------------------------------------------ def catalan_number(n): """ Calculate the nth Catalan number. Example: - + >>>catalan_number(10) 16796 """ nm = dm = 1 - for k in range(2, n + 1): - nm, dm = (nm * (n + k), dm * k) + for k in range(2, n+1): + nm, dm = (nm*(n+k), dm*k) return nm / dm -# --- generate prime numbers---------------------------------------------------------------------- + +#--- generate prime numbers---------------------------------------------------------------------- def prime_sieve(n): """ Return a list of prime numbers from 2 to a prime < n. Very fast (n<10,000,000) in 0.4 sec. @@ -289,15 +262,16 @@ def prime_sieve(n): Algorithm & Python source: Robert William Hanks http://stackoverflow.com/questions/17773352/python-sieve-prime-numbers """ - sieve = [True] * (n // 2) - for i in range(3, int(n**0.5) + 1, 2): - if sieve[i // 2]: - sieve[i * i // 2 :: i] = [False] * ((n - i * i - 1) // (2 * i) + 1) - return [2] + [2 * i + 1 for i in range(1, n // 2) if sieve[i]] + sieve = [True] * (n//2) + for i in range(3,int(n**0.5)+1,2): + if sieve[i//2]: + sieve[i*i//2::i] = [False] * ((n-i*i-1)//(2*i)+1) + return [2] + [2*i+1 for i in range(1,n//2) if sieve[i]] -# --- bezout coefficients-------------------------------------------------------------------------- -def exgcd(a, b): + +#--- bezout coefficients-------------------------------------------------------------------------- +def exgcd(a,b): """ Bézout coefficients (u,v) of (a,b) as: @@ -313,131 +287,85 @@ def exgcd(a, b): Algorithm source: Pierre L. Douillet http://www.douillet.info/~douillet/working_papers/bezout/node2.html """ - u, v, s, t = 1, 0, 0, 1 - while b != 0: - q, r = divmod(a, b) + u, v, s, t = 1, 0, 0, 1 + while b !=0: + q, r = divmod(a,b) a, b = b, r - u, s = s, u - q * s - v, t = t, v - q * t + u, s = s, u - q*s + v, t = t, v - q*t return (u, v, a) - -def mod_inverse(a, b): - x, y, z = exgcd(a, b) - return x - + + +def mod_inverse(a,b): + x,y,z = exgcd(a,b) + return x; def phi(x): - if x == 1: - return 1 - factors = factor(x) - ans = x + if x==1: + return 1; + factors = factor(x); + ans = x; for prime in factors: - ans = int(ans / prime[0] * (prime[0] - 1)) + ans=int(ans / prime[0]*(prime[0]-1)) return ans - def miu(x): - if x == 1: - return 1 + if x==1: + return 1; factors = factor(x) for prime in factors: - if prime[1] > 1: - return 0 - return 1 - (len(factors) and 1) * 2 - - -# --- number base conversion ------------------------------------------------------------------- -# source: http://interactivepython.org/runestone/static/pythonds/Recursion/pythondsConvertinganIntegertoaStringinAnyBase.html -def dec2base(n, base): - convertString = "0123456789ABCDEF" - if n < base: - return convertString[n] - else: - return dec2base(n // base, base) + convertString[n % base] - - -# --- number to words ---------------------------------------------------------------------------- -# this function copied from stackoverflow user: Developer, Oct 5 '13 at 3:45 -def n2words(num, join=True): - """words = {} convert an integer number into words""" - units = ["", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine"] - teens = [ - "", - "Eleven", - "Twelve", - "Thirteen", - "Fourteen", - "Fifteen", - "Sixteen", - "Seventeen", - "Eighteen", - "Nineteen", - ] - tens = [ - "", - "Ten", - "Twenty", - "Thirty", - "Forty", - "Fifty", - "Sixty", - "Seventy", - "Eighty", - "Ninety", - ] - thousands = [ - "", - "Thousand", - "Million", - "Billion", - "Trillion", - "Quadrillion", - "Quintillion", - "Sextillion", - "Septillion", - "Octillion", - "Nonillion", - "Decillion", - "Undecillion", - "Duodecillion", - "Tredecillion", - "Quattuordecillion", - "Sexdecillion", - "Septendecillion", - "Octodecillion", - "Novemdecillion", - "Vigintillion", - ] + if prime[1]>1: + return 0; + return 1-(len(factors) and 1)*2 + + +#--- number base conversion ------------------------------------------------------------------- +#source: http://interactivepython.org/runestone/static/pythonds/Recursion/pythondsConvertinganIntegertoaStringinAnyBase.html +def dec2base(n,base): + convertString = "0123456789ABCDEF" + if n < base: + return convertString[n] + else: + return dec2base(n//base,base) + convertString[n%base] + +#--- number to words ---------------------------------------------------------------------------- +#this function copied from stackoverflow user: Developer, Oct 5 '13 at 3:45 +def n2words(num,join=True): + '''words = {} convert an integer number into words''' + units = ['','One','Two','Three','Four','Five','Six','Seven','Eight','Nine'] + teens = ['','Eleven','Twelve','Thirteen','Fourteen','Fifteen','Sixteen', \ + 'Seventeen','Eighteen','Nineteen'] + tens = ['','Ten','Twenty','Thirty','Forty','Fifty','Sixty','Seventy', \ + 'Eighty','Ninety'] + thousands = ['','Thousand','Million','Billion','Trillion','Quadrillion', \ + 'Quintillion','Sextillion','Septillion','Octillion', \ + 'Nonillion','Decillion','Undecillion','Duodecillion', \ + 'Tredecillion','Quattuordecillion','Sexdecillion', \ + 'Septendecillion','Octodecillion','Novemdecillion', \ + 'Vigintillion'] words = [] - if num == 0: - words.append("zero") + if num==0: words.append('zero') else: - numStr = "%d" % num + numStr = '%d'%num numStrLen = len(numStr) - groups = int((numStrLen + 2) / 3) - numStr = numStr.zfill(groups * 3) - for i in range(0, groups * 3, 3): - h, t, u = int(numStr[i]), int(numStr[i + 1]), int(numStr[i + 2]) - g = groups - (int(i / 3) + 1) - if h >= 1: + groups = int((numStrLen+2)/3) + numStr = numStr.zfill(groups*3) + for i in range(0,groups*3,3): + h,t,u = int(numStr[i]),int(numStr[i+1]),int(numStr[i+2]) + g = groups-(int(i/3)+1) + if h>=1: words.append(units[h]) - words.append("Hundred") - if t > 1: + words.append('Hundred') + if t>1: words.append(tens[t]) - if u >= 1: - words.append(units[u]) - elif t == 1: - if u >= 1: - words.append(teens[u]) - else: - words.append(tens[t]) + if u>=1: words.append(units[u]) + elif t==1: + if u>=1: words.append(teens[u]) + else: words.append(tens[t]) else: - if u >= 1: - words.append(units[u]) - if (g >= 1) and ((h + t + u) > 0): - words.append(thousands[g] + "") - if join: - return " ".join(words) + if u>=1: words.append(units[u]) + if (g>=1) and ((h+t+u)>0): words.append(thousands[g]+'') + if join: return ' '.join(words) return words diff --git a/cyaron/merger.py b/cyaron/merger.py index 41847c3..e7069b6 100644 --- a/cyaron/merger.py +++ b/cyaron/merger.py @@ -1,57 +1,55 @@ from .graph import * - class Merger: def __init__(self, *graphs, **kwargs): """__init__(self, *graphs, **kwargs) -> None - put several graphs into one - list graphs -> the graphs that will be merged - list kwargs: - None + put several graphs into one + list graphs -> the graphs that will be merged + list kwargs: + None """ self.graphs = graphs self.G = Graph(sum([len(i.edges) - 1 for i in graphs]), graphs[0].directed) - - counter = 0 + + counter = 0 for graph in self.graphs: graph.offset = counter for edge in graph.iterate_edges(): - self.G.add_edge( - edge.start + counter, edge.end + counter, weight=edge.weight - ) + self.G.add_edge(edge.start + counter, + edge.end + counter, + weight=edge.weight) counter += len(graph.edges) - 1 def __add_edge(self, u, v, **kwargs): """__add_edge(self, u, v, **kwargs) - tuple u -> (graph_index, vertex) indicating the start point - tuple v -> (graph_index, vertex) indicating the end point - **kwargs: - int weight -> edge weight + tuple u -> (graph_index, vertex) indicating the start point + tuple v -> (graph_index, vertex) indicating the end point + **kwargs: + int weight -> edge weight """ - self.G.add_edge( - self.graphs[u[0]].offset + u[1], - self.graphs[v[0]].offset + v[1], - weight=kwargs.get("weight", 1), - ) - + self.G.add_edge(self.graphs[ u[0] ].offset + u[1], + self.graphs[ v[0] ].offset + v[1], + weight=kwargs.get("weight", 1)) + def add_edge(self, u, v, **kwargs): - """add_edge(self, u, v, **kwargs) -> None""" + """add_edge(self, u, v, **kwargs) -> None + """ self.__add_edge(u, v, **kwargs) def to_str(self, **kwargs): return self.G.to_str(**kwargs) - + def __str__(self): return self.to_str() @staticmethod def component(point_count, edge_count, **kwargs): """component(point_count, edge_count, **kwargs) - generate a graph with certain components - int point_count -> the number of vertices of each component - int edge_count -> the number of edges of each component - **kwargs: - int component_count -> indicating how many components there are + generate a graph with certain components + int point_count -> the number of vertices of each component + int edge_count -> the number of edges of each component + **kwargs: + int component_count -> indicating how many components there are """ component_count = kwargs.get("component_count", (2, 2)) if not list_like(component_count): diff --git a/cyaron/output_capture.py b/cyaron/output_capture.py index 5680526..4bcac58 100644 --- a/cyaron/output_capture.py +++ b/cyaron/output_capture.py @@ -1,12 +1,10 @@ from contextlib import contextmanager import sys - try: from StringIO import StringIO except ImportError: from io import StringIO - @contextmanager def captured_output(): new_out, new_err = StringIO(), StringIO() @@ -15,4 +13,4 @@ def captured_output(): sys.stdout, sys.stderr = new_out, new_err yield sys.stdout, sys.stderr finally: - sys.stdout, sys.stderr = old_out, old_err + sys.stdout, sys.stderr = old_out, old_err \ No newline at end of file diff --git a/cyaron/polygon.py b/cyaron/polygon.py index d73fdec..d0dfca8 100644 --- a/cyaron/polygon.py +++ b/cyaron/polygon.py @@ -6,7 +6,7 @@ class Polygon: - def __init__(self, points=[]): + def __init__(self,points=[]): if not list_like(points): raise Exception("polygon must be constructed by a list of points") self.points = points @@ -15,16 +15,15 @@ def __str__(self): buf = [] for point in self.points: buf.append(str(point[0]) + " " + str(point[1])) - return "\n".join(buf) + return '\n'.join(buf) def perimeter(self): ans = 0 for i in range(0, len(self.points)): a = self.points[i] b = self.points[(i + 1) % len(self.points)] - ans = ans + math.sqrt( - (a[0] - b[0]) * (a[0] - b[0]) + (a[1] - b[1]) * (a[1] - b[1]) - ) + ans = ans + math.sqrt((a[0] - b[0]) * (a[0] - b[0]) + + (a[1] - b[1]) * (a[1] - b[1])) return ans def area(self): @@ -38,8 +37,8 @@ def area(self): ans = ans / 2.0 return ans - # generate a convex hull with n points - # it's possible to have even edges + #generate a convex hull with n points + #it's possible to have even edges @staticmethod def convex_hull(n, **kwargs): # fx, fy are functions which map [0,1] to int or float @@ -76,7 +75,8 @@ def convex_hull(n, **kwargs): a = st[len(st) - 1] b = points[i] o = st[len(st) - 2] - tmp = (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]) + tmp = (a[0] - o[0]) * (b[1] - o[1]) - \ + (a[1] - o[1]) * (b[0] - o[0]) if tmp > 0 or (tmp == 0 and not strict): break st.pop() @@ -87,7 +87,8 @@ def convex_hull(n, **kwargs): a = st[len(st) - 1] b = points[i] o = st[len(st) - 2] - tmp = (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]) + tmp = (a[0] - o[0]) * (b[1] - o[1]) - \ + (a[1] - o[1]) * (b[0] - o[0]) if tmp > 0 or (tmp == 0 and not strict): break st.pop() @@ -109,34 +110,21 @@ def __conquer(points): if len(points) <= 2: return points if len(points) == 3: - (points[1], points[2]) = (points[2], points[1]) + (points[1],points[2])=(points[2],points[1]) return points divide_id = random.randint(2, len(points) - 1) divide_point1 = points[divide_id] divide_k = random.uniform(0.01, 0.99) - divide_point2 = [ - divide_k * (points[1][0] - points[0][0]) + points[0][0], - divide_k * (points[1][1] - points[0][1]) + points[0][1], - ] + divide_point2 = [divide_k * (points[1][0] - points[0][0]) + points[0][0], + divide_k * (points[1][1] - points[0][1]) + points[0][1]] # path: points[0]->points[divide]->points[1] # dividing line in the form Ax+By+C=0 - divide_line = [ - divide_point2[1] - divide_point1[1], - divide_point1[0] - divide_point2[0], - -divide_point1[0] * divide_point2[1] + divide_point1[1] * divide_point2[0], - ] - p0 = ( - divide_line[0] * points[0][0] - + divide_line[1] * points[0][1] - + divide_line[2] - >= 0 - ) - p1 = ( - divide_line[0] * points[1][0] - + divide_line[1] * points[1][1] - + divide_line[2] - >= 0 - ) + divide_line = [divide_point2[1] - divide_point1[1], + divide_point1[0] - divide_point2[0], + -divide_point1[0] * divide_point2[1] + + divide_point1[1] * divide_point2[0]] + p0 = (divide_line[0] * points[0][0] + divide_line[1] * points[0][1] + divide_line[2] >= 0) + p1 = (divide_line[0] * points[1][0] + divide_line[1] * points[1][1] + divide_line[2] >= 0) if p0 == p1: # the divide point isn't good enough... return Polygon.__conquer(points) s = [[], []] @@ -147,12 +135,7 @@ def __conquer(points): for i in range(2, len(points)): if i == divide_id: continue - pt = ( - divide_line[0] * points[i][0] - + divide_line[1] * points[i][1] - + divide_line[2] - >= 0 - ) + pt = (divide_line[0] * points[i][0] + divide_line[1] * points[i][1] + divide_line[2] >= 0) s[pt].append(points[i]) pa = Polygon.__conquer(s[p0]) pb = Polygon.__conquer(s[not p0]) @@ -169,23 +152,17 @@ def simple_polygon(points): if len(points) <= 3: return Polygon(points) # divide by points[0], points[1] - divide_line = [ - points[1][1] - points[0][1], - points[0][0] - points[1][0], - -points[0][0] * points[1][1] + points[0][1] * points[1][0], - ] + divide_line = [points[1][1] - points[0][1], + points[0][0] - points[1][0], + -points[0][0] * points[1][1] + + points[0][1] * points[1][0]] s = [[], []] s[0].append(points[0]) s[0].append(points[1]) s[1].append(points[1]) s[1].append(points[0]) for i in range(2, len(points)): - pt = ( - divide_line[0] * points[i][0] - + divide_line[1] * points[i][1] - + divide_line[2] - >= 0 - ) + pt = (divide_line[0] * points[i][0] + divide_line[1] * points[i][1] + divide_line[2] >= 0) s[pt].append(points[i]) pa = Polygon.__conquer(s[0]) pb = Polygon.__conquer(s[1]) diff --git a/cyaron/sequence.py b/cyaron/sequence.py index c584a33..625456e 100644 --- a/cyaron/sequence.py +++ b/cyaron/sequence.py @@ -1,13 +1,13 @@ from .utils import * - class Sequence: - """Class Sequence: the tool class for sequences.""" + """Class Sequence: the tool class for sequences. + """ def __init__(self, formula, initial_values=()): """__init__(self, formula, initial_values=() -> None - Create a sequence object. - int formula(int, function) -> the formula function ... + Create a sequence object. + int formula(int, function) -> the formula function ... """ if not callable(formula): raise Exception("formula must be a function") @@ -30,4 +30,4 @@ def get(self, left_range, right_range=None): if right_range is None: return self.__get_one(left_range) - return [self.__get_one(i) for i in range(left_range, right_range + 1)] + return [self.__get_one(i) for i in range(left_range, right_range+1)] diff --git a/cyaron/string.py b/cyaron/string.py index 2a5694e..6af1e5c 100644 --- a/cyaron/string.py +++ b/cyaron/string.py @@ -53,9 +53,7 @@ def random_sentence(word_count_range, **kwargs): def random_paragraph(sentence_count_range, **kwargs): sentence_count = sentence_count_range if list_like(sentence_count_range): - sentence_count = random.randint( - sentence_count_range[0], sentence_count_range[1] - ) + sentence_count = random.randint(sentence_count_range[0], sentence_count_range[1]) word_count_range = kwargs.get("word_count_range", (6, 10)) @@ -97,18 +95,15 @@ def random_paragraph(sentence_count_range, **kwargs): sentences.append(string) - paragraph = reduce( - lambda x, y: x + random.choice(sentence_joiners) + y, sentences - ) + paragraph = reduce(lambda x, y: x + random.choice(sentence_joiners) + y, sentences) return paragraph @staticmethod def random_regular(*args, **kwargs): pattern = args limit_len = int(kwargs.get("limit", "10")) - if limit_len <= 1: - limit_len = 10 - if list_like(args): + if (limit_len <= 1): limit_len = 10 + if (list_like(args)): pattern = random.choice(args) _x = xeger.Xeger(limit=limit_len) return _x.xeger(pattern) diff --git a/cyaron/tests/compare_test.py b/cyaron/tests/compare_test.py index 01d4354..f820a9d 100644 --- a/cyaron/tests/compare_test.py +++ b/cyaron/tests/compare_test.py @@ -11,7 +11,6 @@ log.set_verbose() - class TestCompare(unittest.TestCase): def setUp(self): @@ -52,19 +51,16 @@ def test_noipstyle_incorrect(self): with captured_output() as (out, err): Compare.output("test_another_incorrect.out", std=io) except CompareMismatch as e: - self.assertEqual(e.name, "test_another_incorrect.out") + self.assertEqual(e.name, 'test_another_incorrect.out') e = e.mismatch - self.assertEqual(e.content, "test123\r\ntest124 ") - self.assertEqual(e.std, "test123 \ntest123\n\n") - self.assertEqual(str(e), "On line 2 column 7, read 4, expected 3.") + self.assertEqual(e.content, 'test123\r\ntest124 ') + self.assertEqual(e.std, 'test123 \ntest123\n\n') + self.assertEqual(str(e), 'On line 2 column 7, read 4, expected 3.') else: self.assertTrue(False) result = out.getvalue().strip() - self.assertEqual( - result, - "test_another_incorrect.out: !!!INCORRECT!!! On line 2 column 7, read 4, expected 3.", - ) + self.assertEqual(result, "test_another_incorrect.out: !!!INCORRECT!!! On line 2 column 7, read 4, expected 3.") def test_fulltext_program(self): with open("correct.py", "w") as f: @@ -81,31 +77,19 @@ def test_fulltext_program(self): try: with captured_output() as (out, err): - Compare.program( - "python correct.py", - "python incorrect.py", - std=io, - input=io, - grader="FullText", - ) + Compare.program("python correct.py", "python incorrect.py", std=io, input=io, grader="FullText") except CompareMismatch as e: - self.assertEqual(e.name, "python incorrect.py") + self.assertEqual(e.name, 'python incorrect.py') e = e.mismatch - self.assertEqual(e.content, "2\n") - self.assertEqual(e.std, "1\n") - self.assertEqual( - e.content_hash, - "53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3", - ) - self.assertEqual( - e.std_hash, - "4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865", - ) + self.assertEqual(e.content, '2\n') + self.assertEqual(e.std, '1\n') + self.assertEqual(e.content_hash, '53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3') + self.assertEqual(e.std_hash, '4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865') else: self.assertTrue(False) result = out.getvalue().strip() - correct_out = "python correct.py: Correct \npython incorrect.py: !!!INCORRECT!!! Hash mismatch: read 53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3, expected 4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865" + correct_out = 'python correct.py: Correct \npython incorrect.py: !!!INCORRECT!!! Hash mismatch: read 53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3, expected 4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865' self.assertEqual(result, correct_out) def test_file_input(self): @@ -122,38 +106,28 @@ def test_file_input(self): io.input_writeln("233") with captured_output() as (out, err): - Compare.program( - "python correct.py", - std_program="python std.py", - input=io, - grader="NOIPStyle", - ) + Compare.program("python correct.py", std_program="python std.py", input=io, grader="NOIPStyle") result = out.getvalue().strip() - correct_out = "python correct.py: Correct" + correct_out = 'python correct.py: Correct' self.assertEqual(result, correct_out) def test_concurrent(self): - programs = ["test{}.py".format(i) for i in range(16)] + programs = ['test{}.py'.format(i) for i in range(16)] for fn in programs: - with open(fn, "w") as f: - f.write("print({})".format(16)) - with open("std.py", "w") as f: - f.write("print({})".format(16)) + with open(fn, 'w') as f: + f.write('print({})'.format(16)) + with open('std.py', 'w') as f: + f.write('print({})'.format(16)) with IO() as test: - Compare.program( - *[(sys.executable, program) for program in programs], - std_program=(sys.executable, "std.py"), - max_workers=None, - input=test - ) + Compare.program(*[(sys.executable, program) for program in programs], std_program=(sys.executable, 'std.py'), max_workers=None, input=test) ios = [IO() for i in range(16)] try: for f in ios: - f.output_write("16") + f.output_write('16') with IO() as std: - std.output_write("16") + std.output_write('16') Compare.output(*ios, std=std, max_workers=None) finally: for io in ios: @@ -163,11 +137,7 @@ def test_timeout(self): if sys.version_info >= (3, 3): with IO() as test: try: - Compare.program( - ((sys.executable, "-c", "__import__('time').sleep(10)"), 1), - std=test, - input=test, - ) + Compare.program(((sys.executable, '-c', '__import__(\'time\').sleep(10)'), 1), std=test, input=test) except subprocess.TimeoutExpired: pass else: diff --git a/cyaron/tests/graph_test.py b/cyaron/tests/graph_test.py index 57b3e07..976930a 100644 --- a/cyaron/tests/graph_test.py +++ b/cyaron/tests/graph_test.py @@ -24,14 +24,14 @@ def test_same(self, l, r): def tarjan(graph, n): def new_array(len, val=0): - return [val for _ in range(len + 1)] + return [val for _ in range(len+1)] instack = new_array(n, False) low = new_array(n) dfn = new_array(n, 0) stap = new_array(n) belong = new_array(n) - var = [0, 0, 0] # cnt, bc, stop + var = [0, 0, 0] # cnt, bc, stop # cnt = bc = stop = 0 def dfs(cur): @@ -49,7 +49,7 @@ def dfs(cur): low[cur] = min(low[cur], dfn[v.end]) if dfn[cur] == low[cur]: - v = cur + 1 # set v != cur + v = cur + 1 # set v != cur var[1] += 1 while v != cur: var[2] -= 1 @@ -58,8 +58,8 @@ def dfs(cur): belong[v] = var[1] for i in range(n): - if dfn[i + 1] == 0: - dfs(i + 1) + if dfn[i+1] == 0: + dfs(i+1) return belong @@ -69,20 +69,20 @@ class TestGraph(unittest.TestCase): def test_self_loop(self): graph_size = 20 for _ in range(20): - graph = Graph.graph(graph_size, int(graph_size * 2), self_loop=True) + graph = Graph.graph(graph_size, int(graph_size*2), self_loop=True) has_self_loop = max([e.start == e.end for e in graph.iterate_edges()]) if has_self_loop: break self.assertTrue(has_self_loop) for _ in range(10): - graph = Graph.graph(graph_size, int(graph_size * 2), self_loop=False) + graph = Graph.graph(graph_size, int(graph_size*2), self_loop=False) self.assertFalse(max([e.start == e.end for e in graph.iterate_edges()])) def test_repeated_edges(self): graph_size = 20 for _ in range(20): - graph = Graph.graph(graph_size, int(graph_size * 2), repeated_edges=True) + graph = Graph.graph(graph_size, int(graph_size*2), repeated_edges=True) edges = [(e.start, e.end) for e in graph.iterate_edges()] has_repeated_edges = len(edges) > len(set(edges)) if has_repeated_edges: @@ -90,7 +90,7 @@ def test_repeated_edges(self): self.assertTrue(has_repeated_edges) for _ in range(10): - graph = Graph.graph(graph_size, int(graph_size * 2), repeated_edges=False) + graph = Graph.graph(graph_size, int(graph_size*2), repeated_edges=False) edges = list(graph.iterate_edges()) self.assertEqual(len(edges), len(set(edges))) @@ -101,69 +101,53 @@ def test_tree_connected(self): tree = Graph.tree(graph_size) for edge in tree.iterate_edges(): ufs.merge(edge.start, edge.end) - for i in range(graph_size - 1): - self.assertTrue(ufs.test_same(i + 1, i + 2)) + for i in range(graph_size-1): + self.assertTrue(ufs.test_same(i+1, i+2)) + def test_DAG(self): graph_size = 20 - for _ in range(10): # test 10 times + for _ in range(10): # test 10 times ufs = UnionFindSet(graph_size) - graph = Graph.DAG( - graph_size, - int(graph_size * 1.6), - repeated_edges=False, - self_loop=False, - loop=True, - ) + graph = Graph.DAG(graph_size, int(graph_size*1.6), repeated_edges=False, self_loop=False, loop=True) - self.assertEqual(len(list(graph.iterate_edges())), int(graph_size * 1.6)) + self.assertEqual(len(list(graph.iterate_edges())), int(graph_size*1.6)) for edge in graph.iterate_edges(): ufs.merge(edge.start, edge.end) - for i in range(graph_size - 1): - self.assertTrue(ufs.test_same(i + 1, i + 2)) + for i in range(graph_size-1): + self.assertTrue(ufs.test_same(i+1, i+2)) def test_DAG_without_loop(self): graph_size = 20 - for _ in range(10): # test 10 times + for _ in range(10): # test 10 times ufs = UnionFindSet(graph_size) - graph = Graph.DAG( - graph_size, - int(graph_size * 1.6), - repeated_edges=False, - self_loop=False, - loop=False, - ) + graph = Graph.DAG(graph_size, int(graph_size*1.6), repeated_edges=False, self_loop=False, loop=False) - self.assertEqual(len(list(graph.iterate_edges())), int(graph_size * 1.6)) + self.assertEqual(len(list(graph.iterate_edges())), int(graph_size*1.6)) for edge in graph.iterate_edges(): ufs.merge(edge.start, edge.end) - for i in range(graph_size - 1): - self.assertTrue(ufs.test_same(i + 1, i + 2)) + for i in range(graph_size-1): + self.assertTrue(ufs.test_same(i+1, i+2)) belong = tarjan(graph, graph_size) self.assertEqual(max(belong), graph_size) def test_undirected_graph(self): graph_size = 20 - for _ in range(10): # test 10 times + for _ in range(10): # test 10 times ufs = UnionFindSet(graph_size) - graph = Graph.UDAG( - graph_size, int(graph_size * 1.6), repeated_edges=False, self_loop=False - ) + graph = Graph.UDAG(graph_size, int(graph_size*1.6), repeated_edges=False, self_loop=False) - self.assertEqual(len(list(graph.iterate_edges())), int(graph_size * 1.6)) + self.assertEqual(len(list(graph.iterate_edges())), int(graph_size*1.6)) for edge in graph.iterate_edges(): ufs.merge(edge.start, edge.end) - for i in range(graph_size - 1): - self.assertTrue(ufs.test_same(i + 1, i + 2)) + for i in range(graph_size-1): + self.assertTrue(ufs.test_same(i+1, i+2)) def test_DAG_boundary(self): - with self.assertRaises( - Exception, - msg="the number of edges of connected graph must more than the number of nodes - 1", - ): + with self.assertRaises(Exception, msg="the number of edges of connected graph must more than the number of nodes - 1"): Graph.DAG(8, 6) Graph.DAG(8, 7) diff --git a/cyaron/tests/io_test.py b/cyaron/tests/io_test.py index e8d88a2..b83dc03 100644 --- a/cyaron/tests/io_test.py +++ b/cyaron/tests/io_test.py @@ -2,6 +2,7 @@ import os import shutil import tempfile +import subprocess from cyaron import IO from cyaron.output_capture import captured_output @@ -69,8 +70,34 @@ def test_output_gen(self): output = f.read() self.assertEqual(output.strip("\n"), "233") - def test_output_gen_limits(self): - + def test_output_gen_time_limit_exceeded(self): + time_limit_exceeded = False + with captured_output() as (out, err): + with open("long_time.py", "w") as f: + f.write("import time\ntime.sleep(10)\nprint(1)") + + try: + with IO("test_gen.in", "test_gen.out") as test: + test.output_gen("python long_time.py", time_limit=1) + except subprocess.TimeoutExpired: + time_limit_exceeded = True + self.assertEqual(time_limit_exceeded, True) + + def test_output_gen_time_limit_not_exceeded(self): + time_limit_exceeded = False + with captured_output() as (out, err): + with open("short_time.py", "w") as f: + f.write("import time\ntime.sleep(0.2)\nprint(1)") + + try: + with IO("test_gen.in", "test_gen.out") as test: + test.output_gen("python short_time.py", time_limit=1) + except subprocess.TimeoutExpired: + time_limit_exceeded = True + with open("test_gen.out") as f: + output = f.read() + self.assertEqual(output.strip("\n"), "1") + self.assertEqual(time_limit_exceeded, False) def test_init_overload(self): with IO(file_prefix="data{", data_id=5) as test: diff --git a/cyaron/tests/polygon_test.py b/cyaron/tests/polygon_test.py index cac0ada..7548f3d 100644 --- a/cyaron/tests/polygon_test.py +++ b/cyaron/tests/polygon_test.py @@ -4,9 +4,7 @@ class TestPolygon(unittest.TestCase): def test_convex_hull(self): - hull = Polygon.convex_hull( - 300, fx=lambda x: int(x * 100000), fy=lambda x: int(x * 100000) - ) + hull = Polygon.convex_hull(300, fx=lambda x: int(x * 100000), fy=lambda x: int(x * 100000)) points = hull.points points = sorted(points) # unique @@ -21,7 +19,8 @@ def test_convex_hull(self): a = st[len(st) - 1] b = points[i] o = st[len(st) - 2] - if (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]) >= 0: + if (a[0] - o[0]) * (b[1] - o[1]) - \ + (a[1] - o[1]) * (b[0] - o[0]) >= 0: break st.pop() st.append(points[i]) @@ -31,7 +30,8 @@ def test_convex_hull(self): a = st[len(st) - 1] b = points[i] o = st[len(st) - 2] - if (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]) >= 0: + if (a[0] - o[0]) * (b[1] - o[1]) - \ + (a[1] - o[1]) * (b[0] - o[0]) >= 0: break st.pop() st.append(points[i]) @@ -55,10 +55,8 @@ def test_simple_polygon(self): c = points[j] d = points[(j + 1) % len(points)] prod = lambda x, y: x[0] * y[1] - x[1] * y[0] - t1 = prod( - [c[0] - a[0], c[1] - a[1]], [d[0] - a[0], d[1] - a[1]] - ) * prod([c[0] - b[0], c[1] - b[1]], [d[0] - b[0], d[1] - b[1]]) - t2 = prod( - [a[0] - c[0], a[1] - c[1]], [b[0] - c[0], b[1] - c[1]] - ) * prod([a[0] - d[0], a[1] - d[1]], [b[0] - d[0], b[1] - d[1]]) + t1 = prod([c[0] - a[0], c[1] - a[1]], [d[0] - a[0], d[1] - a[1]]) \ + * prod([c[0] - b[0], c[1] - b[1]], [d[0] - b[0], d[1] - b[1]]) + t2 = prod([a[0] - c[0], a[1] - c[1]], [b[0] - c[0], b[1] - c[1]]) \ + * prod([a[0] - d[0], a[1] - d[1]], [b[0] - d[0], b[1] - d[1]]) self.assertFalse(t1 <= 1e-9 and t2 <= 1e-9) diff --git a/cyaron/tests/sequence_test.py b/cyaron/tests/sequence_test.py index 035fcb3..272a1d5 100644 --- a/cyaron/tests/sequence_test.py +++ b/cyaron/tests/sequence_test.py @@ -20,3 +20,4 @@ def test_func_get_one(self): def test_func_get_many(self): seq = Sequence(lambda i, f: 3 * i + 2 * f(i - 1), [0]) self.assertEqual(seq.get(3, 5), [33, 78, 171]) + diff --git a/cyaron/tests/str_test.py b/cyaron/tests/str_test.py index 517a7ef..3f12fab 100644 --- a/cyaron/tests/str_test.py +++ b/cyaron/tests/str_test.py @@ -25,6 +25,6 @@ def test_random_paragraph(self): String.random_paragraph(10) def test_random_regular(self): - pattern = r"[0-9]+\w_.{0,9}" + pattern = r'[0-9]+\w_.{0,9}' sentence = String.random_regular(pattern, limit=5) - self.assertTrue(re.match(pattern, sentence).group() == sentence) + self.assertTrue(re.match(pattern, sentence).group() == sentence) \ No newline at end of file diff --git a/cyaron/utils.py b/cyaron/utils.py index 11a438e..4ef7358 100644 --- a/cyaron/utils.py +++ b/cyaron/utils.py @@ -1,14 +1,14 @@ def ati(array): """ati(array) -> list - Convert all the elements in the array and return them in a list. + Convert all the elements in the array and return them in a list. """ return [int(i) for i in array] def list_like(data): """list_like(data) -> bool - Judge whether the object data is like a list or a tuple. - object data -> the data to judge + Judge whether the object data is like a list or a tuple. + object data -> the data to judge """ return isinstance(data, tuple) or isinstance(data, list) @@ -24,7 +24,7 @@ def int_like(data): def strtolines(str): - lines = str.split("\n") + lines = str.split('\n') for i in range(len(lines)): lines[i] = lines[i].rstrip() @@ -39,7 +39,6 @@ def make_unicode(data): except NameError: return str(data) - def unpack_kwargs(funcname, kwargs, arg_pattern): rv = {} kwargs = kwargs.copy() @@ -59,15 +58,7 @@ def unpack_kwargs(funcname, kwargs, arg_pattern): except KeyError as e: error = True if error: - raise TypeError( - "{}() missing 1 required keyword-only argument: '{}'".format( - funcname, tp - ) - ) + raise TypeError('{}() missing 1 required keyword-only argument: \'{}\''.format(funcname, tp)) if kwargs: - raise TypeError( - "{}() got an unexpected keyword argument '{}'".format( - funcname, next(iter(kwargs.items()))[0] - ) - ) + raise TypeError('{}() got an unexpected keyword argument \'{}\''.format(funcname, next(iter(kwargs.items()))[0])) return rv diff --git a/cyaron/vector.py b/cyaron/vector.py index 3016883..2620c59 100644 --- a/cyaron/vector.py +++ b/cyaron/vector.py @@ -14,9 +14,7 @@ class VectorRandomMode(Enum): class Vector: @staticmethod - def random( - num: int = 5, position_range: list = None, mode: VectorRandomMode = 0, **kwargs - ): + def random(num: int = 5, position_range: list = None, mode: VectorRandomMode = 0, **kwargs): """ brief : generating n random vectors in limited space param : @@ -49,24 +47,16 @@ def random( else: offset.append(0) length.append(position_range[i]) - vector_space *= length[i] + 1 + vector_space *= (length[i] + 1) if mode == VectorRandomMode.unique and num > vector_space: - raise Exception( - "1st param is so large that CYaRon can not generate unique vectors" - ) + raise Exception("1st param is so large that CYaRon can not generate unique vectors") result = [] if mode == VectorRandomMode.repeatable: - result = [ - [random.randint(x, y) for x, y in zip(offset, length)] - for _ in range(num) - ] + result = [[random.randint(x, y) for x, y in zip(offset, length)] for _ in range(num)] elif mode == VectorRandomMode.float: - result = [ - [random.uniform(x, y) for x, y in zip(offset, length)] - for _ in range(num) - ] + result = [[random.uniform(x, y) for x, y in zip(offset, length)] for _ in range(num)] elif mode == VectorRandomMode.unique and vector_space > 5 * num: # O(NlogN) num_set = set() @@ -97,5 +87,5 @@ def get_vector(dimension: int, position_range: list, hashcode: int): tmp = [] for i in range(0, dimension): tmp.append(hashcode % (position_range[i] + 1)) - hashcode //= position_range[i] + 1 + hashcode //= (position_range[i] + 1) return tmp diff --git a/cyaron/visual.py b/cyaron/visual.py index 3e0f63d..7b5f122 100644 --- a/cyaron/visual.py +++ b/cyaron/visual.py @@ -1,26 +1,26 @@ -from .graph import * +from .graph import * from .merger import Merger import pygraphviz as pgv - def visualize(graph, output_path="a.png"): """visualize(graph, **kwargs) -> None - Graph/Merger graph -> the graph/Merger that will be visualized - string output_path-> the output path of the image + Graph/Merger graph -> the graph/Merger that will be visualized + string output_path-> the output path of the image """ - if isinstance(graph, Merger): - graph = Merger.G + if isinstance(graph, Merger): graph = Merger.G G = pgv.AGraph(directed=graph.directed) G.add_nodes_from([i for i in xrange(1, len(graph.edges))]) for edge in graph.iterate_edges(): G.add_edge(edge.start, edge.end, label=edge.weight) + + G.node_attr['shape'] = 'egg' + G.node_attr['width'] = '0.25' + G.node_attr['height'] = '0.25' + G.edge_attr['arrowhead'] = 'open' - G.node_attr["shape"] = "egg" - G.node_attr["width"] = "0.25" - G.node_attr["height"] = "0.25" - G.edge_attr["arrowhead"] = "open" - - G.layout(prog="dot") + G.layout(prog='dot') G.draw(output_path) + + From df0f379d33573015e1ff829e563a308940988cdf Mon Sep 17 00:00:00 2001 From: FredB-mine Date: Thu, 7 Mar 2024 20:46:48 +0800 Subject: [PATCH 03/18] Ignore macOS file system storage --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 94e2e44..7489c27 100644 --- a/.gitignore +++ b/.gitignore @@ -131,4 +131,6 @@ docs/_build/ target/ # Pycharm -venv \ No newline at end of file +venv + +*.DS_Store \ No newline at end of file From 1a9744c0673a0219016d6b0a33aa52e7ed8fc50e Mon Sep 17 00:00:00 2001 From: FredB-mine Date: Thu, 7 Mar 2024 20:54:40 +0800 Subject: [PATCH 04/18] Discard changes of compare.py --- cyaron/compare.py | 132 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 103 insertions(+), 29 deletions(-) diff --git a/cyaron/compare.py b/cyaron/compare.py index ad613a0..f32c0d4 100644 --- a/cyaron/compare.py +++ b/cyaron/compare.py @@ -17,7 +17,7 @@ def __init__(self, name, mismatch): self.mismatch = mismatch def __str__(self): - return 'In program: \'{}\'. {}'.format(self.name,self.mismatch) + return "In program: '{}'. {}".format(self.name, self.mismatch) class Compare: @@ -37,7 +37,7 @@ def __process_file(file): file.output_file.seek(0) return file.output_filename, file.output_file.read() else: - with open(file, "r", newline='\n') as f: + with open(file, "r", newline="\n") as f: return file, f.read() @staticmethod @@ -50,26 +50,43 @@ def __normal_max_workers(workers): @classmethod def output(cls, *files, **kwargs): - kwargs = unpack_kwargs('output', kwargs, ('std', ('grader', DEFAULT_GRADER), ('max_workers', -1), - ('job_pool', None), ('stop_on_incorrect', None))) - std = kwargs['std'] - grader = kwargs['grader'] - max_workers = kwargs['max_workers'] - job_pool = kwargs['job_pool'] - if kwargs['stop_on_incorrect'] is not None: + kwargs = unpack_kwargs( + "output", + kwargs, + ( + "std", + ("grader", DEFAULT_GRADER), + ("max_workers", -1), + ("job_pool", None), + ("stop_on_incorrect", None), + ), + ) + std = kwargs["std"] + grader = kwargs["grader"] + max_workers = kwargs["max_workers"] + job_pool = kwargs["job_pool"] + if kwargs["stop_on_incorrect"] is not None: log.warn("parameter stop_on_incorrect is deprecated and has no effect.") if (max_workers is None or max_workers >= 0) and job_pool is None: max_workers = cls.__normal_max_workers(max_workers) try: from concurrent.futures import ThreadPoolExecutor + with ThreadPoolExecutor(max_workers=max_workers) as job_pool: - return cls.output(*files, std=std, grader=grader, max_workers=max_workers, job_pool=job_pool) + return cls.output( + *files, + std=std, + grader=grader, + max_workers=max_workers, + job_pool=job_pool + ) except ImportError: pass def get_std(): return cls.__process_file(std)[1] + if job_pool is not None: std = job_pool.submit(get_std).result() else: @@ -86,61 +103,118 @@ def do(file): @classmethod def program(cls, *programs, **kwargs): - kwargs = unpack_kwargs('program', kwargs, ('input', ('std', None), ('std_program', None), - ('grader', DEFAULT_GRADER), ('max_workers', -1), - ('job_pool', None), ('stop_on_incorrect', None))) - input = kwargs['input'] - std = kwargs['std'] - std_program = kwargs['std_program'] - grader = kwargs['grader'] - max_workers = kwargs['max_workers'] - job_pool = kwargs['job_pool'] - if kwargs['stop_on_incorrect'] is not None: + kwargs = unpack_kwargs( + "program", + kwargs, + ( + "input", + ("std", None), + ("std_program", None), + ("grader", DEFAULT_GRADER), + ("max_workers", -1), + ("job_pool", None), + ("stop_on_incorrect", None), + ), + ) + input = kwargs["input"] + std = kwargs["std"] + std_program = kwargs["std_program"] + grader = kwargs["grader"] + max_workers = kwargs["max_workers"] + job_pool = kwargs["job_pool"] + if kwargs["stop_on_incorrect"] is not None: log.warn("parameter stop_on_incorrect is deprecated and has no effect.") if (max_workers is None or max_workers >= 0) and job_pool is None: max_workers = cls.__normal_max_workers(max_workers) try: from concurrent.futures import ThreadPoolExecutor + with ThreadPoolExecutor(max_workers=max_workers) as job_pool: - return cls.program(*programs, input=input, std=std, std_program=std_program, grader=grader, max_workers=max_workers, job_pool=job_pool) + return cls.program( + *programs, + input=input, + std=std, + std_program=std_program, + grader=grader, + max_workers=max_workers, + job_pool=job_pool + ) except ImportError: pass if not isinstance(input, IO): - raise TypeError("expect {}, got {}".format(type(IO).__name__, type(input).__name__)) + raise TypeError( + "expect {}, got {}".format(type(IO).__name__, type(input).__name__) + ) input.flush_buffer() input.input_file.seek(0) if std_program is not None: + def get_std(): - with open(os.dup(input.input_file.fileno()), 'r', newline='\n') as input_file: - content = make_unicode(subprocess.check_output(std_program, shell=(not list_like(std_program)), stdin=input.input_file, universal_newlines=True)) + with open( + os.dup(input.input_file.fileno()), "r", newline="\n" + ) as input_file: + content = make_unicode( + subprocess.check_output( + std_program, + shell=(not list_like(std_program)), + stdin=input.input_file, + universal_newlines=True, + ) + ) input_file.seek(0) return content + if job_pool is not None: std = job_pool.submit(get_std).result() else: std = get_std() elif std is not None: + def get_std(): return cls.__process_file(std)[1] + if job_pool is not None: std = job_pool.submit(get_std).result() else: std = get_std() else: - raise TypeError('program() missing 1 required non-None keyword-only argument: \'std\' or \'std_program\'') + raise TypeError( + "program() missing 1 required non-None keyword-only argument: 'std' or 'std_program'" + ) def do(program_name): timeout = None - if list_like(program_name) and len(program_name) == 2 and int_like(program_name[-1]): + if ( + list_like(program_name) + and len(program_name) == 2 + and int_like(program_name[-1]) + ): program_name, timeout = program_name - with open(os.dup(input.input_file.fileno()), 'r', newline='\n') as input_file: + with open( + os.dup(input.input_file.fileno()), "r", newline="\n" + ) as input_file: if timeout is None: - content = make_unicode(subprocess.check_output(program_name, shell=(not list_like(program_name)), stdin=input_file, universal_newlines=True)) + content = make_unicode( + subprocess.check_output( + program_name, + shell=(not list_like(program_name)), + stdin=input_file, + universal_newlines=True, + ) + ) else: - content = make_unicode(subprocess.check_output(program_name, shell=(not list_like(program_name)), stdin=input_file, universal_newlines=True, timeout=timeout)) + content = make_unicode( + subprocess.check_output( + program_name, + shell=(not list_like(program_name)), + stdin=input_file, + universal_newlines=True, + timeout=timeout, + ) + ) input_file.seek(0) cls.__compare_two(program_name, content, std, grader) From cb603fa51cbf242987566e081b5cb6c5331b15df Mon Sep 17 00:00:00 2001 From: weilycoder Date: Tue, 1 Oct 2024 10:41:29 +0800 Subject: [PATCH 05/18] Allow users to convert the graph to adjacency matrix. --- cyaron/graph.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/cyaron/graph.py b/cyaron/graph.py index ee6b9ff..0a8ba82 100644 --- a/cyaron/graph.py +++ b/cyaron/graph.py @@ -40,6 +40,24 @@ def __init__(self, point_count, directed=False): self.directed = directed self.edges = [[] for i in range(point_count + 1)] + def to_matrix(self, **kwargs): + """to_matrix(self, **kwargs) -> list[list[Any]] + Convert the graph to adjacency matrix. + **kwargs(Keyword args): + int default = -1 -> the default value when the edge does not exist. + Any output(Edge) + = lambda edge: edge.weight + -> the mapping from edges to values in matrix. + Note that the index start from 0 and the values in the Column 0 or the Row 0 are always the default. + """ + default = kwargs.get("default", -1) + output = kwargs.get("output", lambda edge: edge.weight) + n = len(self.edges) + matrix = [[default for _ in range(n)] for _ in range(n)] + for edge in self.iterate_edges(): + matrix[edge.start][edge.end] = output(edge) + return matrix + def to_str(self, **kwargs): """to_str(self, **kwargs) -> str Convert the graph to string with format. Splits with "\n" From a1f1b5ce9d4fdaf7998ae7ddea7feeeab3b80ea4 Mon Sep 17 00:00:00 2001 From: weilycoder Date: Tue, 1 Oct 2024 14:34:38 +0800 Subject: [PATCH 06/18] allowing repeated edges --- cyaron/graph.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cyaron/graph.py b/cyaron/graph.py index 0a8ba82..49db3f2 100644 --- a/cyaron/graph.py +++ b/cyaron/graph.py @@ -45,17 +45,17 @@ def to_matrix(self, **kwargs): Convert the graph to adjacency matrix. **kwargs(Keyword args): int default = -1 -> the default value when the edge does not exist. - Any output(Edge) - = lambda edge: edge.weight - -> the mapping from edges to values in matrix. + Any merge(Any, Edge) + = lambda val, edge: edge.weight + -> the mapping from the old values in matrix and the edges to the new values in matrix. Note that the index start from 0 and the values in the Column 0 or the Row 0 are always the default. """ default = kwargs.get("default", -1) - output = kwargs.get("output", lambda edge: edge.weight) + merge = kwargs.get("merge", lambda val, edge: edge.weight) n = len(self.edges) matrix = [[default for _ in range(n)] for _ in range(n)] for edge in self.iterate_edges(): - matrix[edge.start][edge.end] = output(edge) + matrix[edge.start][edge.end] = merge(matrix[edge.start][edge.end], edge) return matrix def to_str(self, **kwargs): From 8027e5a3c443387323f7b18d69f9f3c3cde907e1 Mon Sep 17 00:00:00 2001 From: "Mr. Python" <2789762371@qq.com> Date: Wed, 2 Oct 2024 22:23:48 +0800 Subject: [PATCH 07/18] Format and write commit for io.py --- .pylintrc | 2 +- cyaron/io.py | 195 +++++++++++++++++++++++++++++++----------------- cyaron/math.py | 6 +- cyaron/utils.py | 5 +- 4 files changed, 132 insertions(+), 76 deletions(-) diff --git a/.pylintrc b/.pylintrc index 3f2df06..1b65d41 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,3 +1,3 @@ [MASTER] py-version=3.5 -disable=R0902,R0913 \ No newline at end of file +disable=R0902,R0913,R0917 \ No newline at end of file diff --git a/cyaron/io.py b/cyaron/io.py index 1880143..a21cada 100644 --- a/cyaron/io.py +++ b/cyaron/io.py @@ -1,43 +1,87 @@ +""" +A module that provides a class IO to process the input and output files. +Classes: + IO: IO tool class. It will process the input and output files. +""" from __future__ import absolute_import -from .utils import * -from . import log -from io import open, IOBase -import subprocess -import tempfile import os import re +import subprocess +import tempfile +from typing import Union, overload +from io import IOBase +from . import log +from .utils import list_like, make_unicode -class IO(object): +class IO: """Class IO: IO tool class. It will process the input and output files.""" - def __init__(self, input_file=None, output_file=None, data_id=None, file_prefix=None, input_suffix='.in', output_suffix='.out', disable_output=False): - """__init__(self, input_file=None, output_file=None, data_id=None, file_prefix=None, input_suffix='.in', output_suffix='.out', disable_output=False) -> None - input_file, output_file overload: - None -> make a temp file (if file_prefix is None) - file object -> treat the file-like object as in/output file - int -> open file by file descriptor - str -> a filename or filename template like 'awd{}.in'. ``{}`` will be replaced by ``data_id`` - int data_id -> the id of the data. if it's None, the file names will not contain the id. - legacy argumants: - str file_prefix -> the prefix for the input and output files - str input_suffix = ".in" -> the suffix of the input file - str output_suffix = ".out" -> the suffix of the output file - disable_output -> bool, set to True to disable output - Examples: - IO("a","b") -> create input file "a" and output file "b" - IO("a.in","b.out") -> create input file "a.in" and output file "b.out" - IO(file_prefix="data") -> create input file "data.in" and output file "data.out" - IO(file_prefix="data",data_id=1) -> create input file "data1.in" and output file "data1.out" - IO(file_prefix="data",input_suffix=".input") -> create input file "data.input" and output file "data.out" - IO(file_prefix="data",output_suffix=".output") -> create input file "data.in" and output file "data.output" - IO(file_prefix="data",data_id=2,input_suffix=".input") -> create input file "data2.input" and output file "data2.out" - IO("data{}.in","data{}.out",data_id=2) -> create input file "data2.in" and output file "data2.out" - IO(open('data.in', 'w+'), open('data.out', 'w+')) -> input file "data.in" and output file "data.out" + + @overload + def __init__(self, + input_file: Union[IOBase, str, int, None] = None, + output_file: Union[IOBase, str, int, None] = None, + data_id: Union[str, None] = None, + disable_output: bool = False): + ... + + @overload + def __init__(self, + data_id: Union[str, None] = None, + file_prefix: Union[str, None] = None, + input_suffix: Union[str, None] = '.in', + output_suffix: Union[str, None] = '.out', + disable_output: bool = False): + ... + + def __init__(self, + input_file: Union[IOBase, str, int, None] = None, + output_file: Union[IOBase, str, int, None] = None, + data_id: Union[str, None] = None, + file_prefix: Union[str, None] = None, + input_suffix: Union[str, None] = '.in', + output_suffix: Union[str, None] = '.out', + disable_output: bool = False): + """ + Args: + input_file (optional): input file object or filename or file descriptor. + If it's None, make a temp file. Defaults to None. + output_file (optional): input file object or filename or file descriptor. + If it's None, make a temp file. Defaults to None. + data_id (optional): the id of the data. It will be add after + `input_file` and `output_file` when they are str. + If it's None, the file names will not contain the id. Defaults to None. + file_prefix (optional): the prefix for the input and output files. Defaults to None. + input_suffix (optional): the suffix of the input file. Defaults to '.in'. + output_suffix (optional): the suffix of the output file. Defaults to '.out'. + disable_output (optional): set to True to disable output file. Defaults to False. + Examples: + >>> IO("a","b") + # create input file "a" and output file "b" + >>> IO("a.in","b.out") + # create input file "a.in" and output file "b.out" + >>> IO(file_prefix="data") + # create input file "data.in" and output file "data.out" + >>> IO(file_prefix="data",data_id=1) + # create input file "data1.in" and output file "data1.out" + >>> IO(file_prefix="data",input_suffix=".input") + # create input file "data.input" and output file "data.out" + >>> IO(file_prefix="data",output_suffix=".output") + # create input file "data.in" and output file "data.output" + >>> IO(file_prefix="data",data_id=2,input_suffix=".input") + # create input file "data2.input" and output file "data2.out" + >>> IO("data{}.in","data{}.out",data_id=2) + # create input file "data2.in" and output file "data2.out" + >>> IO(open('data.in', 'w+'), open('data.out', 'w+')) + # input file "data.in" and output file "data.out" """ if file_prefix is not None: # legacy mode - input_file = '{}{{}}{}'.format(self.__escape_format(file_prefix), self.__escape_format(input_suffix)) - output_file = '{}{{}}{}'.format(self.__escape_format(file_prefix), self.__escape_format(output_suffix)) + input_file = '{}{{}}{}'.format(self.__escape_format(file_prefix), + self.__escape_format(input_suffix)) + output_file = '{}{{}}{}'.format( + self.__escape_format(file_prefix), + self.__escape_format(output_suffix)) self.input_filename, self.output_filename = None, None self.__input_temp, self.__output_temp = False, False self.__init_file(input_file, data_id, 'i') @@ -48,12 +92,9 @@ def __init__(self, input_file=None, output_file=None, data_id=None, file_prefix= self.__closed = False self.is_first_char = {} - def __init_file(self, f, data_id, file_type): - try: - is_file = isinstance(f, file) - except NameError: - is_file = False - if isinstance(f, IOBase) or is_file: + def __init_file(self, f: Union[IOBase, str, int, None], + data_id: Union[int, None], file_type: str): + if isinstance(f, IOBase): # consider ``f`` as a file object if file_type == 'i': self.input_file = f @@ -61,7 +102,8 @@ def __init_file(self, f, data_id, file_type): self.output_file = f elif isinstance(f, int): # consider ``f`` as a file descor - self.__init_file(open(f, 'w+', newline='\n'), data_id, file_type) + self.__init_file(open(f, 'w+', encoding="utf-8", newline='\n'), + data_id, file_type) elif f is None: # consider wanna temp file fd, self.input_filename = tempfile.mkstemp() @@ -75,12 +117,13 @@ def __init_file(self, f, data_id, file_type): filename = f.format(data_id or '') if file_type == 'i': self.input_filename = filename - log.debug("Processing %s" % self.input_filename) else: self.output_filename = filename - self.__init_file(open(filename, 'w+', newline='\n'), data_id, file_type) + self.__init_file( + open(filename, 'w+', newline='\n', encoding='utf-8'), data_id, + file_type) - def __escape_format(self, st): + def __escape_format(self, st: str): """replace "{}" to "{{}}" """ return re.sub(r'\{', '{{', re.sub(r'\}', '}}', st)) @@ -99,7 +142,8 @@ def close(self): deleted = False try: # on posix, one can remove a file while it's opend by a process - # the file then will be not visable to others, but process still have the file descriptor + # the file then will be not visable to others, + # but process still have the file descriptor # it is recommand to remove temp file before close it on posix to avoid race # on nt, it will just fail and raise OSError so that after closing remove it again self.__del_files() @@ -123,12 +167,10 @@ def __enter__(self): def __exit__(self, exc_type, exc_val, exc_tb): self.close() - def __write(self, file, *args, **kwargs): - """__write(self, file, *args, **kwargs) -> None - Write every element in *args into file. If the element isn't "\n", insert a space. It will convert every element into str - file file -> the file object to write - **kwargs: - str separator = " " -> a string used to separate every element + def __write(self, file: IOBase, *args, **kwargs): + """ + Write every element in *args into file. If the element isn't "\n", insert `separator`. + It will convert every element into str. """ separator = kwargs.get("separator", " ") for arg in args: @@ -143,53 +185,70 @@ def __write(self, file, *args, **kwargs): self.is_first_char[file] = True def input_write(self, *args, **kwargs): - """input_write(self, *args, **kwargs) -> None - Write every element in *args into the input file. Splits with spaces. It will convert every element into string - **kwargs: - str separator = " " -> a string used to separate every element + """ + Write every element in *args into the input file. Splits with `separator`. + It will convert every element into str. + Args: + *args: the elements to write + separator: a string used to separate every element. Defaults to " ". """ self.__write(self.input_file, *args, **kwargs) def input_writeln(self, *args, **kwargs): - """input_writeln(self, *args, **kwargs) -> None - Write every element in *args into the input file and turn to a new line. Splits with spaces. It will convert every element into string - **kwargs: - str separator = " " -> a string used to separate every element + """ + Write every element in *args into the input file and turn to a new line + Splits with `separator`. + It will convert every element into str. + Args: + *args: the elements to write + separator: a string used to separate every element. Defaults to " ". """ args = list(args) args.append("\n") self.input_write(*args, **kwargs) def output_gen(self, shell_cmd): - """output_gen(self, shell_cmd) -> None - Run the command shell_cmd(usually the std programme) and send it the input file as stdin. Write its output to the output file. - str shell_cmd -> the command to run, usually the std programme + """ + Run the command `shell_cmd` (usually the std program) and send it the input file as stdin. + Write its output to the output file. + Args: + shell_cmd: the command to run, usually the std program. """ self.flush_buffer() origin_pos = self.input_file.tell() self.input_file.seek(0) - subprocess.check_call(shell_cmd, shell=True, stdin=self.input_file, stdout=self.output_file, universal_newlines=True) + subprocess.check_call(shell_cmd, + shell=True, + stdin=self.input_file, + stdout=self.output_file, + universal_newlines=True) self.input_file.seek(origin_pos) log.debug(self.output_filename, " done") def output_write(self, *args, **kwargs): - """output_write(self, *args, **kwargs) -> None - Write every element in *args into the output file. Splits with spaces. It will convert every element into string - **kwargs: - str separator = " " -> a string used to separate every element + """ + Write every element in *args into the output file. Splits with `separator`. + It will convert every element into str. + Args: + *args: the elements to write + separator: a string used to separate every element. Defaults to " ". """ self.__write(self.output_file, *args, **kwargs) def output_writeln(self, *args, **kwargs): - """output_writeln(self, *args, **kwargs) -> None - Write every element in *args into the output file and turn to a new line. Splits with spaces. It will convert every element into string - **kwargs: - str separator = " " -> a string used to separate every element + """ + Write every element in *args into the output file and turn to a new line. + Splits with `separator`. + It will convert every element into str. + Args: + *args: the elements to write + separator: a string used to separate every element. Defaults to " ". """ args = list(args) args.append("\n") self.output_write(*args, **kwargs) def flush_buffer(self): + """Flush the input file""" self.input_file.flush() diff --git a/cyaron/math.py b/cyaron/math.py index 2b7c75e..dce3102 100644 --- a/cyaron/math.py +++ b/cyaron/math.py @@ -1,4 +1,4 @@ -''' +""" This is a module that includes some useful math functions. Functions: @@ -27,7 +27,7 @@ n2words(num,join=True): Number to words forked from https://blog.dreamshire.com/common-functions-routines-project-euler/ -''' +""" from __future__ import absolute_import from math import sqrt, factorial @@ -170,7 +170,7 @@ def fibonacci(n: int): def _fib(n: int) -> Tuple[int, int]: - '''Returns a tuple of fibonacci (F(n), F(n+1)).''' + """Returns a tuple of fibonacci (F(n), F(n+1)).""" if n == 0: return (0, 1) a, b = _fib(n // 2) diff --git a/cyaron/utils.py b/cyaron/utils.py index 4ef7358..cbef2ee 100644 --- a/cyaron/utils.py +++ b/cyaron/utils.py @@ -34,10 +34,7 @@ def strtolines(str): def make_unicode(data): - try: - return unicode(data) - except NameError: - return str(data) + return str(data) def unpack_kwargs(funcname, kwargs, arg_pattern): rv = {} From c696bdc5d6300522e6f0e0a1e1854c82a05c47ca Mon Sep 17 00:00:00 2001 From: "Mr. Python" <2789762371@qq.com> Date: Thu, 3 Oct 2024 02:35:57 +0800 Subject: [PATCH 08/18] Add a matrix class to enable str() --- .gitignore | 5 +- cyaron/graph.py | 138 +++++++++++++++++++++++++++++++----------------- 2 files changed, 93 insertions(+), 50 deletions(-) diff --git a/.gitignore b/.gitignore index 94e2e44..12e97f7 100644 --- a/.gitignore +++ b/.gitignore @@ -131,4 +131,7 @@ docs/_build/ target/ # Pycharm -venv \ No newline at end of file +venv/ + +# VS Code +.vscode/ diff --git a/cyaron/graph.py b/cyaron/graph.py index 49db3f2..0b77d64 100644 --- a/cyaron/graph.py +++ b/cyaron/graph.py @@ -1,9 +1,11 @@ from .utils import * import random +from typing import TypeVar, Callable class Edge: """Class Edge: A class of the edge in the graph""" + def __init__(self, u, v, w): """__init__(self, u, v, w) -> None Initialize a edge. @@ -26,11 +28,13 @@ def unweighted_edge(edge): """unweighted_edge(edge) -> str Return a string to output the edge without weight. The string contains the start vertex, end vertex(u,v) and splits with space. """ - return '%d %d'%(edge.start,edge.end) + return '%d %d' % (edge.start, edge.end) + class Graph: """Class Graph: A class of the graph """ + def __init__(self, point_count, directed=False): """__init__(self, point_count) -> None Initialize a graph. @@ -50,13 +54,7 @@ def to_matrix(self, **kwargs): -> the mapping from the old values in matrix and the edges to the new values in matrix. Note that the index start from 0 and the values in the Column 0 or the Row 0 are always the default. """ - default = kwargs.get("default", -1) - merge = kwargs.get("merge", lambda val, edge: edge.weight) - n = len(self.edges) - matrix = [[default for _ in range(n)] for _ in range(n)] - for edge in self.iterate_edges(): - matrix[edge.start][edge.end] = merge(matrix[edge.start][edge.end], edge) - return matrix + return GraphMatrix(self) def to_str(self, **kwargs): """to_str(self, **kwargs) -> str @@ -75,7 +73,8 @@ def to_str(self, **kwargs): edge_buf = [] for edge in self.iterate_edges(): edge_buf.append( - Edge(new_node_id[edge.start], new_node_id[edge.end], edge.weight)) + Edge(new_node_id[edge.start], new_node_id[edge.end], + edge.weight)) random.shuffle(edge_buf) for edge in edge_buf: if not self.directed and random.randint(0, 1) == 0: @@ -173,9 +172,10 @@ def tree(point_count, chain=0, flower=0, **kwargs): if not list_like(weight_limit): weight_limit = (1, weight_limit) weight_gen = kwargs.get( - "weight_gen", lambda: random.randint( - weight_limit[0], weight_limit[1])) - father_gen = kwargs.get("father_gen", lambda cur: random.randrange(1, cur)) + "weight_gen", + lambda: random.randint(weight_limit[0], weight_limit[1])) + father_gen = kwargs.get("father_gen", + lambda cur: random.randrange(1, cur)) if not 0 <= chain <= 1 or not 0 <= flower <= 1: raise Exception("chain and flower must be between 0 and 1") @@ -222,33 +222,35 @@ def binary_tree(point_count, left=0, right=0, **kwargs): if not list_like(weight_limit): weight_limit = (1, weight_limit) weight_gen = kwargs.get( - "weight_gen", lambda: random.randint( - weight_limit[0], weight_limit[1])) + "weight_gen", + lambda: random.randint(weight_limit[0], weight_limit[1])) if not 0 <= left <= 1 or not 0 <= right <= 1: raise Exception("left and right must be between 0 and 1") if left + right > 1: raise Exception("left plus right must be smaller than 1") - - can_left=[1] - can_right=[1] + + can_left = [1] + can_right = [1] graph = Graph(point_count, directed) for i in range(2, point_count + 1): edge_pos = random.random() node = 0 # Left - if edge_pos < left or left + right < edge_pos <= (1.0 - left - right) / 2: - point_index = random.randint(0,len(can_left)-1) + if edge_pos < left or left + right < edge_pos <= (1.0 - left - + right) / 2: + point_index = random.randint(0, len(can_left) - 1) node = can_left[point_index] - del_last_node = can_left.pop() # Save a copy of the last element + del_last_node = can_left.pop( + ) # Save a copy of the last element if point_index < len(can_left): # If the chosen element isn't the last one, # Copy the last one to the position of the chosen one can_left[point_index] = del_last_node # Right else: - # elif left <= edge_pos <= left + right or (1.0 - left - right) / 2 < edge_pos < 1: - point_index = random.randint(0,len(can_right)-1) + # elif left <= edge_pos <= left + right or (1.0 - left - right) / 2 < edge_pos < 1: + point_index = random.randint(0, len(can_right) - 1) node = can_right[point_index] del_last_node = can_right.pop() if point_index < len(can_right): @@ -282,8 +284,8 @@ def graph(point_count, edge_count, **kwargs): if not list_like(weight_limit): weight_limit = (1, weight_limit) weight_gen = kwargs.get( - "weight_gen", lambda: random.randint( - weight_limit[0], weight_limit[1])) + "weight_gen", + lambda: random.randint(weight_limit[0], weight_limit[1])) graph = Graph(point_count, directed) used_edges = set() i = 0 @@ -291,7 +293,8 @@ def graph(point_count, edge_count, **kwargs): u = random.randint(1, point_count) v = random.randint(1, point_count) - if (not self_loop and u == v) or (not repeated_edges and (u, v) in used_edges): + if (not self_loop and u == v) or (not repeated_edges and + (u, v) in used_edges): # Then we generate a new pair of nodes continue @@ -322,30 +325,33 @@ def DAG(point_count, edge_count, **kwargs): -> the generator of the weights. It should return the weight. The default way is to use the random.randint() """ if edge_count < point_count - 1: - raise Exception("the number of edges of connected graph must more than the number of nodes - 1") + raise Exception( + "the number of edges of connected graph must more than the number of nodes - 1" + ) - self_loop = kwargs.get("self_loop", False) # DAG default has no loop + self_loop = kwargs.get("self_loop", False) # DAG default has no loop repeated_edges = kwargs.get("repeated_edges", True) loop = kwargs.get("loop", False) weight_limit = kwargs.get("weight_limit", (1, 1)) if not list_like(weight_limit): weight_limit = (1, weight_limit) weight_gen = kwargs.get( - "weight_gen", lambda: random.randint( - weight_limit[0], weight_limit[1])) - + "weight_gen", + lambda: random.randint(weight_limit[0], weight_limit[1])) + used_edges = set() - edge_buf = list(Graph.tree(point_count, weight_gen=weight_gen).iterate_edges()) + edge_buf = list( + Graph.tree(point_count, weight_gen=weight_gen).iterate_edges()) graph = Graph(point_count, directed=True) for edge in edge_buf: if loop and random.randint(1, 2) == 1: edge.start, edge.end = edge.end, edge.start graph.add_edge(edge.start, edge.end, weight=edge.weight) - + if not repeated_edges: used_edges.add((edge.start, edge.end)) - + i = point_count - 1 while i < edge_count: u = random.randint(1, point_count) @@ -354,7 +360,8 @@ def DAG(point_count, edge_count, **kwargs): if not loop and u > v: u, v = v, u - if (not self_loop and u == v) or (not repeated_edges and (u, v) in used_edges): + if (not self_loop and u == v) or (not repeated_edges and + (u, v) in used_edges): # Then we generate a new pair of nodes continue @@ -382,8 +389,10 @@ def UDAG(point_count, edge_count, **kwargs): = lambda: random.randint(weight_limit[0], weight_limit[1]) -> the generator of the weights. It should return the weight. The default way is to use the random.randint() """ - if edge_count < point_count - 1: - raise Exception("the number of edges of connected graph must more than the number of nodes - 1") + if edge_count < point_count - 1: + raise Exception( + "the number of edges of connected graph must more than the number of nodes - 1" + ) self_loop = kwargs.get("self_loop", True) repeated_edges = kwargs.get("repeated_edges", True) @@ -391,9 +400,9 @@ def UDAG(point_count, edge_count, **kwargs): if not list_like(weight_limit): weight_limit = (1, weight_limit) weight_gen = kwargs.get( - "weight_gen", lambda: random.randint( - weight_limit[0], weight_limit[1])) - + "weight_gen", + lambda: random.randint(weight_limit[0], weight_limit[1])) + used_edges = set() graph = Graph.tree(point_count, weight_gen=weight_gen, directed=False) @@ -401,13 +410,14 @@ def UDAG(point_count, edge_count, **kwargs): if not repeated_edges: used_edges.add((edge.start, edge.end)) used_edges.add((edge.end, edge.start)) - + i = point_count - 1 while i < edge_count: u = random.randint(1, point_count) v = random.randint(1, point_count) - if (not self_loop and u == v) or (not repeated_edges and (u, v) in used_edges): + if (not self_loop and u == v) or (not repeated_edges and + (u, v) in used_edges): # Then we generate a new pair of nodes continue @@ -441,8 +451,8 @@ def hack_spfa(point_count, **kwargs): if not list_like(weight_limit): weight_limit = (1, weight_limit) weight_gen = kwargs.get( - "weight_gen", lambda: random.randint( - weight_limit[0], weight_limit[1])) + "weight_gen", + lambda: random.randint(weight_limit[0], weight_limit[1])) point_to_skip = point_count + 3 graph = Graph(point_count, directed) @@ -452,15 +462,18 @@ def hack_spfa(point_count, **kwargs): for i in range(1, half): (x, y) = (i, i + 1) - graph.add_edge(x + (x >= point_to_skip), y + - (y >= point_to_skip), weight=weight_gen()) + graph.add_edge(x + (x >= point_to_skip), + y + (y >= point_to_skip), + weight=weight_gen()) (x, y) = (i + half, i + half + 1) - graph.add_edge(x + (x >= point_to_skip), y + - (y >= point_to_skip), weight=weight_gen()) + graph.add_edge(x + (x >= point_to_skip), + y + (y >= point_to_skip), + weight=weight_gen()) for i in range(1, half + 1): (x, y) = (i, i + half) - graph.add_edge(x + (x >= point_to_skip), y + - (y >= point_to_skip), weight=weight_gen()) + graph.add_edge(x + (x >= point_to_skip), + y + (y >= point_to_skip), + weight=weight_gen()) for i in range(extraedg): u = random.randint(1, point_count) @@ -468,3 +481,30 @@ def hack_spfa(point_count, **kwargs): graph.add_edge(u, v, weight=weight_gen()) return graph + + +T = TypeVar('T') + + +class GraphMatrix: + """Class GraphMatrix: A class of the graph represented by adjacency matrix""" + + def __init__(self, + graph: Graph, + default: T = -1, + merge: Callable[[T, Edge], + T] = lambda val, edge: edge.weight): + """ + Args: + graph: the graph to convert, + default: the default value when the edge does not exist, + merge: the mapping from the old values in matrix and the edges to the new values in matrix. + """ + n = len(graph.edges) + self.matrix = [[default for _ in range(n)] for _ in range(n)] + for edge in graph.iterate_edges(): + self.matrix[edge.start][edge.end] = merge( + self.matrix[edge.start][edge.end], edge) + + def __str__(self): + return '\n'.join([' '.join(map(str, row)) for row in self.matrix]) From 313659903f2b8983f861d7bcbc538b2d3efe0622 Mon Sep 17 00:00:00 2001 From: weilycoder Date: Thu, 3 Oct 2024 18:02:48 +0800 Subject: [PATCH 09/18] move TypeVar T --- cyaron/graph.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cyaron/graph.py b/cyaron/graph.py index 01c5132..6c8dbfc 100644 --- a/cyaron/graph.py +++ b/cyaron/graph.py @@ -528,12 +528,11 @@ def _calc_max_edge(point_count, directed, self_loop): return max_edge -T = TypeVar('T') - - class GraphMatrix: """Class GraphMatrix: A class of the graph represented by adjacency matrix""" + T = TypeVar('T') + def __init__(self, graph: Graph, default: T = -1, From c35e622aef5c51ceff5f78c126c34e4ec39ec55f Mon Sep 17 00:00:00 2001 From: weilycoder Date: Thu, 3 Oct 2024 19:03:38 +0800 Subject: [PATCH 10/18] fix bugs --- cyaron/graph.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/cyaron/graph.py b/cyaron/graph.py index 6c8dbfc..aa921d0 100644 --- a/cyaron/graph.py +++ b/cyaron/graph.py @@ -1,6 +1,6 @@ from .utils import * import random -from typing import TypeVar, Callable +from typing import TypeVar, Callable, List class Edge: @@ -54,16 +54,15 @@ def edge_count(self): return cnt def to_matrix(self, **kwargs): - """to_matrix(self, **kwargs) -> list[list[Any]] + """to_matrix(self, **kwargs) -> GraphMatrix Convert the graph to adjacency matrix. **kwargs(Keyword args): int default = -1 -> the default value when the edge does not exist. Any merge(Any, Edge) = lambda val, edge: edge.weight -> the mapping from the old values in matrix and the edges to the new values in matrix. - Note that the index start from 0 and the values in the Column 0 or the Row 0 are always the default. """ - return GraphMatrix(self) + return GraphMatrix(self, **kwargs) def to_str(self, **kwargs): """to_str(self, **kwargs) -> str @@ -551,4 +550,10 @@ def __init__(self, self.matrix[edge.start][edge.end], edge) def __str__(self): - return '\n'.join([' '.join(map(str, row)) for row in self.matrix]) + return '\n'.join([' '.join(map(str, row[1:])) for row in self.matrix[1:]]) + + def __call__(self, u: int, v: int): + return self.matrix[u][v] + + def __iter__(self): + return self.matrix.__iter__() From 011d6ccd8f7968ef405a6b576019c99c03eae693 Mon Sep 17 00:00:00 2001 From: weilycoder Date: Thu, 3 Oct 2024 21:53:18 +0800 Subject: [PATCH 11/18] add test_GraphMatrix --- cyaron/graph.py | 11 +++++++++-- cyaron/tests/graph_test.py | 21 +++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/cyaron/graph.py b/cyaron/graph.py index aa921d0..c5965ed 100644 --- a/cyaron/graph.py +++ b/cyaron/graph.py @@ -1,6 +1,9 @@ from .utils import * import random -from typing import TypeVar, Callable, List +from typing import TypeVar, Callable + + +__all__ = ["Edge", "Graph"] class Edge: @@ -528,7 +531,11 @@ def _calc_max_edge(point_count, directed, self_loop): class GraphMatrix: - """Class GraphMatrix: A class of the graph represented by adjacency matrix""" + """ + Class GraphMatrix: A class of the graph represented by adjacency matrix. + + *Deprecation warning: This class may be removed after a generic matrix class is implemented in the project.* + """ T = TypeVar('T') diff --git a/cyaron/tests/graph_test.py b/cyaron/tests/graph_test.py index 976930a..1e3f1db 100644 --- a/cyaron/tests/graph_test.py +++ b/cyaron/tests/graph_test.py @@ -151,3 +151,24 @@ def test_DAG_boundary(self): with self.assertRaises(Exception, msg="the number of edges of connected graph must more than the number of nodes - 1"): Graph.DAG(8, 6) Graph.DAG(8, 7) + + def test_GraphMatrix(self): + g = Graph(3, True) + edge_set = [(2, 3, 3), (3, 3, 1), (2, 3, 7), (2, 3, 4), (3, 2, 1), (1, 3, 3)] + for u, v, w in edge_set: + g.add_edge(u, v, weight=w) + self.assertEqual(str(g.to_matrix()), "-1 -1 3\n-1 -1 4\n-1 1 1") + self.assertEqual(str(g.to_matrix(default=0)), "0 0 3\n0 0 4\n0 1 1") + # lambda val, edge: edge.weight + gcd = lambda a, b: (gcd(b, a % b) if b else a) + lcm = lambda a, b: a * b // gcd(a, b) + merge1 = lambda v, e: v if v != -1 else e.weight + merge2 = lambda val, edge: max(edge.weight, val) + merge3 = lambda val, edge: min(edge.weight, val) + merge4 = lambda val, edge: gcd(val, edge.weight) + merge5 = lambda val, edge: lcm(val, edge.weight) if val else edge.weight + self.assertEqual(str(g.to_matrix(merge=merge1)), "-1 -1 3\n-1 -1 3\n-1 1 1") + self.assertEqual(str(g.to_matrix(merge=merge2)), "-1 -1 3\n-1 -1 7\n-1 1 1") + self.assertEqual(str(g.to_matrix(default=9, merge=merge3)), "9 9 3\n9 9 3\n9 1 1") + self.assertEqual(str(g.to_matrix(default=0, merge=merge4)), "0 0 3\n0 0 1\n0 1 1") + self.assertEqual(str(g.to_matrix(default=0, merge=merge5)), "0 0 3\n0 0 84\n0 1 1") From 6f47f116e8392f70bce43a960a2aa7afb4eb6121 Mon Sep 17 00:00:00 2001 From: "Mr. Python" <2789762371@qq.com> Date: Thu, 3 Oct 2024 22:57:40 +0800 Subject: [PATCH 12/18] bump version to 0.6.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4aa52b8..59ae302 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "cyaron" -version = "0.5.0" +version = "0.6.0" description = "CYaRon: Yet Another Random Olympic-iNformatics test data generator, A library for automatically generating test data for Online Judge, Olympic Informatics or automatic application testing" authors = ["Luogu Development Team "] license = "LGPL-3.0" From 272729839a24d04b32c770128ccd4036d7775ca9 Mon Sep 17 00:00:00 2001 From: "Mr. Python" <2789762371@qq.com> Date: Fri, 4 Oct 2024 13:42:33 +0800 Subject: [PATCH 13/18] Add GitHub Actions workflow for Python package testing --- .github/workflows/test.yml | 36 ++++++++++++++++++++++++++++++++++++ .gitignore | 2 ++ tox.ini | 11 ++++++++++- 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..4bdbd7c --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,36 @@ +name: Python package + +on: + - push + - pull_request + +jobs: + build: + strategy: + matrix: + platform: [ubuntu-20.04, windows-latest] + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.6 + uses: actions/setup-python@v4 + with: + python-version: '3.6' + - name: Set up Python 3.8 + uses: actions/setup-python@v4 + with: + python-version: '3.8' + - name: Set up Python 3.10 + uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Set up Python 3.12 + uses: actions/setup-python@v4 + with: + python-version: '3.12' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install tox + - name: Test with tox + run: python -m tox diff --git a/.gitignore b/.gitignore index 66d0bc3..bf4997a 100644 --- a/.gitignore +++ b/.gitignore @@ -138,3 +138,5 @@ venv/ # VS Code .vscode + +.python-version diff --git a/tox.ini b/tox.ini index ed1e45d..581148d 100644 --- a/tox.ini +++ b/tox.ini @@ -1,2 +1,11 @@ +[tox] +envlist = py36, py37, py38, py39, py310, py311, py312 +requires = virtualenv < 20.22.0 +isolated_build = true + [testenv] -commands=python unit_test.py +deps = + xeger + colorful +commands = python unit_test.py +allowlist_externals = poetry From 52cc6f141224e21b8e682240dc8cadd9d6f191e6 Mon Sep 17 00:00:00 2001 From: weilycoder Date: Sat, 5 Oct 2024 10:31:53 +0800 Subject: [PATCH 14/18] Fixed errors in Vector.random --- cyaron/vector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cyaron/vector.py b/cyaron/vector.py index c6b70e4..c5ba73e 100644 --- a/cyaron/vector.py +++ b/cyaron/vector.py @@ -54,9 +54,9 @@ def random(num: int = 5, position_range: list = None, mode: VectorRandomMode = 0 result = [] if mode == VectorRandomMode.repeatable: - result = [[random.randint(x, y) for x, y in zip(offset, length)] for _ in range(num)] + result = [[random.randint(x, x + y) for x, y in zip(offset, length)] for _ in range(num)] elif mode == VectorRandomMode.float: - result = [[random.uniform(x, y) for x, y in zip(offset, length)] for _ in range(num)] + result = [[random.uniform(x, x + y) for x, y in zip(offset, length)] for _ in range(num)] elif mode == VectorRandomMode.unique and vector_space > 5 * num: # O(NlogN) num_set = set() From ef322760a046cfb31ae327df80ba9f19c3bf7dc4 Mon Sep 17 00:00:00 2001 From: weilycoder Date: Sat, 5 Oct 2024 11:01:56 +0800 Subject: [PATCH 15/18] add vector_test --- cyaron/tests/__init__.py | 1 + cyaron/tests/vector_test.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 cyaron/tests/vector_test.py diff --git a/cyaron/tests/__init__.py b/cyaron/tests/__init__.py index dabf4f9..328a930 100644 --- a/cyaron/tests/__init__.py +++ b/cyaron/tests/__init__.py @@ -4,3 +4,4 @@ from .polygon_test import TestPolygon from .compare_test import TestCompare from .graph_test import TestGraph +from .vector_test import TestVector diff --git a/cyaron/tests/vector_test.py b/cyaron/tests/vector_test.py new file mode 100644 index 0000000..1380de4 --- /dev/null +++ b/cyaron/tests/vector_test.py @@ -0,0 +1,33 @@ +import unittest +from cyaron.vector import * + + +def has_duplicates(lst: list): + return len(lst) != len(set(lst)) + + +class TestVector(unittest.TestCase): + def test_unique_vector(self): + v = Vector.random(10**5, [10**6]) + self.assertFalse(has_duplicates(list(map(lambda tp: tuple(tp), v)))) + self.assertTrue(all(map(lambda v: 0 <= v[0] <= 10**6, v))) + v = Vector.random(1000, [(10**5, 10**6)]) + self.assertTrue(all(map(lambda v: 10**5 <= v[0] <= 10**6, v))) + with self.assertRaises( + Exception, + msg="1st param is so large that CYaRon can not generate unique vectors", + ): + v = Vector.random(10**5, [10**4]) + + def test_repeatable_vector(self): + v = Vector.random(10**5 + 1, [10**5], VectorRandomMode.repeatable) + self.assertTrue(all(map(lambda v: 0 <= v[0] <= 10**5, v))) + self.assertTrue(has_duplicates(list(map(lambda tp: tuple(tp), v)))) + v = Vector.random(1000, [(10**5, 10**6)], VectorRandomMode.repeatable) + self.assertTrue(all(map(lambda v: 10**5 <= v[0] <= 10**6, v))) + + def test_float_vector(self): + v = Vector.random(10**5, [10**5], VectorRandomMode.float) + self.assertTrue(all(map(lambda v: 0 <= v[0] <= 10**5, v))) + v = Vector.random(10**5, [(24, 25)], VectorRandomMode.float) + self.assertTrue(all(map(lambda v: 24 <= v[0] <= 25, v))) From ed5bacd3e74498aa1a91fce1c1d65330d8711cf7 Mon Sep 17 00:00:00 2001 From: "Mr. Python" <2789762371@qq.com> Date: Sun, 6 Oct 2024 01:24:36 +0800 Subject: [PATCH 16/18] Format,write notes, and add type hints for vector.py --- .pylintrc | 2 +- cyaron/io.py | 2 +- cyaron/utils.py | 12 ++++++-- cyaron/vector.py | 79 ++++++++++++++++++++++++++++++++++-------------- 4 files changed, 67 insertions(+), 28 deletions(-) diff --git a/.pylintrc b/.pylintrc index 1b65d41..71a41c0 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,3 +1,3 @@ [MASTER] py-version=3.5 -disable=R0902,R0913,R0917 \ No newline at end of file +disable=R0902,R0913,R0917,R0912 \ No newline at end of file diff --git a/cyaron/io.py b/cyaron/io.py index 126ce90..f073b50 100644 --- a/cyaron/io.py +++ b/cyaron/io.py @@ -15,7 +15,7 @@ class IO: - """Class IO: IO tool class. It will process the input and output files.""" + """IO tool class. It will process the input and output files.""" @overload def __init__(self, diff --git a/cyaron/utils.py b/cyaron/utils.py index cbef2ee..5b7cfd5 100644 --- a/cyaron/utils.py +++ b/cyaron/utils.py @@ -10,7 +10,7 @@ def list_like(data): Judge whether the object data is like a list or a tuple. object data -> the data to judge """ - return isinstance(data, tuple) or isinstance(data, list) + return isinstance(data, (tuple, list)) def int_like(data): @@ -36,6 +36,7 @@ def strtolines(str): def make_unicode(data): return str(data) + def unpack_kwargs(funcname, kwargs, arg_pattern): rv = {} kwargs = kwargs.copy() @@ -55,7 +56,12 @@ def unpack_kwargs(funcname, kwargs, arg_pattern): except KeyError as e: error = True if error: - raise TypeError('{}() missing 1 required keyword-only argument: \'{}\''.format(funcname, tp)) + raise TypeError( + '{}() missing 1 required keyword-only argument: \'{}\''. + format(funcname, tp)) if kwargs: - raise TypeError('{}() got an unexpected keyword argument \'{}\''.format(funcname, next(iter(kwargs.items()))[0])) + raise TypeError( + '{}() got an unexpected keyword argument \'{}\''.format( + funcname, + next(iter(kwargs.items()))[0])) return rv diff --git a/cyaron/vector.py b/cyaron/vector.py index c5ba73e..7ed60d8 100644 --- a/cyaron/vector.py +++ b/cyaron/vector.py @@ -1,8 +1,12 @@ -# coding=utf8 +""" + +""" -from .utils import * import random from enum import IntEnum +from typing import Union, Tuple, List, Set + +from .utils import list_like class VectorRandomMode(IntEnum): @@ -11,37 +15,48 @@ class VectorRandomMode(IntEnum): float = 2 +_Number = Union[int, float] + + class Vector: + """A class for generating random vectors""" + + IntVector = List[List[int]] + FloatVector = List[List[float]] @staticmethod - def random(num: int = 5, position_range: list = None, mode: VectorRandomMode = 0, **kwargs): + def random( + num: int = 5, + position_range: Union[List[Union[_Number, Tuple[_Number, _Number]]], + None] = None, + mode: VectorRandomMode = VectorRandomMode.unique, + ) -> Union[IntVector, FloatVector]: """ - brief : generating n random vectors in limited space - param : - # num : the number of vectors - # position_range : a list of limits for each dimension - # single number x represents range (0, x) - # list [x, y] or tuple (x, y) represents range (x, y) - # mode : the mode vectors generate, see Enum Class VectorRandomMode + Generate `num` random vectors in limited space + Args: + num: the number of vectors + position_range: a list of limits for each dimension + single number x represents range (0, x) + list [x, y] or tuple (x, y) represents range (x, y) + mode: the mode vectors generate, see Enum Class VectorRandomMode """ if position_range is None: position_range = [10] - if num > 1000000: - raise Exception("num no more than 1e6") if not list_like(position_range): - raise Exception("the 2nd param must be a list or tuple") + raise TypeError("the 2nd param must be a list or tuple") dimension = len(position_range) - offset = [] - length = [] + offset: List[_Number] = [] + length: List[_Number] = [] vector_space = 1 for i in range(0, dimension): if list_like(position_range[i]): if position_range[i][1] < position_range[i][0]: - raise Exception("upper-bound should larger than lower-bound") + raise ValueError( + "upper-bound should be larger than lower-bound") offset.append(position_range[i][0]) length.append(position_range[i][1] - position_range[i][0]) else: @@ -50,16 +65,22 @@ def random(num: int = 5, position_range: list = None, mode: VectorRandomMode = 0 vector_space *= (length[i] + 1) if mode == VectorRandomMode.unique and num > vector_space: - raise Exception("1st param is so large that CYaRon can not generate unique vectors") + raise ValueError( + "1st param is so large that CYaRon can not generate unique vectors" + ) - result = [] if mode == VectorRandomMode.repeatable: - result = [[random.randint(x, x + y) for x, y in zip(offset, length)] for _ in range(num)] + result = [[ + random.randint(x, x + y) for x, y in zip(offset, length) + ] for _ in range(num)] elif mode == VectorRandomMode.float: - result = [[random.uniform(x, x + y) for x, y in zip(offset, length)] for _ in range(num)] + result = [[ + random.uniform(x, x + y) for x, y in zip(offset, length) + ] for _ in range(num)] elif mode == VectorRandomMode.unique and vector_space > 5 * num: # O(NlogN) - num_set = set() + num_set: Set[int] = set() + result: List[List[int]] = [] for i in range(0, num): while True: rand = random.randint(0, vector_space - 1) @@ -74,7 +95,9 @@ def random(num: int = 5, position_range: list = None, mode: VectorRandomMode = 0 # generate 0~vector_space and shuffle rand_arr = list(range(0, vector_space)) random.shuffle(rand_arr) - result = [Vector.get_vector(dimension, length, x) for x in rand_arr[:num]] + result = [ + Vector.get_vector(dimension, length, x) for x in rand_arr[:num] + ] for x in result: for i in range(dimension): @@ -84,7 +107,17 @@ def random(num: int = 5, position_range: list = None, mode: VectorRandomMode = 0 @staticmethod def get_vector(dimension: int, position_range: list, hashcode: int): - tmp = [] + """ + Generates a vector based on the given dimension, position range, and hashcode. + Args: + dimension (int): The number of dimensions for the vector. + position_range (list): A list of integers specifying the range for each dimension. + hashcode (int): A hashcode used to generate the vector. + Returns: + list: A list representing the generated vector. + """ + + tmp: List[int] = [] for i in range(0, dimension): tmp.append(hashcode % (position_range[i] + 1)) hashcode //= (position_range[i] + 1) From ba37d805a3c5c2f2e88fdbf6bf1971475a64494e Mon Sep 17 00:00:00 2001 From: "Mr. Python" <2789762371@qq.com> Date: Mon, 7 Oct 2024 22:14:34 +0800 Subject: [PATCH 17/18] Format, write notes and add type hints for sequence.py --- .pylintrc | 2 +- cyaron/sequence.py | 74 +++++++++++++++++++++++++++++++++++----------- 2 files changed, 58 insertions(+), 18 deletions(-) diff --git a/.pylintrc b/.pylintrc index 71a41c0..de2c0b1 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,3 +1,3 @@ [MASTER] py-version=3.5 -disable=R0902,R0913,R0917,R0912 \ No newline at end of file +disable=R0902,R0903,R0913,R0917,R0912 \ No newline at end of file diff --git a/cyaron/sequence.py b/cyaron/sequence.py index 625456e..f1df388 100644 --- a/cyaron/sequence.py +++ b/cyaron/sequence.py @@ -1,33 +1,73 @@ -from .utils import * +""" +This module provides a `Sequence` class for generating sequences +based on a given formula and initial values. + +Classes: + Sequence: A class for creating and managing sequences. +""" + +from typing import Callable, Dict, List, Optional, Tuple, TypeVar, Union +from typing import cast as typecast + +from .utils import list_like + +T = TypeVar('T') + class Sequence: - """Class Sequence: the tool class for sequences. - """ + """A class for creating and managing sequences.""" - def __init__(self, formula, initial_values=()): - """__init__(self, formula, initial_values=() -> None - Create a sequence object. - int formula(int, function) -> the formula function ... + def __init__(self, + formula: Callable[[int, Callable[[int], T]], T], + initial_values: Optional[Union[List[T], Tuple[T, ...], + Dict[int, T]]] = ()): + """ + Initialize a sequence object. + Parameters: + formula: A function that defines the formula for the sequence. + initial_values (optional): Initial values for the sequence. + Can be a list, tuple, or dictionary. Defaults to an empty tuple. """ if not callable(formula): - raise Exception("formula must be a function") + raise TypeError("formula must be a function") self.formula = formula if list_like(initial_values): - self.values = dict(enumerate(initial_values)) + self.values = dict( + enumerate( + typecast(Union[List[T], Tuple[T, ...]], initial_values))) elif isinstance(initial_values, dict): self.values = initial_values else: - raise Exception("Initial_values must be either a list/tuple or a dict.") + raise TypeError( + "Initial_values must be either a list/tuple or a dict.") - def __get_one(self, i): + def get_one(self, i: int): + """ + Retrieve the value at the specified index, computing it if necessary. + Args: + i (int): The index of the value to retrieve. + Returns: + The value at the specified index. + If the value at the specified index is not already computed, it will be + calculated using the provided formula and stored for future access. + """ if i in self.values: return self.values[i] - - self.values[i] = self.formula(i, self.__get_one) + self.values[i] = self.formula(i, self.get_one) return self.values[i] - def get(self, left_range, right_range=None): + def get(self, left_range: int, right_range: Optional[int] = None): + """ + Retrieve a sequence of elements within the specified range. + If only `left_range` is provided, a single element is retrieved. + If both `left_range` and `right_range` are provided, a list of elements + from `left_range` to `right_range` (inclusive) is retrieved. + Args: + left_range: The starting index or the single index to retrieve. + right_range (optional): The ending index for the range retrieval. Defaults to None. + Returns: + A single element if `right_range` is None, otherwise a list of elements. + """ if right_range is None: - return self.__get_one(left_range) - - return [self.__get_one(i) for i in range(left_range, right_range+1)] + return self.get_one(left_range) + return [self.get_one(i) for i in range(left_range, right_range + 1)] From c92f60d1d067e3402a73b3d2add65cb841db1ffd Mon Sep 17 00:00:00 2001 From: "Mr. Python" <2789762371@qq.com> Date: Mon, 7 Oct 2024 22:44:11 +0800 Subject: [PATCH 18/18] Fix some type issue --- cyaron/io.py | 45 +++++++++++++++++++++++++-------------------- cyaron/sequence.py | 4 ++-- cyaron/vector.py | 38 +++++++++++++++++++++++++------------- 3 files changed, 52 insertions(+), 35 deletions(-) diff --git a/cyaron/io.py b/cyaron/io.py index f073b50..1ddb9ee 100644 --- a/cyaron/io.py +++ b/cyaron/io.py @@ -8,7 +8,7 @@ import re import subprocess import tempfile -from typing import Union, overload +from typing import Union, overload, Optional from io import IOBase from . import log from .utils import list_like, make_unicode @@ -19,29 +19,30 @@ class IO: @overload def __init__(self, - input_file: Union[IOBase, str, int, None] = None, - output_file: Union[IOBase, str, int, None] = None, - data_id: Union[str, None] = None, + input_file: Optional[Union[IOBase, str, int]] = None, + output_file: Optional[Union[IOBase, str, int]] = None, + data_id: Optional[int] = None, disable_output: bool = False): ... @overload def __init__(self, - data_id: Union[str, None] = None, - file_prefix: Union[str, None] = None, - input_suffix: Union[str, None] = '.in', - output_suffix: Union[str, None] = '.out', + data_id: Optional[int] = None, + file_prefix: Optional[str] = None, + input_suffix: str = '.in', + output_suffix: str = '.out', disable_output: bool = False): ... - def __init__(self, - input_file: Union[IOBase, str, int, None] = None, - output_file: Union[IOBase, str, int, None] = None, - data_id: Union[str, None] = None, - file_prefix: Union[str, None] = None, - input_suffix: Union[str, None] = '.in', - output_suffix: Union[str, None] = '.out', - disable_output: bool = False): + def __init__( # type: ignore + self, + input_file: Optional[Union[IOBase, str, int]] = None, + output_file: Optional[Union[IOBase, str, int]] = None, + data_id: Optional[int] = None, + file_prefix: Optional[str] = None, + input_suffix: str = '.in', + output_suffix: str = '.out', + disable_output: bool = False): """ Args: input_file (optional): input file object or filename or file descriptor. @@ -216,6 +217,8 @@ def output_gen(self, shell_cmd, time_limit=None): time_limit: the time limit (seconds) of the command to run. None means infinity. Defaults to None. """ + if self.output_file is None: + raise ValueError("Output file is disabled") self.flush_buffer() origin_pos = self.input_file.tell() self.input_file.seek(0) @@ -224,16 +227,16 @@ def output_gen(self, shell_cmd, time_limit=None): shell_cmd, shell=True, timeout=time_limit, - stdin=self.input_file, - stdout=self.output_file, + stdin=self.input_file.fileno(), + stdout=self.output_file.fileno(), universal_newlines=True, ) else: subprocess.check_call( shell_cmd, shell=True, - stdin=self.input_file, - stdout=self.output_file, + stdin=self.input_file.fileno(), + stdout=self.output_file.fileno(), universal_newlines=True, ) self.input_file.seek(origin_pos) @@ -248,6 +251,8 @@ def output_write(self, *args, **kwargs): *args: the elements to write separator: a string used to separate every element. Defaults to " ". """ + if self.output_file is None: + raise ValueError("Output file is disabled") self.__write(self.output_file, *args, **kwargs) def output_writeln(self, *args, **kwargs): diff --git a/cyaron/sequence.py b/cyaron/sequence.py index f1df388..4cf6ba3 100644 --- a/cyaron/sequence.py +++ b/cyaron/sequence.py @@ -19,8 +19,8 @@ class Sequence: def __init__(self, formula: Callable[[int, Callable[[int], T]], T], - initial_values: Optional[Union[List[T], Tuple[T, ...], - Dict[int, T]]] = ()): + initial_values: Union[List[T], Tuple[T, ...], Dict[int, + T]] = ()): """ Initialize a sequence object. Parameters: diff --git a/cyaron/vector.py b/cyaron/vector.py index 7ed60d8..5814fc9 100644 --- a/cyaron/vector.py +++ b/cyaron/vector.py @@ -4,7 +4,8 @@ import random from enum import IntEnum -from typing import Union, Tuple, List, Set +from typing import Sequence, Union, Tuple, List, Set +from typing import cast as typecast from .utils import list_like @@ -48,20 +49,21 @@ def random( dimension = len(position_range) - offset: List[_Number] = [] - length: List[_Number] = [] + offset: Sequence[_Number] = [] + length: Sequence[_Number] = [] vector_space = 1 for i in range(0, dimension): - if list_like(position_range[i]): - if position_range[i][1] < position_range[i][0]: + now_position_range = position_range[i] + if isinstance(now_position_range, tuple): + if now_position_range[1] < now_position_range[0]: raise ValueError( "upper-bound should be larger than lower-bound") - offset.append(position_range[i][0]) - length.append(position_range[i][1] - position_range[i][0]) + offset.append(now_position_range[0]) + length.append(now_position_range[1] - now_position_range[0]) else: offset.append(0) - length.append(position_range[i]) + length.append(now_position_range) vector_space *= (length[i] + 1) if mode == VectorRandomMode.unique and num > vector_space: @@ -69,7 +71,10 @@ def random( "1st param is so large that CYaRon can not generate unique vectors" ) + result: Union[List[List[int]], List[List[float]]] if mode == VectorRandomMode.repeatable: + offset = typecast(Sequence[int], offset) + length = typecast(Sequence[int], length) result = [[ random.randint(x, x + y) for x, y in zip(offset, length) ] for _ in range(num)] @@ -79,8 +84,11 @@ def random( ] for _ in range(num)] elif mode == VectorRandomMode.unique and vector_space > 5 * num: # O(NlogN) + offset = typecast(Sequence[int], offset) + length = typecast(Sequence[int], length) + vector_space = typecast(int, vector_space) num_set: Set[int] = set() - result: List[List[int]] = [] + result = typecast(List[List[int]], []) for i in range(0, num): while True: rand = random.randint(0, vector_space - 1) @@ -93,6 +101,9 @@ def random( result.append(tmp) else: # generate 0~vector_space and shuffle + offset = typecast(Sequence[int], offset) + length = typecast(Sequence[int], length) + vector_space = typecast(int, vector_space) rand_arr = list(range(0, vector_space)) random.shuffle(rand_arr) result = [ @@ -106,13 +117,14 @@ def random( return result @staticmethod - def get_vector(dimension: int, position_range: list, hashcode: int): + def get_vector(dimension: int, position_range: Sequence[int], + hashcode: int): """ Generates a vector based on the given dimension, position range, and hashcode. Args: - dimension (int): The number of dimensions for the vector. - position_range (list): A list of integers specifying the range for each dimension. - hashcode (int): A hashcode used to generate the vector. + dimension: The number of dimensions for the vector. + position_range: A list of integers specifying the range for each dimension. + hashcode: A hashcode used to generate the vector. Returns: list: A list representing the generated vector. """