1#!/usr/bin/env python3
  2
  3"""Various iterator related utilities."""
  4
  5import itertools
  6import collections
  7
  8
  9def inv_getitem(val, obj):
 10    """Return obj[val]."""
 11    # why?
 12    return obj[val]
 13
 14
 15def firstof(iterator):
 16    """Assuming the input yields iterables, return the first of each."""
 17    for i in iterator:
 18        yield i[0]
 19
 20
 21def consume(iterator, n=None):
 22    """Advance the iterator n-steps ahead. If n is none, consume entirely."""
 23
 24    # The technique uses objects that consume iterators at C speed.
 25    if n is None:
 26        # feed the entire iterator into a zero-length deque
 27        collections.deque(iterator, maxlen=0)
 28    else:
 29        # advance to the emtpy slice starting at position n
 30        next(itertools.islice(iterator, n, n), None)
 31
 32
 33def count_iterable(i):
 34    """Count the number of values returned by an iterator."""
 35    return sum(1 for e in i)
 36
 37
 38def pairwise(iterable):
 39    """Map s -> (s0,s1), (s1,s2), (s2, s3), ..."""
 40    a, b = itertools.tee(iterable)
 41    #next(b, None)
 42    next(b)
 43    return zip(a, b)
 44
 45
 46def ranger(iterable, binary_pred, stop_mod=None):
 47    """Return a (start,stop) tuples giving ranges found in the input series.
 48    Consecutive values are considered part of a range if binary_pred(n,n+1) returns True.
 49    stop_mod is added to the stop value of every output.
 50    I.e. [1,2,3,6,7,10] -> [(1,3),(6,7),(10,10)] if binary_pred is b-a<=1 and stop_mode is 0."""
 51
 52    #logging.debug('RANGER: '+str(iterable))
 53
 54    def returner(stop):
 55        """Return the end value of the range, modified to `stop_mod` if present."""
 56        if stop_mod is None:
 57            return stop
 58
 59        else:
 60            return stop + stop_mod
 61
 62    prev_item = None
 63    seq_start = None
 64    item = None
 65    for item in iterable:
 66        #logging.debug('ITEM '+str(item))
 67        if prev_item is not None:
 68            if binary_pred(prev_item, item):
 69                if seq_start is None:
 70                    seq_start = prev_item
 71                else:
 72                    pass
 73
 74            else:
 75                if seq_start is None:
 76                    yield prev_item, returner(prev_item)
 77                else:
 78                    yield seq_start, returner(prev_item)
 79                    seq_start = None
 80
 81        prev_item = item
 82
 83    if item is not None:
 84        if seq_start is None:
 85            yield item, returner(item)
 86        else:
 87            yield seq_start, returner(item)
 88
 89
 90def quantify(iterable, pred=bool):
 91    """Count how many times the predicate is true."""
 92    return sum(map(pred, iterable))
 93
 94
 95def compose(func_1, func_2, unpack=False):
 96    """The function returned by compose is a composition of func_1 and func_2.
 97    That is, compose(func_1, func_2)(5) == func_1(func_2(5)).
 98    """
 99    if not callable(func_1):
100        raise TypeError('First argument to compose must be callable')
101
102    if not callable(func_2):
103        raise TypeError('Second argument to compose must be callable')
104
105    if unpack:
106        def composition(*args, **kwargs):
107            """Implementation passing results from inner function as separate parameters
108            to the outer function.
109            """
110            return func_1(*func_2(*args, **kwargs))
111
112    else:
113        def composition(*args, **kwargs):
114            """Implementation passing the result of the inner function literally to the
115            outer function.
116            """
117            return func_1(func_2(*args, **kwargs))
118
119    return composition