15
15
http://rightfootin.blogspot.fi/2006/09/more-on-python-flatten.html
16
16
"""
17
17
18
- __all__ = ["scanl" , "scanr" , "scanl1" , "scanr1" ,
18
+ __all__ = ["make_heads" ,
19
+ "scanl" , "scanr" , "scanl1" , "scanr1" ,
19
20
"foldl" , "foldr" , "reducel" , "reducer" ,
20
- "flatmap" , "mapr" , "zipr" , "uniqify" , "uniq" ,
21
+ "mapr" , "zipr" , "map_longest" , "mapr_longest" , "zipr_longest" ,
22
+ "flatmap" , "uniqify" , "uniq" ,
21
23
"take" , "drop" , "split_at" , "unpack" ,
22
24
"tail" , "first" , "second" , "nth" , "last" ,
23
25
"flatten" , "flatten1" , "flatten_in" ,
24
26
"iterate" , "iterate1" ]
25
27
26
- from itertools import tee , islice
28
+ from functools import partial
29
+ from itertools import tee , islice , zip_longest
27
30
from collections import deque
28
31
from inspect import isgenerator
29
32
33
+ def make_heads (longest = False , fillvalue = None ):
34
+ """Create a function returning the head elements from any number of iterators.
35
+
36
+ The return value is a function, ``heads``:
37
+
38
+ ``heads(*iterators)`` -> ``tuple`` or ``StopIteration``.
39
+
40
+ When the resulting function is called on some iterators, it returns the head
41
+ element from each, packed into a tuple. When elements run out, it **returns**
42
+ (does **not** raise!) ``StopIteration``.
43
+
44
+ The parameter ``longest`` controls when we consider elements to run out:
45
+
46
+ - If ``longest=False``, iteration stops when the shortest input runs out.
47
+
48
+ - If ``longest=True``, iteration stops only when all inputs have run out.
49
+ Any missing elements (after the ends of shorter inputs) are filled with
50
+ the optional ``fillvalue``, which defaults to ``None``.
51
+
52
+ This function is a low-level utility for defining higher-order functions
53
+ that may take multiple iterables, and terminate either on the shortest
54
+ or on the longest input.
55
+ """
56
+ if longest :
57
+ def heads (iterators ):
58
+ hs = []
59
+ nempty = 0
60
+ for it in iterators :
61
+ try :
62
+ h = next (it )
63
+ except StopIteration : # this sequence has run out
64
+ h = fillvalue
65
+ nempty += 1 # may legitimately contain None so must count
66
+ hs .append (h )
67
+ if nempty == len (iterators ):
68
+ return StopIteration
69
+ return tuple (hs )
70
+ else :
71
+ def heads (iterators ):
72
+ hs = []
73
+ for it in iterators :
74
+ try :
75
+ h = next (it )
76
+ except StopIteration : # shortest sequence ran out
77
+ return StopIteration
78
+ hs .append (h )
79
+ return tuple (hs )
80
+ return heads
81
+
30
82
# require at least one iterable to make this work seamlessly with curry.
31
83
def scanl (proc , init , iterable0 , * iterables , longest = False , fillvalue = None ):
32
84
"""Scan (accumulate), optionally with multiple input iterables.
@@ -51,31 +103,8 @@ def scanl(proc, init, iterable0, *iterables, longest=False, fillvalue=None):
51
103
yield proc(*elts, acc) # if this was legal syntax
52
104
"""
53
105
iterables = (iterable0 ,) + iterables
54
- if not longest : # terminate on shortest input
55
- def heads (its ):
56
- hs = []
57
- for it in its :
58
- try :
59
- h = next (it )
60
- except StopIteration : # shortest sequence ran out
61
- return StopIteration
62
- hs .append (h )
63
- return tuple (hs )
64
- else : # terminate on longest input
65
- def heads (its ):
66
- hs = []
67
- nempty = 0
68
- for it in its :
69
- try :
70
- h = next (it )
71
- except StopIteration : # this sequence has run out
72
- h = fillvalue
73
- nempty += 1 # may legitimately contain None so must count
74
- hs .append (h )
75
- if nempty == len (its ):
76
- return StopIteration
77
- return tuple (hs )
78
106
iters = tuple (iter (x ) for x in iterables )
107
+ heads = make_heads (longest = longest , fillvalue = fillvalue )
79
108
acc = init
80
109
while True :
81
110
yield acc
@@ -151,6 +180,36 @@ def reducer(proc, sequence, init=None):
151
180
"""
152
181
return reducel (proc , reversed (sequence ), init )
153
182
183
+ def mapr (func , * sequences ):
184
+ """Like map, but walk each sequence from the right."""
185
+ return map (func , * (reversed (s ) for s in sequences ))
186
+
187
+ def zipr (* sequences ):
188
+ """Like zip, but walk each sequence from the right."""
189
+ return zip (* (reversed (s ) for s in sequences ))
190
+
191
+ def map_longest (func , * iterables , fillvalue = None ):
192
+ """Like map, but terminate on the longest input.
193
+
194
+ In the input to ``func``, missing elements (after end of shorter inputs)
195
+ are replaced by ``fillvalue``, which defaults to ``None``.
196
+ """
197
+ iters = tuple (iter (x ) for x in iterables )
198
+ heads = make_heads (longest = True )
199
+ while True :
200
+ hs = heads (iters )
201
+ if hs is StopIteration :
202
+ break
203
+ yield func (* hs )
204
+
205
+ def mapr_longest (func , * sequences , fillvalue = None ):
206
+ """Like map_longest, but walk each sequence from the right."""
207
+ return map_longest (func , * (reversed (s ) for s in sequences ), fillvalue = fillvalue )
208
+
209
+ def zipr_longest (* sequences , fillvalue = None ):
210
+ """Like itertools.zip_longest, but walk each sequence from the right."""
211
+ return zip_longest (* (reversed (s ) for s in sequences ), fillvalue = fillvalue )
212
+
154
213
def flatmap (f , iterable0 , * iterables ):
155
214
"""Map, then concatenate results.
156
215
@@ -190,14 +249,6 @@ def sum_and_diff(a, b):
190
249
for x in xs :
191
250
yield x
192
251
193
- def mapr (func , * sequences ):
194
- """Like map, but walk each sequence from the right."""
195
- return map (func , * (reversed (s ) for s in sequences ))
196
-
197
- def zipr (* sequences ):
198
- """Like zip, but walk each sequence from the right."""
199
- return zip (* (reversed (s ) for s in sequences ))
200
-
201
252
def uniqify (iterable , key = None ):
202
253
"""Skip duplicates in iterable.
203
254
@@ -480,7 +531,6 @@ def iterate(f, *args):
480
531
481
532
def test ():
482
533
from operator import add , mul , itemgetter
483
- from functools import partial
484
534
from unpythonic .fun import curry , composer , composerc , composel , to1st , rotate , identity
485
535
from unpythonic .llist import cons , nil , ll , lreverse
486
536
@@ -573,6 +623,17 @@ def noneadd(a, b):
573
623
return a + b
574
624
assert curry (mymap_longest , noneadd , ll (1 , 2 , 3 ), ll (2 , 4 )) == ll (3 , 6 , None )
575
625
626
+ # Adding the missing batteries to the algebra of map and zip.
627
+ # Note Python's (and Racket's) map is like Haskell's zipWith, but for n inputs.
628
+ assert tuple (map (add , (1 , 2 ), (3 , 4 ))) == (4 , 6 ) # builtin
629
+ assert tuple (mapr (add , (1 , 2 ), (3 , 4 ))) == (6 , 4 )
630
+ assert tuple (zip ((1 , 2 , 3 ), (4 , 5 , 6 ), (7 , 8 ))) == ((1 , 4 , 7 ), (2 , 5 , 8 )) # builtin
631
+ assert tuple (zipr ((1 , 2 , 3 ), (4 , 5 , 6 ), (7 , 8 ))) == ((3 , 6 , 8 ), (2 , 5 , 7 ))
632
+ assert tuple (map_longest (noneadd , (1 , 2 , 3 ), (2 , 4 ))) == (3 , 6 , None )
633
+ assert tuple (mapr_longest (noneadd , (1 , 2 , 3 ), (2 , 4 ))) == (7 , 4 , None )
634
+ assert tuple (zip_longest ((1 , 2 , 3 ), (2 , 4 ))) == ((1 , 2 ), (2 , 4 ), (3 , None )) # itertools
635
+ assert tuple (zipr_longest ((1 , 2 , 3 ), (2 , 4 ))) == ((3 , 4 ), (2 , 2 ), (1 , None ))
636
+
576
637
reverse_one = curry (foldl , cons , nil )
577
638
assert reverse_one (ll (1 , 2 , 3 )) == ll (3 , 2 , 1 )
578
639
@@ -635,8 +696,6 @@ def sum_and_diff(a, b):
635
696
assert a == tuple (range (3 ))
636
697
assert b == ()
637
698
638
- assert tuple (zipr ((1 , 2 , 3 ), (4 , 5 , 6 ), (7 , 8 ))) == ((3 , 6 , 8 ), (2 , 5 , 7 ))
639
-
640
699
@rotate (1 )
641
700
def zipper (acc , * rest ): # so that we can use the *args syntax to declare this
642
701
return acc + (rest ,) # even though the input is (e1, ..., en, acc).
0 commit comments