From eb072bd51139971ba4ed2059773a4cb58965fc3d Mon Sep 17 00:00:00 2001 From: Bertrand Benjamin Date: Thu, 9 May 2019 16:46:24 +0200 Subject: [PATCH 1/7] Feat(random): Start random tree generation --- mapytex/calculus/core/random/__init__.py | 67 ++++++++++++++++++++++++ mapytex/calculus/core/random/str_gene.py | 39 -------------- 2 files changed, 67 insertions(+), 39 deletions(-) delete mode 100644 mapytex/calculus/core/random/str_gene.py diff --git a/mapytex/calculus/core/random/__init__.py b/mapytex/calculus/core/random/__init__.py index e69de29..c9f3080 100644 --- a/mapytex/calculus/core/random/__init__.py +++ b/mapytex/calculus/core/random/__init__.py @@ -0,0 +1,67 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# vim:fenc=utf-8 +# +# Copyright © 2017 lafrite +# +# Distributed under terms of the MIT license. + +""" +Tools to extract random nodes, random variables, generate random values and +fill new trees + +Flow +---- + +Tree with RdLeaf +| +| Extract nodes +| +List of nodes to generate +| +| extract_rv +| +List random variables to generate +| +| Generate +| +Dictionnary of generated random variables +| +| Compute nodes +| +Dictionnary of computed nodes +| +| Replace +| +Tree with RdLeaf replaced by generated values + +""" + + +def extract_rv(node_list): + """ Extract the set of random values from the node list + + :param node_list: list of nodes + :return: set of random values + + :example: + >>> node_list = ["{a}", "{a*k}"] + >>> extract_rv(node_list) + {"a", "k"} + """ + pass + +def compute_nodes(node_list, generated_values): + """ Compute nodes from generated random values + + :param generated_values: Dictionnary of name:generated value + :param node_list: list of nodes + :return: Dictionnary of evaluated nodes from generated values + + :example: + >>> node_list = ["{a}", "{a*k}"] + >>> generated_values = {"a":2, "k":3} + >>> compute_nodes(node_list, generated_values) + {"{a}": 2, "{a*k}": 6} + """ + pass diff --git a/mapytex/calculus/core/random/str_gene.py b/mapytex/calculus/core/random/str_gene.py deleted file mode 100644 index 7f857a7..0000000 --- a/mapytex/calculus/core/random/str_gene.py +++ /dev/null @@ -1,39 +0,0 @@ -#! /usr/bin/env python -# -*- coding: utf-8 -*- -# vim:fenc=utf-8 -# -# Copyright © 2017 lafrite -# -# Distributed under terms of the MIT license. - -""" -Function to create random strings - -:example: - ->>> form = "{a} / {b} + {c} / {k*b}" ->>> conditions = [ -... "abs({b}) != 1", -... "{d} > 1", -... "{b} != {d}", -... "gcd({a},{b}) == 1", -... "gcd({c},{k*b}) == 1", -... ] ->>> #random_str(form, conditions) - ->>> form = "{a:int}x + {b:fraction}" ->>> conditions = [ -... "{b.denom} > 2", -... ] ->>> #random_str(form, conditions) - -""" -import random - -__all__ = [] - - -# ----------------------------- -# Reglages pour 'vim' -# vim:set autoindent expandtab tabstop=4 shiftwidth=4: -# cursor: 16 del From a77c2f97954285665a959ebc59d734e413db5dc1 Mon Sep 17 00:00:00 2001 From: Bertrand Benjamin Date: Thu, 9 May 2019 16:46:24 +0200 Subject: [PATCH 2/7] Feat(random): Start random tree generation --- mapytex/calculus/core/random/__init__.py | 67 ++++++++++++++++++++++++ mapytex/calculus/core/random/str_gene.py | 39 -------------- 2 files changed, 67 insertions(+), 39 deletions(-) delete mode 100644 mapytex/calculus/core/random/str_gene.py diff --git a/mapytex/calculus/core/random/__init__.py b/mapytex/calculus/core/random/__init__.py index e69de29..c9f3080 100644 --- a/mapytex/calculus/core/random/__init__.py +++ b/mapytex/calculus/core/random/__init__.py @@ -0,0 +1,67 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# vim:fenc=utf-8 +# +# Copyright © 2017 lafrite +# +# Distributed under terms of the MIT license. + +""" +Tools to extract random nodes, random variables, generate random values and +fill new trees + +Flow +---- + +Tree with RdLeaf +| +| Extract nodes +| +List of nodes to generate +| +| extract_rv +| +List random variables to generate +| +| Generate +| +Dictionnary of generated random variables +| +| Compute nodes +| +Dictionnary of computed nodes +| +| Replace +| +Tree with RdLeaf replaced by generated values + +""" + + +def extract_rv(node_list): + """ Extract the set of random values from the node list + + :param node_list: list of nodes + :return: set of random values + + :example: + >>> node_list = ["{a}", "{a*k}"] + >>> extract_rv(node_list) + {"a", "k"} + """ + pass + +def compute_nodes(node_list, generated_values): + """ Compute nodes from generated random values + + :param generated_values: Dictionnary of name:generated value + :param node_list: list of nodes + :return: Dictionnary of evaluated nodes from generated values + + :example: + >>> node_list = ["{a}", "{a*k}"] + >>> generated_values = {"a":2, "k":3} + >>> compute_nodes(node_list, generated_values) + {"{a}": 2, "{a*k}": 6} + """ + pass diff --git a/mapytex/calculus/core/random/str_gene.py b/mapytex/calculus/core/random/str_gene.py deleted file mode 100644 index 7f857a7..0000000 --- a/mapytex/calculus/core/random/str_gene.py +++ /dev/null @@ -1,39 +0,0 @@ -#! /usr/bin/env python -# -*- coding: utf-8 -*- -# vim:fenc=utf-8 -# -# Copyright © 2017 lafrite -# -# Distributed under terms of the MIT license. - -""" -Function to create random strings - -:example: - ->>> form = "{a} / {b} + {c} / {k*b}" ->>> conditions = [ -... "abs({b}) != 1", -... "{d} > 1", -... "{b} != {d}", -... "gcd({a},{b}) == 1", -... "gcd({c},{k*b}) == 1", -... ] ->>> #random_str(form, conditions) - ->>> form = "{a:int}x + {b:fraction}" ->>> conditions = [ -... "{b.denom} > 2", -... ] ->>> #random_str(form, conditions) - -""" -import random - -__all__ = [] - - -# ----------------------------- -# Reglages pour 'vim' -# vim:set autoindent expandtab tabstop=4 shiftwidth=4: -# cursor: 16 del From c7561fa6fe322b2ec20b069d8e0db36223d94cb5 Mon Sep 17 00:00:00 2001 From: Bertrand Benjamin Date: Thu, 9 May 2019 18:37:39 +0200 Subject: [PATCH 3/7] Feat(random): random tree generation flow is ok. Need to create generate function --- mapytex/calculus/core/random/__init__.py | 99 +++++++++++++++++++----- mapytex/calculus/core/random/leaf.py | 32 ++++++++ 2 files changed, 111 insertions(+), 20 deletions(-) create mode 100644 mapytex/calculus/core/random/leaf.py diff --git a/mapytex/calculus/core/random/__init__.py b/mapytex/calculus/core/random/__init__.py index c9f3080..d550a66 100644 --- a/mapytex/calculus/core/random/__init__.py +++ b/mapytex/calculus/core/random/__init__.py @@ -7,7 +7,7 @@ # Distributed under terms of the MIT license. """ -Tools to extract random nodes, random variables, generate random values and +Tools to extract random leafs, random variables, generate random values and fill new trees Flow @@ -15,9 +15,9 @@ Flow Tree with RdLeaf | -| Extract nodes +| Extract rdLeaf | -List of nodes to generate +List of leafs to generate | | extract_rv | @@ -27,41 +27,100 @@ List random variables to generate | Dictionnary of generated random variables | -| Compute nodes +| Compute leafs | -Dictionnary of computed nodes +Dictionnary of computed leafs | | Replace | Tree with RdLeaf replaced by generated values +:example: + +>>> from ..tree import Tree +>>> rd_t = Tree("+", RdLeaf("a"), RdLeaf("a*k")) +>>> print(rd_t) ++ + > {a} + > {a*k} +>>> extract_rdleaf(rd_t) +['a', 'a*k'] + + """ +from .leaf import RdLeaf -def extract_rv(node_list): - """ Extract the set of random values from the node list +def extract_rdleaf(tree): + """ Extract rdLeaf in a Tree - :param node_list: list of nodes + :example: + >>> from ..tree import Tree + >>> rd_t = Tree("+", RdLeaf("a"), RdLeaf("a*k")) + >>> extract_rdleaf(rd_t) + ['a', 'a*k'] + >>> rd_t = Tree("+", RdLeaf("a"), 2) + >>> extract_rdleaf(rd_t) + ['a'] + """ + rd_leafs = [] + for leaf in tree.get_leafs(): + try: + leaf.rdleaf + except AttributeError: + pass + else: + rd_leafs.append(leaf.name) + return rd_leafs + +def extract_rv(leafs): + """ Extract the set of random values from the leaf list + + :param leafs: list of leafs :return: set of random values :example: - >>> node_list = ["{a}", "{a*k}"] - >>> extract_rv(node_list) - {"a", "k"} + >>> leafs = ["a", "a*k"] + >>> list(extract_rv(leafs)) + ['a', 'k'] """ - pass + rd_values = set() + for leaf in leafs: + for c in leaf: + if c.isalpha(): + rd_values.add(c) + return rd_values -def compute_nodes(node_list, generated_values): - """ Compute nodes from generated random values +def compute_leafs(leafs, generated_values): + """ Compute leafs from generated random values :param generated_values: Dictionnary of name:generated value - :param node_list: list of nodes - :return: Dictionnary of evaluated nodes from generated values + :param leafs: list of leafs + :return: Dictionnary of evaluated leafs from generated values :example: - >>> node_list = ["{a}", "{a*k}"] + >>> leafs = ["a", "a*k"] >>> generated_values = {"a":2, "k":3} - >>> compute_nodes(node_list, generated_values) - {"{a}": 2, "{a*k}": 6} + >>> compute_leafs(leafs, generated_values) + {'a': 2, 'a*k': 6} """ - pass + return {leaf: eval(leaf, generated_values) for leaf in leafs} + +def replace_rdleaf(tree, computed_leafs): + """ Replace RdLeaf by the corresponding computed value + + >>> from ..tree import Tree + >>> rd_t = Tree("+", RdLeaf("a"), RdLeaf("a*k")) + >>> computed_leafs = {'a': 2, 'a*k': 6} + >>> print(replace_rdleaf(rd_t, computed_leafs)) + + + > 2 + > 6 + """ + def replace(leaf): + try: + return leaf.replace(computed_leafs) + except AttributeError: + return leaf + return tree.map_on_leaf(replace) + diff --git a/mapytex/calculus/core/random/leaf.py b/mapytex/calculus/core/random/leaf.py new file mode 100644 index 0000000..481cfc8 --- /dev/null +++ b/mapytex/calculus/core/random/leaf.py @@ -0,0 +1,32 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# vim:fenc=utf-8 +# +# Copyright © 2017 lafrite +# +# Distributed under terms of the MIT license. + +""" + +""" + +class RdLeaf(): + """ Random leaf + + """ + def __init__(self, name): + self._name = name + self.rdleaf = True + + @property + def name(self): + return self._name + + def replace(self, computed): + return computed[self.name] + + def __str__(self): + return "{" + self.name + "}" + + def __repr__(self): + return f"<{self.__class__.__name__} {self.__str__}>" From d94beef79af473287dc129914b1822623f11150d Mon Sep 17 00:00:00 2001 From: Bertrand Benjamin Date: Sun, 12 May 2019 15:59:49 +0200 Subject: [PATCH 4/7] Fix: doctest over sets --- mapytex/calculus/core/random/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mapytex/calculus/core/random/__init__.py b/mapytex/calculus/core/random/__init__.py index d550a66..2b6ced4 100644 --- a/mapytex/calculus/core/random/__init__.py +++ b/mapytex/calculus/core/random/__init__.py @@ -81,8 +81,8 @@ def extract_rv(leafs): :example: >>> leafs = ["a", "a*k"] - >>> list(extract_rv(leafs)) - ['a', 'k'] + >>> extract_rv(leafs) == {'a', 'k'} + True """ rd_values = set() for leaf in leafs: From 295677045bdca4a35519a2536294d65a2b43747c Mon Sep 17 00:00:00 2001 From: Bertrand Benjamin Date: Sun, 12 May 2019 16:58:36 +0200 Subject: [PATCH 5/7] Feat: random variable generator done! --- mapytex/calculus/core/random/__init__.py | 93 ++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/mapytex/calculus/core/random/__init__.py b/mapytex/calculus/core/random/__init__.py index 2b6ced4..e7af569 100644 --- a/mapytex/calculus/core/random/__init__.py +++ b/mapytex/calculus/core/random/__init__.py @@ -49,6 +49,7 @@ Tree with RdLeaf replaced by generated values """ +from random import choice from .leaf import RdLeaf def extract_rdleaf(tree): @@ -124,3 +125,95 @@ def replace_rdleaf(tree, computed_leafs): return leaf return tree.map_on_leaf(replace) + +def random_generator(rd_variables, + conditions = [], + rejected = [0], + min_max = (-10, 10), + variables_scope = {}): + """ Generate random variables + + :param rd_variables: list of random variables to generate + :param conditions: condition over variables + :param rejected: Rejected values for the generator (default [0]) + :param min_max: (min, max) limits in between variables will be generated + :param variables_scope: rejected and min_max define for individual variables + :return: dictionnary of generated variables + + :example: + >>> gene = random_generator(["a", "b"], + ... ["a > 0"], + ... [0], (-10, 10), + ... {"a": {"rejected": [0, 1]}, + ... "b": {"min_max": (-5, 0)}}) + >>> gene["a"] > 0 + True + >>> gene["a"] != 0 + True + >>> gene["b"] < 0 + True + >>> gene = random_generator(["a", "b"], + ... ["a % b == 0"], + ... [0, 1], (-10, 10)) + >>> gene["a"] not in [0, 1] + True + >>> gene["b"] in list(range(-10, 11)) + True + >>> gene["a"] % gene["b"] + 0 + """ + complete_scope = build_variable_scope(rd_variables, + rejected, min_max, + variables_scope) + choices_list = {v: list(set(range( + complete_scope[v]["min_max"][0], complete_scope[v]["min_max"][1]+1) + ).difference(complete_scope[v]["rejected"])) + for v in rd_variables} + + generate_variable = {v: choice(choices_list[v]) for v in rd_variables} + + while not all([eval(c, __builtins__, generate_variable) for c in conditions]): + generate_variable = {v: choice(choices_list[v]) for v in rd_variables} + + return generate_variable + +def build_variable_scope(rd_variables, rejected, min_max, variables_scope): + """ Build variables scope from incomplete one + + :param rd_variables: list of random variables to generate + :param rejected: Rejected values for the generator + :param min_max: (min, max) limits in between variables will be generated + :param variables_scope: rejected and min_max define for individual variables + :return: complete variable scope + + :example: + >>> completed = build_variable_scope(["a", "b", "c", "d"], [0], (-10, 10), + ... {"a": {"rejected": [0, 1]}, + ... "b": {"min_max": (-5, 0)}, + ... "c": {"rejected": [2], "min_max": (0, 5)}}) + >>> complete = {'a': {'rejected': [0, 1], 'min_max': (-10, 10)}, + ... 'b': {'rejected': [0], 'min_max': (-5, 0)}, + ... 'c': {'rejected': [2], 'min_max': (0, 5)}, + ... 'd': {'rejected': [0], 'min_max': (-10, 10)}} + >>> completed == complete + True + """ + complete_scope = variables_scope + for v in rd_variables: + try: + complete_scope[v] + except KeyError: + complete_scope[v] = {"rejected": rejected, "min_max": min_max} + else: + try: + complete_scope[v]["rejected"] + except KeyError: + complete_scope[v]["rejected"] = rejected + try: + complete_scope[v]["min_max"] + except KeyError: + complete_scope[v]["min_max"] = min_max + return complete_scope + + + From 33ded0d90e84f577cd1c0aa9ff0da38eebf4efa4 Mon Sep 17 00:00:00 2001 From: Bertrand Benjamin Date: Sun, 12 May 2019 17:14:25 +0200 Subject: [PATCH 6/7] Doc: Doctest for Tree generator --- mapytex/calculus/core/random/__init__.py | 27 ++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/mapytex/calculus/core/random/__init__.py b/mapytex/calculus/core/random/__init__.py index e7af569..3900fe0 100644 --- a/mapytex/calculus/core/random/__init__.py +++ b/mapytex/calculus/core/random/__init__.py @@ -38,18 +38,34 @@ Tree with RdLeaf replaced by generated values :example: >>> from ..tree import Tree ->>> rd_t = Tree("+", RdLeaf("a"), RdLeaf("a*k")) +>>> rd_t = Tree("/", RdLeaf("a"), RdLeaf("a*k")) >>> print(rd_t) -+ +/ > {a} > {a*k} ->>> extract_rdleaf(rd_t) +>>> leafs = extract_rdleaf(rd_t) +>>> leafs ['a', 'a*k'] +>>> rd_varia = extract_rv(leafs) +>>> rd_varia # doctest: +SKIP +{'a', 'k'} +>>> generated = random_generator(rd_varia, conditions=['a%2+1']) +>>> generated # doctest: +SKIP +{'a': 7, 'k': 4} +>>> computed = compute_leafs(leafs, generated) +>>> computed # doctest: +SKIP +{'a': 7, 'a*k': 28} +>>> replaced = replace_rdleaf(rd_t, computed) +>>> print(replaced) # doctest: +SKIP +/ + > 7 + > 28 """ from random import choice +from functools import reduce from .leaf import RdLeaf def extract_rdleaf(tree): @@ -125,7 +141,6 @@ def replace_rdleaf(tree, computed_leafs): return leaf return tree.map_on_leaf(replace) - def random_generator(rd_variables, conditions = [], rejected = [0], @@ -170,6 +185,10 @@ def random_generator(rd_variables, ).difference(complete_scope[v]["rejected"])) for v in rd_variables} + # quantity_choices = reduce(lambda x,y : x*y, + # [len(choices_list[v]) for v in choices_list]) + # TODO: améliorer la méthode de rejet avec un cache |dim. mai 12 17:04:11 CEST 2019 + generate_variable = {v: choice(choices_list[v]) for v in rd_variables} while not all([eval(c, __builtins__, generate_variable) for c in conditions]): From ca7cd001d8514494b7b9fbb8a57229f5e097879d Mon Sep 17 00:00:00 2001 From: Bertrand Benjamin Date: Mon, 13 May 2019 19:37:34 +0200 Subject: [PATCH 7/7] Feat(random): random str parsing is done --- mapytex/calculus/core/random/leaf.py | 45 ++++++++++++++++++++++++++-- mapytex/calculus/core/str2.py | 31 +++++++++++++++---- 2 files changed, 68 insertions(+), 8 deletions(-) diff --git a/mapytex/calculus/core/random/leaf.py b/mapytex/calculus/core/random/leaf.py index 481cfc8..fd74e60 100644 --- a/mapytex/calculus/core/random/leaf.py +++ b/mapytex/calculus/core/random/leaf.py @@ -9,6 +9,45 @@ """ """ +from ..coroutine import * + +@coroutine +def look_for_rdleaf(target): + """ Coroutine which look to "{...}" which are RdLeaf + + :example: + >>> from ..str2 import list_sink + >>> str2list = look_for_rdleaf(list_sink) + >>> for i in "{a}+{a*b}-2": + ... str2list.send(i) + >>> a = str2list.throw(STOOOP) + >>> a + [, '+', , '-', '2'] + + """ + try: + target_ = target() + except TypeError: + target_ = target + + try: + while True: + tok = yield + if tok == "{": + stack = "" + stacking = True + elif tok == "}": + target_.send(RdLeaf(stack)) + stack = "" + stacking = False + else: + if stacking: + stack += tok + else: + target_.send(tok) + + except STOOOP as err: + yield target_.throw(err) class RdLeaf(): """ Random leaf @@ -23,10 +62,10 @@ class RdLeaf(): return self._name def replace(self, computed): - return computed[self.name] + return computed[self._name] def __str__(self): - return "{" + self.name + "}" + return "{" + self._name + "}" def __repr__(self): - return f"<{self.__class__.__name__} {self.__str__}>" + return f"<{self.__class__.__name__} {self._name}>" diff --git a/mapytex/calculus/core/str2.py b/mapytex/calculus/core/str2.py index 0630db0..0e9f89c 100644 --- a/mapytex/calculus/core/str2.py +++ b/mapytex/calculus/core/str2.py @@ -15,6 +15,7 @@ from decimal import Decimal, InvalidOperation from .coroutine import * from .operator import is_operator from .MO import moify +from .random.leaf import look_for_rdleaf __all__ = ["str2", ] @@ -466,7 +467,7 @@ def lookforNumbers(target): if tok is not None: try: int(tok) - except ValueError: + except (ValueError, TypeError): if tok == '.': if current.replace("-", "", 1).isdigit(): current += tok @@ -487,7 +488,7 @@ def lookforNumbers(target): else: try: target_.send(typifiy_numbers(current)) - except InvalidOperation: + except (InvalidOperation, TypeError): target_.send(current) target_.send('+') current = tok @@ -499,7 +500,7 @@ def lookforNumbers(target): else: try: target_.send(typifiy_numbers(current)) - except InvalidOperation: + except (InvalidOperation, TypeError): target_.send(current) current = tok else: @@ -518,7 +519,7 @@ def lookforNumbers(target): if current: try: target_.send(typifiy_numbers(current)) - except InvalidOperation: + except (InvalidOperation, TypeError): target_.send(current) yield target_.throw(err) @@ -589,7 +590,7 @@ def list_sink(): yield ans def str2(sink, convert_to_mo=True): - """ Return a pipeline which parse an expression with the sink as an endpont + """ Return a pipeline which parse an expression with the sink as an endpoint :example: @@ -790,6 +791,26 @@ def str2(sink, convert_to_mo=True): return pipeline +def rdstr2(sink): + """ Return a pipeline which parse random expression and with sink as endpoint + + :example: + >>> rdstr2list = rdstr2(list_sink) + >>> rdstr2list("{a}+{a*b}-2") + [, '+', , '+', ] + """ + lfop = lookfor(is_operator) + operator_corout = partial(concurent_broadcast, lookfors=[lfop]) + def pipeline(expression): + str2_corout = look_for_rdleaf(lookforNumbers(operator_corout(missing_times(moify(pparser(sink)))))) + + for i in expression.replace(" ",""): + str2_corout.send(i) + a = str2_corout.throw(STOOOP) + + return a + + return pipeline str2nestedlist = str2(list_sink)