From ee60550e9ba3ab94ca43a890cf4313b63ffa1a81 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Tue, 25 Jan 2022 06:56:53 -0600 Subject: Move doctests to the main docs. Eliminate duplication. Improve coverage. (GH-30869) --- Doc/library/itertools.rst | 238 +++++++++++++++++++++++++++ Lib/test/test_itertools.py | 393 --------------------------------------------- 2 files changed, 238 insertions(+), 393 deletions(-) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 3466756..f0d93eb 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -1004,3 +1004,241 @@ which incur interpreter overhead. c, n = c*(n-r)//n, n-1 result.append(pool[-1-n]) return tuple(result) + +.. doctest:: + :hide: + + These examples no longer appear in the docs but are guaranteed + to keep working. + + >>> amounts = [120.15, 764.05, 823.14] + >>> for checknum, amount in zip(count(1200), amounts): + ... print('Check %d is for $%.2f' % (checknum, amount)) + ... + Check 1200 is for $120.15 + Check 1201 is for $764.05 + Check 1202 is for $823.14 + + >>> import operator + >>> for cube in map(operator.pow, range(1,4), repeat(3)): + ... print(cube) + ... + 1 + 8 + 27 + + >>> reportlines = ['EuroPython', 'Roster', '', 'alex', '', 'laura', '', 'martin', '', 'walter', '', 'samuele'] + >>> for name in islice(reportlines, 3, None, 2): + ... print(name.title()) + ... + Alex + Laura + Martin + Walter + Samuele + + >>> from operator import itemgetter + >>> d = dict(a=1, b=2, c=1, d=2, e=1, f=2, g=3) + >>> di = sorted(sorted(d.items()), key=itemgetter(1)) + >>> for k, g in groupby(di, itemgetter(1)): + ... print(k, list(map(itemgetter(0), g))) + ... + 1 ['a', 'c', 'e'] + 2 ['b', 'd', 'f'] + 3 ['g'] + + # Find runs of consecutive numbers using groupby. The key to the solution + # is differencing with a range so that consecutive numbers all appear in + # same group. + >>> data = [ 1, 4,5,6, 10, 15,16,17,18, 22, 25,26,27,28] + >>> for k, g in groupby(enumerate(data), lambda t:t[0]-t[1]): + ... print(list(map(operator.itemgetter(1), g))) + ... + [1] + [4, 5, 6] + [10] + [15, 16, 17, 18] + [22] + [25, 26, 27, 28] + + Now, we test all of the itertool recipes + + >>> import operator + >>> import collections + + >>> take(10, count()) + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + + >>> list(prepend(1, [2, 3, 4])) + [1, 2, 3, 4] + + >>> list(enumerate('abc')) + [(0, 'a'), (1, 'b'), (2, 'c')] + + >>> list(islice(tabulate(lambda x: 2*x), 4)) + [0, 2, 4, 6] + + >>> list(tail(3, 'ABCDEFG')) + ['E', 'F', 'G'] + + >>> it = iter(range(10)) + >>> consume(it, 3) + >>> next(it) + 3 + >>> consume(it) + >>> next(it, 'Done') + 'Done' + + >>> nth('abcde', 3) + 'd' + + >>> nth('abcde', 9) is None + True + + >>> [all_equal(s) for s in ('', 'A', 'AAAA', 'AAAB', 'AAABA')] + [True, True, True, False, False] + + >>> quantify(range(99), lambda x: x%2==0) + 50 + + >>> quantify([True, False, False, True, True]) + 3 + + >>> quantify(range(12), pred=lambda x: x%2==1) + 6 + + >>> a = [[1, 2, 3], [4, 5, 6]] + >>> list(flatten(a)) + [1, 2, 3, 4, 5, 6] + + >>> list(repeatfunc(pow, 5, 2, 3)) + [8, 8, 8, 8, 8] + + >>> import random + >>> take(5, map(int, repeatfunc(random.random))) + [0, 0, 0, 0, 0] + + >>> list(islice(pad_none('abc'), 0, 6)) + ['a', 'b', 'c', None, None, None] + + >>> list(ncycles('abc', 3)) + ['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c'] + + >>> dotproduct([1,2,3], [4,5,6]) + 32 + + >>> data = [20, 40, 24, 32, 20, 28, 16] + >>> list(convolve(data, [0.25, 0.25, 0.25, 0.25])) + [5.0, 15.0, 21.0, 29.0, 29.0, 26.0, 24.0, 16.0, 11.0, 4.0] + >>> list(convolve(data, [1, -1])) + [20, 20, -16, 8, -12, 8, -12, -16] + >>> list(convolve(data, [1, -2, 1])) + [20, 0, -36, 24, -20, 20, -20, -4, 16] + + >>> list(flatten([('a', 'b'), (), ('c', 'd', 'e'), ('f',), ('g', 'h', 'i')])) + ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'] + + >>> import random + >>> random.seed(85753098575309) + >>> list(repeatfunc(random.random, 3)) + [0.16370491282496968, 0.45889608687313455, 0.3747076837820118] + >>> list(repeatfunc(chr, 3, 65)) + ['A', 'A', 'A'] + >>> list(repeatfunc(pow, 3, 2, 5)) + [32, 32, 32] + + >>> list(grouper('abcdefg', 3, fillvalue='x')) + [('a', 'b', 'c'), ('d', 'e', 'f'), ('g', 'x', 'x')] + + >>> it = grouper('abcdefg', 3, incomplete='strict') + >>> next(it) + ('a', 'b', 'c') + >>> next(it) + ('d', 'e', 'f') + >>> next(it) + Traceback (most recent call last): + ... + ValueError: zip() argument 2 is shorter than argument 1 + + >>> list(grouper('abcdefg', n=3, incomplete='ignore')) + [('a', 'b', 'c'), ('d', 'e', 'f')] + + >>> list(triplewise('ABCDEFG')) + [('A', 'B', 'C'), ('B', 'C', 'D'), ('C', 'D', 'E'), ('D', 'E', 'F'), ('E', 'F', 'G')] + + >>> list(sliding_window('ABCDEFG', 4)) + [('A', 'B', 'C', 'D'), ('B', 'C', 'D', 'E'), ('C', 'D', 'E', 'F'), ('D', 'E', 'F', 'G')] + + >>> list(roundrobin('abc', 'd', 'ef')) + ['a', 'd', 'e', 'b', 'f', 'c'] + + >>> def is_odd(x): + ... return x % 2 == 1 + + >>> evens, odds = partition(is_odd, range(10)) + >>> list(evens) + [0, 2, 4, 6, 8] + >>> list(odds) + [1, 3, 5, 7, 9] + + >>> it = iter('ABCdEfGhI') + >>> all_upper, remainder = before_and_after(str.isupper, it) + >>> ''.join(all_upper) + 'ABC' + >>> ''.join(remainder) + 'dEfGhI' + + >>> list(powerset([1,2,3])) + [(), (1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)] + + >>> all(len(list(powerset(range(n)))) == 2**n for n in range(18)) + True + + >>> list(powerset('abcde')) == sorted(sorted(set(powerset('abcde'))), key=len) + True + + >>> list(unique_everseen('AAAABBBCCDAABBB')) + ['A', 'B', 'C', 'D'] + + >>> list(unique_everseen('ABBCcAD', str.lower)) + ['A', 'B', 'C', 'D'] + + >>> list(unique_justseen('AAAABBBCCDAABBB')) + ['A', 'B', 'C', 'D', 'A', 'B'] + + >>> list(unique_justseen('ABBCcAD', str.lower)) + ['A', 'B', 'C', 'A', 'D'] + + >>> d = dict(a=1, b=2, c=3) + >>> it = iter_except(d.popitem, KeyError) + >>> d['d'] = 4 + >>> next(it) + ('d', 4) + >>> next(it) + ('c', 3) + >>> next(it) + ('b', 2) + >>> d['e'] = 5 + >>> next(it) + ('e', 5) + >>> next(it) + ('a', 1) + >>> next(it, 'empty') + 'empty' + + >>> first_true('ABC0DEF1', '9', str.isdigit) + '0' + + >>> population = 'ABCDEFGH' + >>> for r in range(len(population) + 1): + ... seq = list(combinations(population, r)) + ... for i in range(len(seq)): + ... assert nth_combination(population, r, i) == seq[i] + ... for i in range(-len(seq), 0): + ... assert nth_combination(population, r, i) == seq[i] + + >>> iterable = 'abcde' + >>> r = 3 + >>> combos = list(combinations(iterable, r)) + >>> all(nth_combination(iterable, r, i) == comb for i, comb in enumerate(combos)) + True diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py index 3043e8c..3f3f7cb 100644 --- a/Lib/test/test_itertools.py +++ b/Lib/test/test_itertools.py @@ -2321,399 +2321,6 @@ class SizeofTest(unittest.TestCase): basesize + 10 * self.ssize_t + 4 * self.ssize_t) -libreftest = """ Doctest for examples in the library reference: libitertools.tex - - ->>> amounts = [120.15, 764.05, 823.14] ->>> for checknum, amount in zip(count(1200), amounts): -... print('Check %d is for $%.2f' % (checknum, amount)) -... -Check 1200 is for $120.15 -Check 1201 is for $764.05 -Check 1202 is for $823.14 - ->>> import operator ->>> for cube in map(operator.pow, range(1,4), repeat(3)): -... print(cube) -... -1 -8 -27 - ->>> reportlines = ['EuroPython', 'Roster', '', 'alex', '', 'laura', '', 'martin', '', 'walter', '', 'samuele'] ->>> for name in islice(reportlines, 3, None, 2): -... print(name.title()) -... -Alex -Laura -Martin -Walter -Samuele - ->>> from operator import itemgetter ->>> d = dict(a=1, b=2, c=1, d=2, e=1, f=2, g=3) ->>> di = sorted(sorted(d.items()), key=itemgetter(1)) ->>> for k, g in groupby(di, itemgetter(1)): -... print(k, list(map(itemgetter(0), g))) -... -1 ['a', 'c', 'e'] -2 ['b', 'd', 'f'] -3 ['g'] - -# Find runs of consecutive numbers using groupby. The key to the solution -# is differencing with a range so that consecutive numbers all appear in -# same group. ->>> data = [ 1, 4,5,6, 10, 15,16,17,18, 22, 25,26,27,28] ->>> for k, g in groupby(enumerate(data), lambda t:t[0]-t[1]): -... print(list(map(operator.itemgetter(1), g))) -... -[1] -[4, 5, 6] -[10] -[15, 16, 17, 18] -[22] -[25, 26, 27, 28] - ->>> def take(n, iterable): -... "Return first n items of the iterable as a list" -... return list(islice(iterable, n)) - ->>> def prepend(value, iterator): -... "Prepend a single value in front of an iterator" -... # prepend(1, [2, 3, 4]) -> 1 2 3 4 -... return chain([value], iterator) - ->>> def enumerate(iterable, start=0): -... return zip(count(start), iterable) - ->>> def tabulate(function, start=0): -... "Return function(0), function(1), ..." -... return map(function, count(start)) - ->>> import collections ->>> def consume(iterator, n=None): -... "Advance the iterator n-steps ahead. If n is None, consume entirely." -... # Use functions that consume iterators at C speed. -... if n is None: -... # feed the entire iterator into a zero-length deque -... collections.deque(iterator, maxlen=0) -... else: -... # advance to the empty slice starting at position n -... next(islice(iterator, n, n), None) - ->>> def nth(iterable, n, default=None): -... "Returns the nth item or a default value" -... return next(islice(iterable, n, None), default) - ->>> def all_equal(iterable): -... "Returns True if all the elements are equal to each other" -... g = groupby(iterable) -... return next(g, True) and not next(g, False) - ->>> def quantify(iterable, pred=bool): -... "Count how many times the predicate is true" -... return sum(map(pred, iterable)) - ->>> def pad_none(iterable): -... "Returns the sequence elements and then returns None indefinitely" -... return chain(iterable, repeat(None)) - ->>> def ncycles(iterable, n): -... "Returns the sequence elements n times" -... return chain(*repeat(iterable, n)) - ->>> def dotproduct(vec1, vec2): -... return sum(map(operator.mul, vec1, vec2)) - ->>> def flatten(listOfLists): -... return list(chain.from_iterable(listOfLists)) - ->>> def repeatfunc(func, times=None, *args): -... "Repeat calls to func with specified arguments." -... " Example: repeatfunc(random.random)" -... if times is None: -... return starmap(func, repeat(args)) -... else: -... return starmap(func, repeat(args, times)) - ->>> def grouper(iterable, n, *, incomplete='fill', fillvalue=None): -... "Collect data into non-overlapping fixed-length chunks or blocks" -... # grouper('ABCDEFG', 3, fillvalue='x') --> ABC DEF Gxx -... # grouper('ABCDEFG', 3, incomplete='strict') --> ABC DEF ValueError -... # grouper('ABCDEFG', 3, incomplete='ignore') --> ABC DEF -... args = [iter(iterable)] * n -... if incomplete == 'fill': -... return zip_longest(*args, fillvalue=fillvalue) -... if incomplete == 'strict': -... return zip(*args, strict=True) -... if incomplete == 'ignore': -... return zip(*args) -... else: -... raise ValueError('Expected fill, strict, or ignore') - ->>> def triplewise(iterable): -... "Return overlapping triplets from an iterable" -... # pairwise('ABCDEFG') -> ABC BCD CDE DEF EFG -... for (a, _), (b, c) in pairwise(pairwise(iterable)): -... yield a, b, c - ->>> import collections ->>> def sliding_window(iterable, n): -... # sliding_window('ABCDEFG', 4) -> ABCD BCDE CDEF DEFG -... it = iter(iterable) -... window = collections.deque(islice(it, n), maxlen=n) -... if len(window) == n: -... yield tuple(window) -... for x in it: -... window.append(x) -... yield tuple(window) - ->>> def roundrobin(*iterables): -... "roundrobin('ABC', 'D', 'EF') --> A D E B F C" -... # Recipe credited to George Sakkis -... pending = len(iterables) -... nexts = cycle(iter(it).__next__ for it in iterables) -... while pending: -... try: -... for next in nexts: -... yield next() -... except StopIteration: -... pending -= 1 -... nexts = cycle(islice(nexts, pending)) - ->>> def partition(pred, iterable): -... "Use a predicate to partition entries into false entries and true entries" -... # partition(is_odd, range(10)) --> 0 2 4 6 8 and 1 3 5 7 9 -... t1, t2 = tee(iterable) -... return filterfalse(pred, t1), filter(pred, t2) - ->>> def before_and_after(predicate, it): -... ''' Variant of takewhile() that allows complete -... access to the remainder of the iterator. -... -... >>> all_upper, remainder = before_and_after(str.isupper, 'ABCdEfGhI') -... >>> str.join('', all_upper) -... 'ABC' -... >>> str.join('', remainder) -... 'dEfGhI' -... -... Note that the first iterator must be fully -... consumed before the second iterator can -... generate valid results. -... ''' -... it = iter(it) -... transition = [] -... def true_iterator(): -... for elem in it: -... if predicate(elem): -... yield elem -... else: -... transition.append(elem) -... return -... def remainder_iterator(): -... yield from transition -... yield from it -... return true_iterator(), remainder_iterator() - ->>> def powerset(iterable): -... "powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)" -... s = list(iterable) -... return chain.from_iterable(combinations(s, r) for r in range(len(s)+1)) - ->>> def unique_everseen(iterable, key=None): -... "List unique elements, preserving order. Remember all elements ever seen." -... # unique_everseen('AAAABBBCCDAABBB') --> A B C D -... # unique_everseen('ABBCcAD', str.lower) --> A B C D -... seen = set() -... seen_add = seen.add -... if key is None: -... for element in iterable: -... if element not in seen: -... seen_add(element) -... yield element -... else: -... for element in iterable: -... k = key(element) -... if k not in seen: -... seen_add(k) -... yield element - ->>> def unique_justseen(iterable, key=None): -... "List unique elements, preserving order. Remember only the element just seen." -... # unique_justseen('AAAABBBCCDAABBB') --> A B C D A B -... # unique_justseen('ABBCcAD', str.lower) --> A B C A D -... return map(next, map(itemgetter(1), groupby(iterable, key))) - ->>> def first_true(iterable, default=False, pred=None): -... '''Returns the first true value in the iterable. -... -... If no true value is found, returns *default* -... -... If *pred* is not None, returns the first item -... for which pred(item) is true. -... -... ''' -... # first_true([a,b,c], x) --> a or b or c or x -... # first_true([a,b], x, f) --> a if f(a) else b if f(b) else x -... return next(filter(pred, iterable), default) - ->>> def nth_combination(iterable, r, index): -... 'Equivalent to list(combinations(iterable, r))[index]' -... pool = tuple(iterable) -... n = len(pool) -... if r < 0 or r > n: -... raise ValueError -... c = 1 -... k = min(r, n-r) -... for i in range(1, k+1): -... c = c * (n - k + i) // i -... if index < 0: -... index += c -... if index < 0 or index >= c: -... raise IndexError -... result = [] -... while r: -... c, n, r = c*r//n, n-1, r-1 -... while index >= c: -... index -= c -... c, n = c*(n-r)//n, n-1 -... result.append(pool[-1-n]) -... return tuple(result) - - -This is not part of the examples but it tests to make sure the definitions -perform as purported. - ->>> take(10, count()) -[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - ->>> list(prepend(1, [2, 3, 4])) -[1, 2, 3, 4] - ->>> list(enumerate('abc')) -[(0, 'a'), (1, 'b'), (2, 'c')] - ->>> list(islice(tabulate(lambda x: 2*x), 4)) -[0, 2, 4, 6] - ->>> it = iter(range(10)) ->>> consume(it, 3) ->>> next(it) -3 ->>> consume(it) ->>> next(it, 'Done') -'Done' - ->>> nth('abcde', 3) -'d' - ->>> nth('abcde', 9) is None -True - ->>> [all_equal(s) for s in ('', 'A', 'AAAA', 'AAAB', 'AAABA')] -[True, True, True, False, False] - ->>> quantify(range(99), lambda x: x%2==0) -50 - ->>> a = [[1, 2, 3], [4, 5, 6]] ->>> flatten(a) -[1, 2, 3, 4, 5, 6] - ->>> list(repeatfunc(pow, 5, 2, 3)) -[8, 8, 8, 8, 8] - ->>> import random ->>> take(5, map(int, repeatfunc(random.random))) -[0, 0, 0, 0, 0] - ->>> list(islice(pad_none('abc'), 0, 6)) -['a', 'b', 'c', None, None, None] - ->>> list(ncycles('abc', 3)) -['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c'] - ->>> dotproduct([1,2,3], [4,5,6]) -32 - ->>> list(grouper('abcdefg', 3, fillvalue='x')) -[('a', 'b', 'c'), ('d', 'e', 'f'), ('g', 'x', 'x')] - ->>> it = grouper('abcdefg', 3, incomplete='strict') ->>> next(it) -('a', 'b', 'c') ->>> next(it) -('d', 'e', 'f') ->>> next(it) -Traceback (most recent call last): - ... -ValueError: zip() argument 2 is shorter than argument 1 - ->>> list(grouper('abcdefg', n=3, incomplete='ignore')) -[('a', 'b', 'c'), ('d', 'e', 'f')] - ->>> list(triplewise('ABCDEFG')) -[('A', 'B', 'C'), ('B', 'C', 'D'), ('C', 'D', 'E'), ('D', 'E', 'F'), ('E', 'F', 'G')] - ->>> list(sliding_window('ABCDEFG', 4)) -[('A', 'B', 'C', 'D'), ('B', 'C', 'D', 'E'), ('C', 'D', 'E', 'F'), ('D', 'E', 'F', 'G')] - ->>> list(roundrobin('abc', 'd', 'ef')) -['a', 'd', 'e', 'b', 'f', 'c'] - ->>> def is_odd(x): -... return x % 2 == 1 - ->>> evens, odds = partition(is_odd, range(10)) ->>> list(evens) -[0, 2, 4, 6, 8] ->>> list(odds) -[1, 3, 5, 7, 9] - ->>> it = iter('ABCdEfGhI') ->>> all_upper, remainder = before_and_after(str.isupper, it) ->>> ''.join(all_upper) -'ABC' ->>> ''.join(remainder) -'dEfGhI' - ->>> list(powerset([1,2,3])) -[(), (1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)] - ->>> all(len(list(powerset(range(n)))) == 2**n for n in range(18)) -True - ->>> list(powerset('abcde')) == sorted(sorted(set(powerset('abcde'))), key=len) -True - ->>> list(unique_everseen('AAAABBBCCDAABBB')) -['A', 'B', 'C', 'D'] - ->>> list(unique_everseen('ABBCcAD', str.lower)) -['A', 'B', 'C', 'D'] - ->>> list(unique_justseen('AAAABBBCCDAABBB')) -['A', 'B', 'C', 'D', 'A', 'B'] - ->>> list(unique_justseen('ABBCcAD', str.lower)) -['A', 'B', 'C', 'A', 'D'] - ->>> first_true('ABC0DEF1', '9', str.isdigit) -'0' - ->>> population = 'ABCDEFGH' ->>> for r in range(len(population) + 1): -... seq = list(combinations(population, r)) -... for i in range(len(seq)): -... assert nth_combination(population, r, i) == seq[i] -... for i in range(-len(seq), 0): -... assert nth_combination(population, r, i) == seq[i] - - -""" - -__test__ = {'libreftest' : libreftest} - def load_tests(loader, tests, pattern): tests.addTest(doctest.DocTestSuite()) return tests -- cgit v0.12