def all_equivalent(iterable, relation=operator.eq, *, assume_reflexive=True, assume_symmetric=True, assume_transitive=True): ''' Return whether all elements in the iterable are equivalent to each other. By default "equivalent" means they're all equal to each other in Python. You can set a different relation to the `relation` argument, as a function that accepts two arguments and returns whether they're equivalent or not. You can use this, for example, to test if all items are NOT equal by passing in `relation=operator.ne`. You can also define any custom relation you want: `relation=(lambda x, y: x % 7 == y % 7)`. By default, we assume that the relation we're using is an equivalence relation (see http://en.wikipedia.org/wiki/Equivalence_relation for definition.) This means that we assume the relation is reflexive, symmetric and transitive, so we can do less checks on the elements to save time. You can use `assume_reflexive=False`, `assume_symmetric=False` and `assume_transitive=False` to break any of these assumptions and make this function do more checks that the equivalence holds between any pair of items from the iterable. (The more assumptions you ask to break, the more checks this function does before it concludes that the relation holds between all items.) ''' from python_toolbox import sequence_tools if not assume_transitive or not assume_reflexive: iterable = sequence_tools.ensure_iterable_is_sequence(iterable) if assume_transitive: pairs = cute_iter_tools.iterate_overlapping_subsequences(iterable) else: from python_toolbox import combi pairs = tuple(iterable * comb for comb in combi.CombSpace(len(iterable), 2)) # Can't feed the items directly to `CombSpace` because they might not # be hashable. if not assume_symmetric: pairs = itertools.chain( *itertools.starmap(lambda x, y: ((x, y), (y, x)), pairs)) if not assume_reflexive: pairs = itertools.chain(pairs, zip(iterable, iterable)) return all(itertools.starmap(relation, pairs))
def all_equivalent(iterable, relation=operator.eq, assume_reflexive=True, assume_symmetric=True, assume_transitive=True): ''' Return whether all elements in the iterable are equivalent to each other. By default "equivalent" means they're all equal to each other in Python. You can set a different relation to the `relation` argument, as a function that accepts two arguments and returns whether they're equivalent or not. You can use this, for example, to test if all items are NOT equal by passing in `relation=operator.ne`. You can also define any custom relation you want: `relation=(lambda x, y: x % 7 == y % 7)`. By default, we assume that the relation we're using is an equivalence relation (see http://en.wikipedia.org/wiki/Equivalence_relation for definition.) This means that we assume the relation is reflexive, symmetric and transitive, so we can do less checks on the elements to save time. You can use `assume_reflexive=False`, `assume_symmetric=False` and `assume_transitive=False` to break any of these assumptions and make this function do more checks that the equivalence holds between any pair of items from the iterable. (The more assumptions you ask to break, the more checks this function does before it concludes that the relation holds between all items.) ''' from python_toolbox import sequence_tools if not assume_transitive or not assume_reflexive: iterable = sequence_tools.ensure_iterable_is_sequence(iterable) if assume_transitive: pairs = cute_iter_tools.iterate_overlapping_subsequences(iterable) else: from python_toolbox import combi pairs = tuple( iterable * comb for comb in combi.CombSpace(len(iterable), 2) ) # Can't feed the items directly to `CombSpace` because they might not # be hashable. if not assume_symmetric: pairs = itertools.chain( *itertools.starmap(lambda x, y: ((x, y), (y, x)), pairs) ) if not assume_reflexive: pairs = itertools.chain(pairs, zip(iterable, iterable)) return all(itertools.starmap(relation, pairs))
def from_factoradic(factoradic_number): ''' Convert a factoradic representation to the number it's representing. Read about factoradic numbers here: https://en.wikipedia.org/wiki/Factorial_number_system Example: >>> from_factoradic((4, 0, 2, 0, 0)) 100 ''' from python_toolbox import sequence_tools assert isinstance(factoradic_number, collections.Iterable) factoradic_number = \ sequence_tools.ensure_iterable_is_sequence(factoradic_number) number = 0 for i, value in enumerate(reversed(factoradic_number)): assert 0 <= value <= i number += value * math.factorial(i) return number