A generator for cycling over a set of values. This recipe shows a generator-based approach for creating repeating alternators as well as several other approaches.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | #! /usr/bin/env python
from __future__ import generators
import unittest
def repeating_alternator(*args):
"""Return a repeating alternator for args."""
while args:
for a in args:
yield a
def finite_alternator(length, *args):
"""Return a finite repeating alternator for args."""
c = 0
while args and c < length:
for a in args:
yield a
c += 1
if c >= length:
break
def finite_iterator(n, iterator):
"""Return an iterator that will stop after generating n elements."""
for j in xrange(n):
yield iterator.next()
def weave_sets(set_n, set_m):
"""Return an iterator for set_n and set_m that stops when set_n is
exhausted. If set_n is larger than set_m, cycle over set_m.
"""
m = len(set_m)
if not m:
raise ValueError("Set to be cycled on cannot be empty.")
for i in range(len(set_n)):
yield set_n[i], set_m[i%m]
# In Python 2.3, this can be simplified to:
#for index, item in enumerate(set_n):
# yield item, set_m[index%m]
def weave_items(iterator, alternator, weave_item):
"""Return an iterator for iterator, applying weave_item to each item with
its pair in alternator.
"""
for item in iterator:
yield weave_item(item, alternator.next())
def color_item(item, color):
template = "<%(color)s>%(item)s</%(color)s>"
return template % locals()
class test(unittest.TestCase):
def setUp(self):
self.colors = ("red", "orange", "yellow", "green", "blue", "indigo",
"violet")
self.expected = ['<red>0</red>',
'<orange>1</orange>',
'<yellow>2</yellow>',
'<green>3</green>',
'<blue>4</blue>',
'<indigo>5</indigo>',
'<violet>6</violet>',
'<red>7</red>',
'<orange>8</orange>',
'<yellow>9</yellow>']
self.length = 10
def test_weave_sets(self):
colors = self.colors
length = self.length
expected = self.expected
generated = [color_item(x, y) for x, y in weave_sets(range(length), colors)]
self.assertEquals(expected, generated)
def test_zip(self):
colors = self.colors
length = self.length
expected = self.expected
iterator = range(length)
alternator = repeating_alternator(*colors)
weaved = zip(iterator, alternator)
generated = [color_item(x, y) for x, y in weaved]
self.assertEquals(expected, generated)
def test_list_comprehension(self):
colors = self.colors
length = self.length
expected = self.expected
iterator = range(length)
alternator = repeating_alternator(*colors)
generated = [color_item(x, alternator.next()) for x in iterator]
self.assertEquals(expected, generated)
def test_map_finite_alternator(self):
colors = self.colors
length = self.length
expected = self.expected
iterator = range(length)
alternator = finite_alternator(length, *colors)
generated = map(color_item, iterator, alternator)
self.assertEquals(expected, generated)
def test_map_finite_iterator(self):
colors = self.colors
length = self.length
expected = self.expected
iterator = range(length)
alternator = repeating_alternator(*colors)
alternator = finite_iterator(length, alternator)
generated = map(color_item, iterator, alternator)
self.assertEquals(expected, generated)
def test_weave_items(self):
colors = self.colors
length = self.length
expected = self.expected
iterator = range(length)
alternator = repeating_alternator(*colors)
generated = [x for x in weave_items(iterator, alternator, color_item)]
self.assertEquals(expected, generated)
def test_empty(self):
r = repeating_alternator()
self.assertRaises(StopIteration, r.next)
if __name__ == "__main__":
unittest.main()
|
According to Tim Peters, the Icon language calls this repeated alternation:
http://mail.python.org/pipermail/python-list/2002-July/113144.html
This recipe was inspired by a post to comp.lang.python:
http://mail.python.org/pipermail/python-list/2002-July/113071.html
When I read this post, I recalled that I occasionally want to cycle over a set of values. And so I began looking for a general solution.
I posted my initial general solution here and got some helpful feedback from several Pythonistas without which this recipe would not be in its current form:
http://mail.python.org/pipermail/python-list/2002-July/113109.html
Perhaps the most common problem where this solution would be helpful is applying class="[odd|even]" to a sequence of HTML table rows. Of course, you can apply the solutions in this recipe to any set of values you want to cycle through as you weave these values to another finite set of values with an arbitrary function.
The code that shows this general approach most clearly, in my opinion, is demonstrated in test_weave_items() above.
As several people noted, you can use zip and map to achieve similar effects. zip will weave two sequences together, but you have to apply the function separately; while map allows you to specify a function that will be applied to each item from the sequences, map requires that each sequence be finite. That led to two variations on creating a finite alternator--either directly from a set of values (finite_alternator) or from a repeating alternator (finite_iterator).
Alex Martelli pointed out the simplification in weave_sets available with Python 2.3's enumerate iterator:
http://mail.python.org/pipermail/python-list/2002-July/113491.html