Commits

Sergey Astanin committed 859e59a

change license, rename splitBy() to partition(), add chop(), version bump

  • Participants
  • Parent commits e01562f

Comments (0)

Files changed (2)

+"""Functions to split or partition a sequence."""
+
+from collections import deque
+
+__all__ = [ "partition", "chop" ]
+__author__ = "Sergey Astanin"
+__license__ = "MIT"
+__version__ = "0.2"
+
+class _SplitSeq:
+    """
+    Lazily process a sequence in single pass and split into two.
+
+    Computes both output sequences even if only one of them is consumed.
+    """
+    def __init__(self, condition, sequence):
+        self.cond = condition
+        self.goods = deque([])
+        self.bads = deque([])
+        self.seq = iter(sequence)
+    def getNext(self, getGood=True):
+        if getGood:
+            these, those, cond = self.goods, self.bads, self.cond
+        else:
+            these, those, cond = self.bads, self.goods, lambda x: not self.cond(x)
+        if these:
+            return these.popleft()
+        else:
+            while 1: # exit on StopIteration
+                n = self.seq.next()
+                if cond(n):
+                    return n
+                else:
+                    those.append(n)
+
+def partition(condition, sequence):
+    """
+    Split a sequence into two subsequences, in single-pass and preserving order.
+
+    Arguments:
+
+    condition   a function; if condition is None, split true and false items
+    sequence    an iterable object
+
+    Return a pair of generators (seq_true, seq_false). The first one
+    builds a subsequence for which the condition holds, the second one
+    builds a subsequence for which the condition doesn't hold.
+
+    As the function works in single pass, it leads to build-up of both
+    subsequences even if only one of them is consumed.
+
+    It is similar to Data.List.partition in Haskell, partition-by in
+    Clojure, or running two complementary filters:
+
+       from itertools import ifilter, ifilterfalse
+       (ifilter(condition, sequence), ifilterfalse(condition, sequence))
+
+    >>> def odd(x): return x%2 != 0
+    >>> odds, evens = partition(odd, range(10))
+    >>> odds.next()
+    1
+    >>> odds.next()
+    3
+    >>> list(evens)
+    [0, 2, 4, 6, 8]
+    >>> list(odds)
+    [5, 7, 9]
+
+    """
+    cond = condition if condition else bool  # eval as bool if condition is None
+    ss = _SplitSeq(cond, sequence)
+    def goods():
+        while 1:
+            yield ss.getNext(getGood=True)
+    def bads():
+        while 1:
+            yield ss.getNext(getGood=False)
+    return goods(), bads()
+
+def _take(n, sequence):
+    """Take the first n elements of the sequence.
+    Return head and tail sequences.
+
+    >>> head, tail = _take(3, range(10))
+    >>> list(head), list(tail)
+    ([0, 1, 2], [3, 4, 5, 6, 7, 8, 9])
+
+    >>> head, tail = _take(13, range(10))
+    >>> list(head), list(tail)
+    ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [])
+    """
+    head = []
+    xs = iter(sequence)
+    try:
+        for i in xrange(n):
+            x = xs.next()
+            head.append(x)
+        return head, xs
+    except StopIteration:
+        return head, ()
+
+def chop(n, sequence, truncate=False):
+    """
+    Split a sequence into chunks of size n. Return an iterator over chunks.
+
+    Arguments:
+
+    n           chunk size
+    sequence    an iterable object
+    truncate    if True, truncate sequence length to the multiple of n;
+                if False, the size of last chunk may be less than n.
+
+    It is similar to partition and partition-all in Clojure.
+
+    >>> list(chop(3, range(10)))
+    [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
+    >>> list(chop(3, range(10), truncate=True))
+    [[0, 1, 2], [3, 4, 5], [6, 7, 8]]
+
+    """
+    assert n > 1, "chunk size is not positive"
+    def chopper():
+        tail = sequence
+        while tail:
+            head, tail = _take(n, tail)
+            if not truncate or len(head) == n:
+                yield head
+    return chopper()
+
+if __name__ == "__main__":
+    import doctest
+    doctest.testmod()

File splitBy.py

-"""
-This module provides splitBy function which splits a sequence into two
-subsequences, in single pass and preserving order.
-
-Some tests:
-
->>> def odd(x): return x%2 != 0
->>> odds, evens = splitBy(odd, range(10))
->>> odds.next()
-1
->>> odds.next()
-3
->>> list(evens)
-[0, 2, 4, 6, 8]
->>> list(odds)
-[5, 7, 9]
-
-"""
-
-from collections import deque
-
-__all__ = [ "splitBy" ]
-__author__ = "Sergey Astanin"
-__license__ = "BSD3"
-__version__ = "0.1"
-
-class SplitSeq:
-    """
-    Lazily process a sequence in single pass and split into two.
-
-    Computes both output sequences even if only one of them is consumed.
-    """
-    def __init__(self, condition, sequence):
-        self.cond = condition
-        self.goods = deque([])
-        self.bads = deque([])
-        self.seq = iter(sequence)
-    def getNext(self, getGood=True):
-        if getGood:
-            these, those, cond = self.goods, self.bads, self.cond
-        else:
-            these, those, cond = self.bads, self.goods, lambda x: not self.cond(x)
-        if these:
-            return these.popleft()
-        else:
-            while 1: # exit on StopIteration
-                n = self.seq.next()
-                if cond(n):
-                    return n
-                else:
-                    those.append(n)
-
-def splitBy(condition, sequence):
-    """
-    Split a sequence into two subsequences, in single-pass and preserving order.
-
-    Arguments:
-
-    condition   a function; if condition is None, split true and false items
-    sequence    an iterable object
-
-    Return a pair of generators (seq_true, seq_false). The first one
-    builds a subsequence for which the condition holds, the second one
-    builds a subsequence for which the condition doesn't hold.
-
-    As the function works in single pass, it leads to build-up of both
-    subsequences even if only one of them is consumed.
-    """
-    cond = condition if condition else bool  # evaluate as bool if condition == None
-    ss = SplitSeq(cond, sequence)
-    def goods():
-        while 1:
-            yield ss.getNext(getGood=True)
-    def bads():
-        while 1:
-            yield ss.getNext(getGood=False)
-    return goods(), bads()
-
-if __name__ == "__main__":
-    import doctest
-    doctest.testmod()