28 A collection of iteration utilities.
39 from .
import git_version
42 __author__ =
"Kipp Cannon <kipp.cannon@ligo.org>"
43 __version__ =
"git id %s" % git_version.id
44 __date__ = git_version.date
58 A generator for iterating over the elements of multiple sequences
59 simultaneously. With N sequences given as input, the generator
60 yields all possible distinct N-tuples that contain one element from
61 each of the input sequences.
65 >>> x = MultiIter([0, 1, 2], [10, 11])
67 [(0, 10), (1, 10), (2, 10), (0, 11), (1, 11), (2, 11)]
69 The elements in each output tuple are in the order of the input
70 sequences, and the left-most input sequence is iterated over first.
72 Internally, the input sequences themselves are each iterated over
73 only once, so it is safe to pass generators as arguments. Also,
74 this generator is significantly faster if the longest input
75 sequence is given as the first argument. For example, this code
77 >>> lengths = range(1, 12)
78 >>> for x in MultiIter(*map(range, lengths)):
82 runs approximately 5 times faster if the lengths list is reversed.
84 if len(sequences) > 1:
91 head = tuple((x,)
for x
in sequences[0])
96 for t
in sequences[0]:
102 A generator for iterating over all choices of n elements from the
103 input sequence vals. In each result returned, the original order
104 of the values is preserved.
108 >>> x = choices(["a", "b", "c"], 2)
110 [('a', 'b'), ('a', 'c'), ('b', 'c')]
112 The order of combinations in the output sequence is always the
113 same, so if choices() is called twice with two different sequences
114 of the same length the first combination in each of the two output
115 sequences will contain elements from the same positions in the two
116 different input sequences, and so on for each subsequent pair of
121 >>> x = choices(["a", "b", "c"], 2)
122 >>> y = choices(["1", "2", "3"], 2)
124 [(('a', 'b'), ('1', '2')), (('a', 'c'), ('1', '3')), (('b', 'c'), ('2', '3'))]
126 Furthermore, the order of combinations in the output sequence is
127 such that if the input list has n elements, and one constructs the
128 combinations choices(input, m), then each combination in
129 choices(input, n-m).reverse() contains the elements discarded in
130 forming the corresponding combination in the former.
134 >>> x = ["a", "b", "c", "d", "e"]
135 >>> X = list(choices(x, 2))
136 >>> Y = list(choices(x, len(x) - 2))
139 [(('a', 'b'), ('c', 'd', 'e')), (('a', 'c'), ('b', 'd', 'e')), (('a', 'd'), ('b', 'c', 'e')), (('a', 'e'), ('b', 'c', 'd')), (('b', 'c'), ('a', 'd', 'e')), (('b', 'd'), ('a', 'c', 'e')), (('b', 'e'), ('a', 'c', 'd')), (('c', 'd'), ('a', 'b', 'e')), (('c', 'e'), ('a', 'b', 'd')), (('d', 'e'), ('a', 'b', 'c'))]
141 NOTE: this generator is identical to the itertools.combinations()
142 generator in Python's standard library. This routine was written
143 before Python provided that functionality, and is now only
144 preserved for two reasons. The first is to maintain this API for
145 existing codes that were written before the standard library
146 provided the capability. But the second reason is because the
147 Python standard library doesn't make the guarantees that we do,
148 here, about the order of the results. Specifically, there is no
149 guarantee that itertools.combinations() is repeatable, nor is there
150 a statement on how to obtain the inverse of the sequence as
151 described above. At the time of writing the order in which results
152 are produced is identical to this generator and in all use cases
153 they are exact substitutes for each other, and new code should use
154 itertools.combinations(). Be careful making assumptions about the
155 order of the results, add safety checks where needed, and if a
156 problem arises this generator can be used as a fall-back.
162 for i, v
in enumerate(vals[:-n]):
164 for c
in choices(vals[i+1:], n):
178 Yield the unique items of an iterable, preserving order.
179 http://mail.python.org/pipermail/tutor/2002-March/012930.html
183 >>> x = uniq([0, 0, 2, 6, 2, 0, 5])
189 if e
not in temp_dict:
190 yield temp_dict.setdefault(e, e)
195 Yield the non-unique items of an iterable, preserving order. If an
196 item occurs N > 0 times in the input sequence, it will occur N-1
197 times in the output sequence.
201 >>> x = nonuniq([0, 0, 2, 6, 2, 0, 5])
209 temp_dict.setdefault(e, e)
212 def flatten(sequence, levels = 1):
215 >>> nested = [[1,2], [[3]]]
216 >>> list(flatten(nested))
224 for y
in flatten(x, levels - 1):
239 Like Python's filter() builtin, but modifies the sequence in place.
243 >>> l = list(range(10))
244 >>> inplace_filter(lambda x: x > 5, l)
248 Performance considerations: the function iterates over the
249 sequence, shuffling surviving members down and deleting whatever
250 top part of the sequence is left empty at the end, so sequences
251 whose surviving members are predominantly at the bottom will be
255 for source
in range(len(sequence)):
256 if func(sequence[source]):
257 sequence[target] = sequence[source]
259 del sequence[target:]
271 def inorder(*iterables, **kwargs):
273 A generator that yields the values from several ordered iterables
279 >>> y = [1.5, 2.5, 3.5, 4.5]
280 >>> z = [1.75, 2.25, 3.75, 4.25]
281 >>> list(inorder(x, y, z))
282 [0, 1, 1.5, 1.75, 2, 2.25, 2.5, 3, 3.5, 3.75, 4.25, 4.5]
283 >>> list(inorder(x, y, z, key=lambda x: x * x))
284 [0, 1, 1.5, 1.75, 2, 2.25, 2.5, 3, 3.5, 3.75, 4.25, 4.5]
286 >>> x.sort(key=lambda x: abs(x-3))
287 >>> y.sort(key=lambda x: abs(x-3))
288 >>> z.sort(key=lambda x: abs(x-3))
289 >>> list(inorder(x, y, z, key=lambda x: abs(x - 3)))
290 [3, 2.5, 3.5, 2.25, 3.75, 2, 1.75, 4.25, 1.5, 4.5, 1, 0]
293 >>> y = [4.5, 3.5, 2.5, 1.5]
294 >>> z = [4.25, 3.75, 2.25, 1.75]
295 >>> list(inorder(x, y, z, reverse = True))
296 [4.5, 4.25, 3.75, 3.5, 3, 2.5, 2.25, 2, 1.75, 1.5, 1, 0]
297 >>> list(inorder(x, y, z, key = lambda x: -x))
298 [4.5, 4.25, 3.75, 3.5, 3, 2.5, 2.25, 2, 1.75, 1.5, 1, 0]
300 NOTE: this function will never reverse the order of elements in
301 the input iterables. If the reverse keyword argument is False (the
302 default) then the input sequences must yield elements in increasing
303 order, likewise if the keyword argument is True then the input
304 sequences must yield elements in decreasing order. Failure to
305 adhere to this yields undefined results, and for performance
306 reasons no check is performed to validate the element order in the
309 reverse = kwargs.pop(
"reverse",
False)
310 keyfunc = kwargs.pop(
"key",
lambda x: x)
312 raise TypeError(
"invalid keyword argument '%s'" % list(kwargs.keys())[0])
314 for iterable
in iterables:
315 next_ = functools.partial(next, iter(iterable))
318 nextvals[next_] = keyfunc(nextval), nextval, next_
319 except StopIteration:
325 select =
lambda seq: max(seq, key =
lambda elem: elem[0])
327 select =
lambda seq: min(seq, key =
lambda elem: elem[0])
328 values = functools.partial(six.itervalues, nextvals)
329 if len(nextvals) > 1:
331 _, val, next_ = select(values())
335 nextvals[next_] = keyfunc(nextval), nextval, next_
336 except StopIteration:
338 if len(nextvals) < 2:
343 (_, val, next_), = values()
348 except StopIteration:
363 Yields integers in the range [lo, hi) where 0 <= lo < hi. Each
364 return value is a two-element tuple. The first element is the
365 random integer, the second is the natural logarithm of the
366 probability with which that integer will be chosen.
368 The CDF for the distribution from which the integers are drawn goes
369 as [integer]^{n}, where n > 0. Specifically, it's
371 CDF(x) = (x^{n} - lo^{n}) / (hi^{n} - lo^{n})
373 n = 1 yields a uniform distribution; n > 1 favours larger
374 integers, n < 1 favours smaller integers.
377 raise ValueError(
"require 0 <= lo < hi: lo = %d, hi = %d" % (lo, hi))
379 raise ValueError(
"n <= 0: %g" % n)
383 lnP = math.log(1. / (hi - lo))
385 raise ValueError(
"[lo, hi) domain error")
389 yield rnd(lo, hi), lnP
392 lnP = numpy.arange(lo, hi + 1, dtype =
"double")**n
396 lnP = tuple(numpy.log(lnP[1:] - lnP[:-1]))
397 if numpy.isinf(lnP).any():
398 raise ValueError(
"[lo, hi) domain error")
400 beta = lo**n / (hi**n - lo**n)
402 alpha = hi / (1. + beta)**n
406 index = int(flr(alpha * (rnd() + beta)**n))
410 yield index, lnP[index - lo]
def inorder(*iterables, **kwargs)
A generator that yields the values from several ordered iterables in order.
def inplace_filter(func, sequence)
Like Python's filter() builtin, but modifies the sequence in place.
def flatten(sequence, levels=1)
Example: nested = [[1,2], [[3]]] list(flatten(nested)) [1, 2, [3]].
def randindex(lo, hi, n=1.)
Yields integers in the range [lo, hi) where 0 <= lo < hi.
def nonuniq(iterable)
Yield the non-unique items of an iterable, preserving order.
def choices(vals, n)
A generator for iterating over all choices of n elements from the input sequence vals.
def uniq(iterable)
Yield the unique items of an iterable, preserving order.
def MultiIter(*sequences)
A generator for iterating over the elements of multiple sequences simultaneously.