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</%(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