def test_short_iterables(): assert tuple(iterate_overlapping_subsequences([1])) == () assert tuple(iterate_overlapping_subsequences([1], length=7)) == ()
def test_various_lengths(): assert tuple(iterate_overlapping_subsequences(xrange(7), length=3)) == \ ((0, 1, 2), (1, 2, 3), (2, 3, 4), (3, 4, 5), (4, 5, 6)) assert tuple(iterate_overlapping_subsequences(xrange(7), length=4)) == \ ((0, 1, 2, 3), (1, 2, 3, 4), (2, 3, 4, 5), (3, 4, 5, 6)) assert tuple(iterate_overlapping_subsequences(xrange(7), length=5)) == \ ((0, 1, 2, 3, 4), (1, 2, 3, 4, 5), (2, 3, 4, 5, 6)) assert tuple(iterate_overlapping_subsequences(xrange(7), length=4, wrap_around=True)) == ((0, 1, 2, 3), (1, 2, 3, 4), (2, 3, 4, 5), (3, 4, 5, 6), (4, 5, 6, 0), (5, 6, 0, 1), (6, 0, 1, 2)) assert tuple(iterate_overlapping_subsequences(xrange(7), length=5, wrap_around=True)) == ((0, 1, 2, 3, 4), (1, 2, 3, 4, 5), (2, 3, 4, 5, 6), (3, 4, 5, 6, 0), (4, 5, 6, 0, 1), (5, 6, 0, 1, 2), (6, 0, 1, 2, 3))
def test_various_lengths(): assert tuple(iterate_overlapping_subsequences(range(7), length=3)) == \ ((0, 1, 2), (1, 2, 3), (2, 3, 4), (3, 4, 5), (4, 5, 6)) assert tuple(iterate_overlapping_subsequences(range(7), length=4)) == \ ((0, 1, 2, 3), (1, 2, 3, 4), (2, 3, 4, 5), (3, 4, 5, 6)) assert tuple(iterate_overlapping_subsequences(range(7), length=5)) == \ ((0, 1, 2, 3, 4), (1, 2, 3, 4, 5), (2, 3, 4, 5, 6)) assert tuple(iterate_overlapping_subsequences(range(7), length=4, wrap_around=True)) == ((0, 1, 2, 3), (1, 2, 3, 4), (2, 3, 4, 5), (3, 4, 5, 6), (4, 5, 6, 0), (5, 6, 0, 1), (6, 0, 1, 2)) assert tuple(iterate_overlapping_subsequences(range(7), length=5, wrap_around=True)) == ((0, 1, 2, 3, 4), (1, 2, 3, 4, 5), (2, 3, 4, 5, 6), (3, 4, 5, 6, 0), (4, 5, 6, 0, 1), (5, 6, 0, 1, 2), (6, 0, 1, 2, 3))
def divide_to_slices(sequence, n_slices): ''' Divide a sequence to slices. Example: >>> divide_to_slices(range(10), 3) [range(0, 4), range(4, 7), range(7, 10)] ''' from python_toolbox import cute_iter_tools assert isinstance(n_slices, numbers.Integral) assert n_slices >= 1 sequence_length = get_length(sequence) base_slice_length, remainder = divmod(sequence_length, n_slices) indices = [0] for i in range(n_slices): indices.append(indices[-1] + base_slice_length + (remainder > i)) assert len(indices) == n_slices + 1 assert indices[0] == 0 assert indices[-1] == sequence_length return [ sequence[x:y] for x, y in cute_iter_tools.iterate_overlapping_subsequences(indices) ]
def test_garbage_collection(): garbage_collected = set() class GarbageNoter(object): def __init__(self, n): assert isinstance(n, int) self.n = n def __del__(self): garbage_collected.add(self.n) iterable = (GarbageNoter(i) for i in xrange(7)) consecutive_subsequences_iterator = \ iterate_overlapping_subsequences(iterable, length=3) def assert_garbage_collected(indexes): gc_tools.collect() assert set(indexes) == garbage_collected assert_garbage_collected(()) next(consecutive_subsequences_iterator) assert_garbage_collected(()) next(consecutive_subsequences_iterator) assert_garbage_collected((0,)) next(consecutive_subsequences_iterator) assert_garbage_collected((0, 1)) next(consecutive_subsequences_iterator) assert_garbage_collected((0, 1, 2)) next(consecutive_subsequences_iterator) assert_garbage_collected((0, 1, 2, 3)) with cute_testing.RaiseAssertor(StopIteration): next(consecutive_subsequences_iterator) assert_garbage_collected((0, 1, 2, 3, 4, 5, 6))
def divide_to_slices(sequence, n_slices): ''' Divide a sequence to slices. Example: >>> divide_to_slices(range(10), 3) [range(0, 4), range(4, 7), range(7, 10)] ''' from python_toolbox import cute_iter_tools assert isinstance(n_slices, numbers.Integral) assert n_slices >= 1 sequence_length = get_length(sequence) base_slice_length, remainder = divmod(sequence_length, n_slices) indices = [0] for i in range(n_slices): indices.append(indices[-1] + base_slice_length + (remainder > i)) assert len(indices) == n_slices + 1 assert indices[0] == 0 assert indices[-1] == sequence_length return [sequence[x:y] for x, y in cute_iter_tools.iterate_overlapping_subsequences(indices)]
def test_garbage_collection(): garbage_collected = set() class GarbageNoter(object): def __init__(self, n): assert isinstance(n, int) self.n = n def __del__(self): garbage_collected.add(self.n) iterable = (GarbageNoter(i) for i in xrange(7)) consecutive_subsequences_iterator = \ iterate_overlapping_subsequences(iterable, length=3) def assert_garbage_collected(indexes): gc_tools.collect() assert set(indexes) == garbage_collected assert_garbage_collected(()) next(consecutive_subsequences_iterator) assert_garbage_collected(()) next(consecutive_subsequences_iterator) assert_garbage_collected((0, )) next(consecutive_subsequences_iterator) assert_garbage_collected((0, 1)) next(consecutive_subsequences_iterator) assert_garbage_collected((0, 1, 2)) next(consecutive_subsequences_iterator) assert_garbage_collected((0, 1, 2, 3)) with cute_testing.RaiseAssertor(StopIteration): next(consecutive_subsequences_iterator) assert_garbage_collected((0, 1, 2, 3, 4, 5, 6))
def all_equal(iterable, exhaustive=False): ''' Return whether all elements in the iterable are equal to each other. If `exhaustive` is set to `False`, it's assumed that the equality relation is transitive, therefore not every member is tested against every other member. So in a list of size `n`, `n-1` equality checks will be made. If `exhaustive` is set to `True`, every member will be checked against every other member. So in a list of size `n`, `(n*(n-1))/2` equality checks will be made. ''' # todo: Maybe I should simply check if `len(set(iterable)) == 1`? Will not # work for unhashables. if exhaustive is True: items = tuple(iterable) if len(items) <= 1: return True from python_toolbox import combi pairs = tuple( items * comb for comb in combi.CombSpace(len(items), 2) ) # Can't feed the items directly to `CombSpace` because they might not # be hashable. else: # exhaustive is False pairs = cute_iter_tools.iterate_overlapping_subsequences(iterable) return all(a == b for (a, b) in pairs)
def all_equal(iterable, exhaustive=False): ''' Return whether all elements in the iterable are equal to each other. If `exhaustive` is set to `False`, it's assumed that the equality relation is transitive, therefore not every member is tested against every other member. So in a list of size `n`, `n-1` equality checks will be made. If `exhaustive` is set to `True`, every member will be checked against every other member. So in a list of size `n`, `(n*(n-1))/2` equality checks will be made. ''' # todo: Maybe I should simply check if `len(set(iterable)) == 1`? Will not # work for unhashables. if exhaustive is True: items = tuple(iterable) if len(items) <= 1: return True from python_toolbox import combi pairs = tuple(items * comb for comb in combi.CombSpace(len(items), 2)) # Can't feed the items directly to `CombSpace` because they might not # be hashable. else: # exhaustive is False pairs = cute_iter_tools.iterate_overlapping_subsequences(iterable) return all(a == b for (a, b) in pairs)
def test_lazy_tuple(): lazy_tuple = \ iterate_overlapping_subsequences(range(7), length=3, lazy_tuple=True) assert isinstance(lazy_tuple, nifty_collections.LazyTuple) assert not lazy_tuple.collected_data assert lazy_tuple == \ ((0, 1, 2), (1, 2, 3), (2, 3, 4), (3, 4, 5), (4, 5, 6))
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 find_clear_place_on_circle(circle_points, circle_size=1): ''' Find the point on a circle that's the farthest away from other points. Given an interval `(0, circle_size)` and a bunch of points in it, find a place for a new point that is as far away from the other points as possible. (Since this is a circle, there's wraparound, e.g. the end of the interval connects to the start.) ''' from python_toolbox import cute_iter_tools # Before starting, taking care of two edge cases: if not circle_points: # Edge case: No points at all return circle_size / 2 if len(circle_points) == 1: # Edge case: Only one point return (circle_points[0] + circle_size / 2) % circle_size sorted_circle_points = sorted(circle_points) last_point = sorted_circle_points[-1] if last_point >= circle_size: raise Exception(f"One of the points ({last_point}) is bigger than the " f"circle size {circle_size}.") clear_space = {} for first_point, second_point in \ cute_iter_tools.iterate_overlapping_subsequences(sorted_circle_points, wrap_around=True): clear_space[first_point] = second_point - first_point # That's the only one that might be negative, so we ensure it's positive: clear_space[last_point] %= circle_size maximum_clear_space = max(clear_space.values()) winners = [ key for (key, value) in clear_space.items() if value == maximum_clear_space ] winner = winners[0] result = (winner + (maximum_clear_space / 2)) % circle_size return result
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 find_clear_place_on_circle(circle_points, circle_size=1): ''' Find the point on a circle that's the farthest away from other points. Given an interval `(0, circle_size)` and a bunch of points in it, find a place for a new point that is as far away from the other points as possible. (Since this is a circle, there's wraparound, e.g. the end of the interval connects to the start.) ''' from python_toolbox import cute_iter_tools # Before starting, taking care of two edge cases: if not circle_points: # Edge case: No points at all return circle_size / 2 if len(circle_points) == 1: # Edge case: Only one point return (circle_points[0] + circle_size / 2) % circle_size sorted_circle_points = sorted(circle_points) last_point = sorted_circle_points[-1] if last_point >= circle_size: raise Exception("One of the points (%s) is bigger than the circle " "size %s." % (last_point, circle_size)) clear_space = {} for first_point, second_point in \ cute_iter_tools.iterate_overlapping_subsequences(sorted_circle_points, wrap_around=True): clear_space[first_point] = second_point - first_point # That's the only one that might be negative, so we ensure it's positive: clear_space[last_point] %= circle_size maximum_clear_space = max(clear_space.values()) winners = [key for (key, value) in clear_space.items() if value == maximum_clear_space] winner = winners[0] result = (winner + (maximum_clear_space / 2)) % circle_size return result
def all_equal(iterable, exhaustive=False): ''' Return whether all elements in the iterable are equal to each other. If `exhaustive` is set to `False`, it's assumed that the equality relation is transitive, therefore not every member is tested against every other member. So in a list of size `n`, `n-1` equality checks will be made. If `exhaustive` is set to `True`, every member will be checked against every other member. So in a list of size `n`, `(n*(n-1))/2` equality checks will be made. ''' # todo: Maybe I should simply check if `len(set(iterable)) == 1`? Will not # work for unhashables. if exhaustive is True: pairs = sequence_tools.combinations(list(iterable), 2) else: # exhaustive is False pairs = cute_iter_tools.iterate_overlapping_subsequences(iterable) return all(a==b for (a, b) in pairs)
def all_equal(iterable, exhaustive=False): ''' Return whether all elements in the iterable are equal to each other. If `exhaustive` is set to `False`, it's assumed that the equality relation is transitive, therefore not every member is tested against every other member. So in a list of size `n`, `n-1` equality checks will be made. If `exhaustive` is set to `True`, every member will be checked against every other member. So in a list of size `n`, `(n*(n-1))/2` equality checks will be made. ''' # todo: Maybe I should simply check if `len(set(iterable)) == 1`? Will not # work for unhashables. if exhaustive is True: pairs = sequence_tools.combinations(list(iterable), 2) else: # exhaustive is False pairs = cute_iter_tools.iterate_overlapping_subsequences(iterable) return all(a == b for (a, b) in pairs)
def test_length_2(): # `iterate_overlapping_subsequences` returns an iterator, not a sequence: assert not isinstance(iterate_overlapping_subsequences(list(range(4))), collections.Sequence) assert tuple(iterate_overlapping_subsequences(range(4))) == \ tuple(iterate_overlapping_subsequences(xrange(4))) == \ ((0, 1), (1, 2), (2, 3)) assert tuple(iterate_overlapping_subsequences(range(4), wrap_around=True)) == \ tuple(iterate_overlapping_subsequences(xrange(4), wrap_around=True)) ==\ ((0, 1), (1, 2), (2, 3), (3, 0)) assert tuple(iterate_overlapping_subsequences('meow')) == \ (('m', 'e'), ('e', 'o'), ('o', 'w'))
def test_length_2(): # `iterate_overlapping_subsequences` returns an iterator, not a sequence: assert not sequence_tools.is_sequence( iterate_overlapping_subsequences(range(4))) assert tuple(iterate_overlapping_subsequences(range(4))) == \ tuple(iterate_overlapping_subsequences(xrange(4))) == \ ((0, 1), (1, 2), (2, 3)) assert tuple(iterate_overlapping_subsequences(range(4), wrap_around=True)) == \ tuple(iterate_overlapping_subsequences(xrange(4), wrap_around=True)) ==\ ((0, 1), (1, 2), (2, 3), (3, 0)) assert tuple(iterate_overlapping_subsequences('meow')) == \ (('m', 'e'), ('e', 'o'), ('o', 'w'))
def test_length_2(): # `iterate_overlapping_subsequences` returns an iterator, not a sequence: assert not sequence_tools.is_sequence( iterate_overlapping_subsequences(range(4)) ) assert tuple(iterate_overlapping_subsequences(range(4))) == \ tuple(iterate_overlapping_subsequences(xrange(4))) == \ ((0, 1), (1, 2), (2, 3)) assert tuple(iterate_overlapping_subsequences(range(4), wrap_around=True)) == \ tuple(iterate_overlapping_subsequences(xrange(4), wrap_around=True)) ==\ ((0, 1), (1, 2), (2, 3), (3, 0)) assert tuple(iterate_overlapping_subsequences('meow')) == \ (('m', 'e'), ('e', 'o'), ('o', 'w'))
def test_length_2(): # `iterate_overlapping_subsequences` returns an iterator, not a sequence: assert not isinstance( iterate_overlapping_subsequences(list(range(4))), collections.Sequence ) assert tuple(iterate_overlapping_subsequences(list(range(4)))) == \ tuple(iterate_overlapping_subsequences(range(4))) == \ ((0, 1), (1, 2), (2, 3)) assert tuple(iterate_overlapping_subsequences(list(range(4)), wrap_around=True)) == \ tuple(iterate_overlapping_subsequences(range(4), wrap_around=True)) ==\ ((0, 1), (1, 2), (2, 3), (3, 0)) assert tuple(iterate_overlapping_subsequences('meow')) == \ (('m', 'e'), ('e', 'o'), ('o', 'w'))
def test_iterable_too_short(): with cute_testing.RaiseAssertor(NotImplementedError): tuple(iterate_overlapping_subsequences([1], wrap_around=True))