From f04e221f706bd01e3995802f5a0da57a761cdaec Mon Sep 17 00:00:00 2001 From: Bertrand Benjamin Date: Sat, 22 Dec 2018 08:42:31 +0100 Subject: [PATCH] Feat(Core/API): Random generation methods for numbers --- mapytex/calculus/API/tokens/number.py | 48 +++++------ mapytex/calculus/core/random.py | 119 ++++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 26 deletions(-) create mode 100644 mapytex/calculus/core/random.py diff --git a/mapytex/calculus/API/tokens/number.py b/mapytex/calculus/API/tokens/number.py index b2cd9db..5299c16 100644 --- a/mapytex/calculus/API/tokens/number.py +++ b/mapytex/calculus/API/tokens/number.py @@ -14,6 +14,7 @@ from decimal import Decimal as _Decimal from random import random, randint from .token import Token from ...core.arithmetic import gcd +from ...core.random import filter_random from ...core.MO import MO, MOnumber from ...core.MO.fraction import MOFraction @@ -58,7 +59,7 @@ class Integer(Token): min_value = -10, max_value = 10, rejected = [0, 1], - reject_callbacks=[], + accept_callbacks=[], ): """ Generate a random Integer @@ -66,14 +67,11 @@ class Integer(Token): :param min_value: minimum value :param max_value: maximum value :param rejected: rejected values - :param reject_callbacks: list of function for value rejection + :param accept_callbacks: list of function for value acceptation """ - conditions = [lambda x: x in rejected] + reject_callbacks - - candidate = randint(min_value, max_value) - while any(c(candidate) for c in conditions): - candidate = randint(min_value, max_value) + candidate = filter_random(min_value, max_value, + rejected, accept_callbacks) return Integer(candidate, name) @@ -181,10 +179,10 @@ class Fraction(Token): name="", fix_num="", min_num=-10, max_num=10, rejected_num=[0], - reject_num_callbacks=[], + accept_num_callbacks=[], fix_denom="", min_denom=-10, max_denom=10, rejected_denom=[0, 1, -1], - reject_denom_callbacks=[], + accept_denom_callbacks=[], irreductible=False, not_integer=True ): @@ -195,39 +193,37 @@ class Fraction(Token): :param min_num: minimum value for the numerator :param max_num: maximum value for the numerator :param rejected_num: rejected values for the numerator - :param reject_num_callbacks: list of function for numerator rejection + :param accept_num_callbacks: list of function for numerator rejection :param fix_denom: if set, the denomerator will get this value :param min_denom: minimum value for the denominator :param max_denom: maximum value for the denominator :param rejected_denom: rejected values for the denominator - :param reject_denom_callbacks: list of function for denomerator rejection + :param accept_denom_callbacks: list of function for denomerator rejection :param irreductible: is the generated fraction necessary irreductible :param not_integer: can the generated fraction be egal to an interger """ if fix_num == "": - conditions = [lambda x: x in rejected_denom] + reject_num_callbacks - - num = randint(min_num, max_num) - while any(c(num) for c in conditions): - num = randint(min_num, max_num) + num = filter_random(min_num, max_num, + rejected_num, + accept_num_callbacks) else: num = fix_num if fix_denom == "": - conditions = [lambda x: x in rejected_denom] + reject_denom_callbacks + accept_callbacks = accept_denom_callbacks if irreductible: - def not_prime_with_num(denom): - return gcd(num, denom) != 1 - conditions.append(not_prime_with_num) + def prime_with_num(denom): + return gcd(num, denom) == 1 + accept_callbacks.append(prime_with_num) if not_integer: - def divise_num(denom): - return num % denom == 0 - conditions.append(divise_num) + def not_divise_num(denom): + return num % denom != 0 + accept_callbacks.append(not_divise_num) - denom = randint(min_denom, max_denom) - while any(c(denom) for c in conditions) : - denom = randint(min_denom, max_denom) + denom = filter_random(min_denom, max_denom, + rejected_denom, + accept_callbacks) else: denom = fix_denom diff --git a/mapytex/calculus/core/random.py b/mapytex/calculus/core/random.py new file mode 100644 index 0000000..431518b --- /dev/null +++ b/mapytex/calculus/core/random.py @@ -0,0 +1,119 @@ +#! /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 things + +""" +from random import randint, choice + +__all__ = ["reject_random", "filter_random", "FilterRandom"] + +def reject_random(min_value = -10, + max_value = 10, + rejected = [0, 1], + accept_callbacks=[], + ): + """ Generate a random integer with the rejection method + + :param name: name of the Integer + :param min_value: minimum value + :param max_value: maximum value + :param rejected: rejected values + :param accept_callbacks: list of function for value rejection + + """ + conditions = [lambda x: x not in rejected] + accept_callbacks + + candidate = randint(min_value, max_value) + while not all(c(candidate) for c in conditions): + candidate = randint(min_value, max_value) + + return candidate + +def filter_random(min_value = -10, + max_value = 10, + rejected = [0, 1], + accept_callbacks=[], + ): + """ Generate a random integer by filtering then choosing a candidate + + :param name: name of the Integer + :param min_value: minimum value + :param max_value: maximum value + :param rejected: rejected values + :param accept_callbacks: list of function for value rejection + + """ + candidates = set(range(min_value, max_value+1)) + candidates = {c for c in candidates if c not in rejected} + + candidates = [candidate for candidate in candidates \ + if all(c(candidate) for c in accept_callbacks)] + + if len(candidates) == 0: + raise OverflowError("There is no candidates for this range and those conditions") + return choice(candidates) + +class FilterRandom(object): + + """ Integer random generator which filter then choose candidate + """ + # TODO: Faire un cache pour éviter de reconstruire les listes à chaque fois |ven. déc. 21 19:07:42 CET 2018 + + def __init__(self, + rejected = [0, 1], + accept_callbacks=[], + min_value = -10, + max_value = 10, + ): + self.conditions = (lambda x: x not in rejected,) + tuple(accept_callbacks) + + self._min = min_value + self._max = max_value + + candidates = set(range(self._min, self._max+1)) + + self._candidates = { candidate for candidate in candidates \ + if all(c(candidate) for c in self.conditions) } + + def add_candidates(self, low, high): + """ Add candidates between low and high to _candidates """ + if low < self._min: + self._min = low + useless_low = False + else: + useless_low = True + if high > self._max: + self._max = high + useless_high = False + else: + useless_high = True + + if not(useless_low and useless_high): + candidates = set(range(low, high+1)) + + self._candidates = self._candidates.union({ + candidate for candidate in candidates \ + if all(c(candidate) for c in self.conditions) \ + }) + + def candidates(self, min_value=-10, max_value=10): + """ Return candidates between min_value and max_value """ + return [c for c in self._candidates if (c > min_value and c < max_value)] + + def __call__(self, min_value=-10, max_value=10): + """ Randomly choose on candidate """ + self.add_candidates(min_value, max_value) + return choice(self.candidates(min_value, max_value)) + + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del