Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2"""
3@file
4@brief Simple *mapper* and *reducer* implemented in :epkg:`Python`
5"""
6from itertools import groupby
9def mapper(fct, gen):
10 """
11 Applies function *fct* to a generator.
13 @param fct function
14 @param gen generator
15 @return generator
17 .. exref::
18 :title: mapper
19 :tag: progfonc
21 .. runpython::
22 :showcode:
24 from sparkouille.fctmr import mapper
25 res = mapper(lambda x: x + 1, [4, 5])
26 print(list(res))
28 .. faqref::
29 :title: Différence entre un itérateur et un générateur ?
30 :tag: faqprogfonc
32 Un :epkg:`itérateur` et un :epkg:`générateur` produisent
33 tous deux des éléments issus d'un ensemble. La différence
34 vient du fait que qu'un :epkg:`itérateur` parcourt les
35 éléments d'un ensemble qui existe en mémoire. Un :epkg:`générateur`
36 produit ou calcule des éléments d'un ensemble qui n'existe
37 pas en mémoire. Par conséquent, parcourir deux fois un ensemble
38 avec un itérateur a un coût en :math:`O(n)` alors que pour
39 un générateur, il faut ajouter le calcul de l'élément une
40 seconde fois. Le coût est imprévisible et parfois il est
41 préférable de :epkg:`cacher` les éléments pour le parcourir
42 plusieurs fois : cela revient à transformer un :epkg:`générateur`
43 en :epkg:`itérateur`. Un générateur est souvent défini comme suit
44 en :epkg:`Python` :
46 .. runpython::
47 :showcode:
49 def generate(some_iterator):
50 for el in some_iterator:
51 yield el
53 g = generate([4, 5])
54 print(list(g))
55 print(g.__class__.__name__)
56 """
57 return map(fct, gen)
60def take(gen, count=5, skip=0):
61 """
62 Skips and takes elements from a generator.
64 @param gen generator
65 @param count number of elements to consider
66 @param skip skip the first elements
67 @return generator
69 .. exref::
70 :title: take
71 :tag: progfonc
73 .. runpython::
74 :showcode:
76 from sparkouille.fctmr import take
77 res = take([4, 5, 6, 7, 8, 9], 2, 2)
78 print(list(res))
79 """
80 took = 0
81 for i, el in enumerate(gen):
82 if i < skip:
83 continue
84 if took >= count:
85 break
86 yield el
87 took += 1
90def ffilter(fct, gen):
91 """
92 Filters out elements from a generator.
94 @param fct function
95 @param gen generator
96 @return generator
98 .. exref::
99 :title: filter
100 :tag: progfonc
102 .. runpython::
103 :showcode:
105 from sparkouille.fctmr import ffilter
106 res = ffilter(lambda x: x % 2 == 0, [4, 5])
107 print(list(res))
108 """
109 return filter(fct, gen)
112def reducer(fctkey, gen, asiter=True, sort=True):
113 """
114 Implements a reducer.
116 @param fctkey function which returns the key
117 @param gen generator
118 @param asiter returns an iterator on each element of the group
119 of the group itself
120 @param sort sort elements by key before grouping
121 @return generator
123 .. exref::
124 :title: reducer
125 :tag: progfonc
127 .. runpython::
128 :showcode:
130 from sparkouille.fctmr import reducer
131 res = reducer(lambda x: x[0], [
132 ('a', 1), ('b', 2), ('a', 3)], asiter=False)
133 print(list(res))
134 """
135 if sort:
136 new_gen = map(lambda x: x[1], sorted(
137 map(lambda el: (fctkey(el), el), gen)))
138 gr = groupby(new_gen, fctkey)
139 else:
140 gr = groupby(gen, fctkey)
141 if asiter:
142 # Cannot return gr. Python is confused when yield and return
143 # are used in the same function.
144 for _ in gr:
145 yield _
146 else:
147 for key, it in gr:
148 yield key, list(it)
151def combiner(fctkey1, gen1, fctkey2, gen2, how='inner'):
152 """
153 Joins (or combines) two generators.
154 The function is written based on two reducers.
155 The function is more efficient if the groups
156 of the second ensemble *gen2* are shorter
157 as each of them will be held in memory.
159 @param fctkey1 function which returns the key for gen1
160 @param gen1 generator for the first element
161 @param fctkey2 function which returns the key for gen2
162 @param gen2 generator for the second element
163 @param how *inner*, *outer*, *left*, right*
164 @return generator
166 .. exref::
167 :title: combiner or join
168 :tag: progfonc
170 .. runpython::
171 :showcode:
173 from sparkouille.fctmr import combiner
175 def c0(el):
176 return el[0]
178 ens1 = [('a', 1), ('b', 2), ('a', 3)]
179 ens2 = [('a', 10), ('b', 20), ('a', 30)]
180 res = combiner(c0, ens1, c0, ens2)
181 print(list(res))
182 """
183 gr1 = reducer(fctkey1, gen1, asiter=True, sort=True)
184 gr2 = reducer(fctkey2, gen2, asiter=False, sort=True)
186 def fetch_next(it):
187 "local function"
188 try:
189 return next(it)
190 except StopIteration:
191 return None, None
193 k1, g1 = fetch_next(gr1)
194 k2, g2 = fetch_next(gr2)
195 while k1 is not None or k2 is not None:
196 if k1 is None or (k2 is not None and k2 < k1):
197 if how in ('outer', 'right'):
198 for el in g2:
199 yield None, el
200 k2, g2 = fetch_next(gr2)
201 else:
202 break
203 elif k2 is None or k1 < k2:
204 if how in ('outer', 'left'):
205 for el in g1:
206 yield el, None
207 k1, g1 = fetch_next(gr1)
208 else:
209 break
210 elif k1 == k2:
211 for el1 in g1:
212 for el2 in g2:
213 yield el1, el2
214 k1, g1 = fetch_next(gr1)
215 k2, g2 = fetch_next(gr2)