Welcome, guest | Sign In | My Account | Store | Cart

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.

Python, 133 lines
 ``` 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" return template % locals() class test(unittest.TestCase): def setUp(self): self.colors = ("red", "orange", "yellow", "green", "blue", "indigo", "violet") self.expected = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] 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

 Created by Mark McEahern on Fri, 12 Jul 2002 (PSF)