5757
5858def _process_keys (left , right ):
5959 """
60- Helper function to compose cycler keys
60+ Helper function to compose cycler keys.
6161
6262 Parameters
6363 ----------
6464 left, right : iterable of dictionaries or None
65- The cyclers to be composed
65+ The cyclers to be composed.
66+
6667 Returns
6768 -------
6869 keys : set
69- The keys in the composition of the two cyclers
70+ The keys in the composition of the two cyclers.
7071 """
7172 l_peek = next (iter (left )) if left is not None else {}
7273 r_peek = next (iter (right )) if right is not None else {}
@@ -77,9 +78,38 @@ def _process_keys(left, right):
7778 return l_key | r_key
7879
7980
81+ def concat (left , right ):
82+ r"""
83+ Concatenate `Cycler`\s, as if chained using `itertools.chain`.
84+
85+ The keys must match exactly.
86+
87+ Examples
88+ --------
89+ >>> num = cycler('a', range(3))
90+ >>> let = cycler('a', 'abc')
91+ >>> num.concat(let)
92+ cycler('a', [0, 1, 2, 'a', 'b', 'c'])
93+
94+ Returns
95+ -------
96+ `Cycler`
97+ The concatenated cycler.
98+ """
99+ if left .keys != right .keys :
100+ raise ValueError ("Keys do not match:\n "
101+ "\t Intersection: {both!r}\n "
102+ "\t Disjoint: {just_one!r}" .format (
103+ both = left .keys & right .keys ,
104+ just_one = left .keys ^ right .keys ))
105+ _l = left .by_key ()
106+ _r = right .by_key ()
107+ return reduce (add , (_cycler (k , _l [k ] + _r [k ]) for k in left .keys ))
108+
109+
80110class Cycler (object ):
81111 """
82- Composable cycles
112+ Composable cycles.
83113
84114 This class has compositions methods:
85115
@@ -95,25 +125,22 @@ class Cycler(object):
95125 ``*=``
96126 in-place ``*``
97127
98- and supports basic slicing via ``[]``
128+ and supports basic slicing via ``[]``.
99129
100130 Parameters
101131 ----------
102- left : Cycler or None
103- The 'left' cycler
104-
105- right : Cycler or None
106- The 'right' cycler
107-
132+ left, right : Cycler or None
133+ The 'left' and 'right' cyclers.
108134 op : func or None
109135 Function which composes the 'left' and 'right' cyclers.
110-
111136 """
137+
112138 def __call__ (self ):
113139 return cycle (self )
114140
115141 def __init__ (self , left , right = None , op = None ):
116- """Semi-private init
142+ """
143+ Semi-private init.
117144
118145 Do not use this directly, use `cycler` function instead.
119146 """
@@ -143,9 +170,7 @@ def __contains__(self, k):
143170
144171 @property
145172 def keys (self ):
146- """
147- The keys this Cycler knows about
148- """
173+ """The keys this Cycler knows about."""
149174 return set (self ._keys )
150175
151176 def change_key (self , old , new ):
@@ -156,7 +181,6 @@ def change_key(self, old, new):
156181 Does nothing if the old key is the same as the new key.
157182 Raises a ValueError if the new key is already a key.
158183 Raises a KeyError if the old key isn't a key.
159-
160184 """
161185 if old == new :
162186 return
@@ -183,16 +207,6 @@ def change_key(self, old, new):
183207 # iteration.
184208 self ._left = [{new : entry [old ]} for entry in self ._left ]
185209
186- def _compose (self ):
187- """
188- Compose the 'left' and 'right' components of this cycle
189- """
190- for a , b in self ._op (self ._left , self ._right ):
191- out = dict ()
192- out .update (a )
193- out .update (b )
194- yield out
195-
196210 @classmethod
197211 def _from_iter (cls , label , itr ):
198212 """
@@ -210,8 +224,8 @@ def _from_iter(cls, label, itr):
210224
211225 Returns
212226 -------
213- cycler : Cycler
214- New 'base' `Cycler`
227+ ` Cycler`
228+ New 'base' cycler.
215229 """
216230 ret = cls (None )
217231 ret ._left = list ({label : v } for v in itr )
@@ -228,18 +242,22 @@ def __getitem__(self, key):
228242
229243 def __iter__ (self ):
230244 if self ._right is None :
231- return iter (dict (l ) for l in self ._left )
232-
233- return self ._compose ()
245+ for l in self ._left :
246+ yield dict (l )
247+ else :
248+ for a , b in self ._op (self ._left , self ._right ):
249+ out = dict ()
250+ out .update (a )
251+ out .update (b )
252+ yield out
234253
235254 def __add__ (self , other ):
236255 """
237- Pair-wise combine two equal length cycles (zip)
256+ Pair-wise combine two equal length cyclers (zip).
238257
239258 Parameters
240259 ----------
241260 other : Cycler
242- The second Cycler
243261 """
244262 if len (self ) != len (other ):
245263 raise ValueError ("Can only add equal length cycles, "
@@ -248,13 +266,12 @@ def __add__(self, other):
248266
249267 def __mul__ (self , other ):
250268 """
251- Outer product of two cycles (`itertools.product`) or integer
269+ Outer product of two cyclers (`itertools.product`) or integer
252270 multiplication.
253271
254272 Parameters
255273 ----------
256274 other : Cycler or int
257- The second Cycler or integer
258275 """
259276 if isinstance (other , Cycler ):
260277 return Cycler (self , other , product )
@@ -277,12 +294,11 @@ def __len__(self):
277294
278295 def __iadd__ (self , other ):
279296 """
280- In-place pair-wise combine two equal length cycles (zip)
297+ In-place pair-wise combine two equal length cyclers (zip).
281298
282299 Parameters
283300 ----------
284301 other : Cycler
285- The second Cycler
286302 """
287303 if not isinstance (other , Cycler ):
288304 raise TypeError ("Cannot += with a non-Cycler object" )
@@ -296,12 +312,11 @@ def __iadd__(self, other):
296312
297313 def __imul__ (self , other ):
298314 """
299- In-place outer product of two cycles (`itertools.product`)
315+ In-place outer product of two cyclers (`itertools.product`).
300316
301317 Parameters
302318 ----------
303319 other : Cycler
304- The second Cycler
305320 """
306321 if not isinstance (other , Cycler ):
307322 raise TypeError ("Cannot *= with a non-Cycler object" )
@@ -314,14 +329,10 @@ def __imul__(self, other):
314329 return self
315330
316331 def __eq__ (self , other ):
317- """
318- Check equality
319- """
320332 if len (self ) != len (other ):
321333 return False
322334 if self .keys ^ other .keys :
323335 return False
324-
325336 return all (a == b for a , b in zip (self , other ))
326337
327338 def __ne__ (self , other ):
@@ -355,7 +366,8 @@ def _repr_html_(self):
355366 return output
356367
357368 def by_key (self ):
358- """Values by key
369+ """
370+ Values by key.
359371
360372 This returns the transposed values of the cycler. Iterating
361373 over a `Cycler` yields dicts with a single value for each key,
@@ -386,90 +398,22 @@ def by_key(self):
386398 _transpose = by_key
387399
388400 def simplify (self ):
389- """Simplify the Cycler
390-
391- Returned as a composition using only sums (no multiplications)
401+ """
402+ Simplify the cycler into a sum (but no products) of cyclers.
392403
393404 Returns
394405 -------
395406 simple : Cycler
396- An equivalent cycler using only summation """
407+ """
397408 # TODO: sort out if it is worth the effort to make sure this is
398409 # balanced. Currently it is is
399410 # (((a + b) + c) + d) vs
400411 # ((a + b) + (c + d))
401412 # I would believe that there is some performance implications
402-
403413 trans = self .by_key ()
404414 return reduce (add , (_cycler (k , v ) for k , v in trans .items ()))
405415
406- def concat (self , other ):
407- """Concatenate this cycler and an other.
408-
409- The keys must match exactly.
410-
411- This returns a single Cycler which is equivalent to
412- `itertools.chain(self, other)`
413-
414- Examples
415- --------
416-
417- >>> num = cycler('a', range(3))
418- >>> let = cycler('a', 'abc')
419- >>> num.concat(let)
420- cycler('a', [0, 1, 2, 'a', 'b', 'c'])
421-
422- Parameters
423- ----------
424- other : `Cycler`
425- The `Cycler` to concatenate to this one.
426-
427- Returns
428- -------
429- ret : `Cycler`
430- The concatenated `Cycler`
431- """
432- return concat (self , other )
433-
434-
435- def concat (left , right ):
436- """Concatenate two cyclers.
437-
438- The keys must match exactly.
439-
440- This returns a single Cycler which is equivalent to
441- `itertools.chain(left, right)`
442-
443- Examples
444- --------
445-
446- >>> num = cycler('a', range(3))
447- >>> let = cycler('a', 'abc')
448- >>> num.concat(let)
449- cycler('a', [0, 1, 2, 'a', 'b', 'c'])
450-
451- Parameters
452- ----------
453- left, right : `Cycler`
454- The two `Cycler` instances to concatenate
455-
456- Returns
457- -------
458- ret : `Cycler`
459- The concatenated `Cycler`
460- """
461- if left .keys != right .keys :
462- msg = '\n \t ' .join (["Keys do not match:" ,
463- "Intersection: {both!r}" ,
464- "Disjoint: {just_one!r}" ]).format (
465- both = left .keys & right .keys ,
466- just_one = left .keys ^ right .keys )
467-
468- raise ValueError (msg )
469-
470- _l = left .by_key ()
471- _r = right .by_key ()
472- return reduce (add , (_cycler (k , _l [k ] + _r [k ]) for k in left .keys ))
416+ concat = concat
473417
474418
475419def cycler (* args , ** kwargs ):
@@ -495,12 +439,10 @@ def cycler(*args, **kwargs):
495439 ----------
496440 arg : Cycler
497441 Copy constructor for Cycler (does a shallow copy of iterables).
498-
499442 label : name
500443 The property key. In the 2-arg form of the function,
501444 the label can be any hashable object. In the keyword argument
502445 form of the function, it must be a valid python identifier.
503-
504446 itr : iterable
505447 Finite length iterable of the property values.
506448 Can be a single-property `Cycler` that would
@@ -535,14 +477,12 @@ def cycler(*args, **kwargs):
535477
536478def _cycler (label , itr ):
537479 """
538- Create a new `Cycler` object from a property name and
539- iterable of values.
480+ Create a new `Cycler` object from a property name and iterable of values.
540481
541482 Parameters
542483 ----------
543484 label : hashable
544485 The property key.
545-
546486 itr : iterable
547487 Finite length iterable of the property values.
548488
0 commit comments