from __future__ import generators
def make_color_switch(color1, color2):
def color_switch():
i = 0
while True:
if i % 2:
yield color2
else:
yield color1
i += 1
return color_switch
It seemed like this could be more generic:
from __future__ import generators
def make_switch(*args):
"""Return a generator that loops through args."""
if not args:
raise RuntimeError("Missing parameter: args.")
def switch():
i = n = 0
while True:
i = n % len(args)
yield args[i]
n += 1
return switch
Is switch a bad name for this? Can anyone suggest a better name? Other
improvements? What I like about this code is it demonstrates several
"advanced" features of Python, all the while retaining (imho) the simplicity
and clarity Python is known for:
generators
nested scopes
variable length argument arrays
functions as objects
Here's sample code that shows it used in the context of the original
question:
#! /usr/bin/env python
from __future__ import generators
def make_switch(*args):
"""Return a generator that loops through args."""
if not args:
raise RuntimeError("Missing parameter: args.")
def switch():
i = n = 0
while True:
i = n % len(args)
yield args[i]
n += 1
return switch
def colorize(s, *colors):
switch = make_switch(*colors)()
template = "<%(color)s><b>%(c)s</b></color>"
l = []
for c in s:
color = switch.next()
l.append(template % locals())
print ''.join(l)
colorize("testing", "black", "red", "green", "blue")
Is make_switch useful enough to post as a recipe? I didn't search to see
whether someone has already made something like this. It seems at once both
trivial and reuseful.
Cheers,
// mark
-
out of curiosity, what happens when n reaches the end of the int/long/whatever
that stores it?
> Is switch a bad name for this? Can anyone suggest a better name? Other
> improvements? What I like about this code is it demonstrates several
> "advanced" features of Python, all the while retaining (imho) the simplicity
> and clarity Python is known for:
>
> generators
> nested scopes
> variable length argument arrays
> functions as objects
>
> Here's sample code that shows it used in the context of the original
> question:
>
while the above code is a nice sample, the below code should not be given to
people learning the language, it uses way too many tricks. Plus one letter
variable names are a pain.
> def colorize(s, *colors):
> switch = make_switch(*colors)()
> template = "<%(color)s><b>%(c)s</b></color>"
> l = []
> for c in s:
> color = switch.next()
> l.append(template % locals())
> print ''.join(l)
>
> colorize("testing", "black", "red", "green", "blue")
>
switch is an interesting idea, definately needs a new name.
My guess is this will run forever--if you let it. <wink>
Perhaps it should allow an optional sentinel? The assumption is the caller
is responsible for using it wisely.
> while the above code is a nice sample, the below code should not
> be given to people learning the language, it uses way too many tricks.
I appreciate your opinion and I mean and say this with respect: One of the
joys of Python is that even supposedly advanced tricks are eminently
explorable. I would rather not patronize someone and I'd rather make the
mistake of offering them too much than too little. Besides, even if they
can't get it, they're not the only ones reading this list now or in the
future. Ultimately, I can trace this sentiment back to something very
personal: That's precisely how I'd want to be treated. Wouldn't you?
I mean, it's not like I vomitted some obscurified black hole of
context-laden punctuation (i.e., Perl) on them. <wink>
> Plus one letter variable names are a pain.
I go back and forth on this one. Presumably, you don't mean for counters
and the like (e.g., i, n). I tend to use one-letter variable names when
locals() is very small, the function is short (e.g., 5-10 lines) and the
meaning is painfully obvious. Perhaps a bad habit? E.g., s, imho, is
better than text or string for a string. I'm not trying to save typing, so
much. I strive for clarity of intent at the level of module, class,
function names. So that I don't feel so bad using one-letter variable
names. But, reasonable people disagree and I enjoy seeing alternative
styles, discussing them.
> switch is an interesting idea, definately needs a new name.
Maybe loop and make_loop? At least then the danger of an infinite one is
explicit?
Thanks for the comments.
Cheers,
// mark
-
the template variable ......
>> Plus one letter variable names are a pain.
>
> I go back and forth on this one. Presumably, you don't mean for counters
> and the like (e.g., i, n). I tend to use one-letter variable names when
> locals() is very small, the function is short (e.g., 5-10 lines) and the
> meaning is painfully obvious. Perhaps a bad habit? E.g., s, imho, is
> better than text or string for a string. I'm not trying to save typing, so
> much. I strive for clarity of intent at the level of module, class,
> function names. So that I don't feel so bad using one-letter variable
> names. But, reasonable people disagree and I enjoy seeing alternative
> styles, discussing them.
>
I always seem to have a semi violent reaction when I see locals().
Or you could just change it to (untested):
def make_color_switch(color1, color2):
def color_switch():
i = 0
while True:
if i:
yield color2
else:
yield color1
i = not i
return color_switch
Cliff
> from __future__ import generators
>
> def make_switch(*args):
> """Return a generator that loops through args."""
> if not args:
> raise RuntimeError("Missing parameter: args.")
this should be a TypeError, as the builtins raise that too
raise TypeError("switch() takes at least one argument")
> def switch():
> i = n = 0
> while True:
> i = n % len(args)
> yield args[i]
> n += 1
> return switch
>
> Is switch a bad name for this?
definetly yes ;-) as a C programmer, i expect something completly
different... (switch() {case x:})
> Can anyone suggest a better name?
how about "repeat"? 'cause that's what it does.
> Other improvements?
isn't this simpler, more obvious what it does, and still doing what you
want:
>>> from __future__ import generators
>>> def repeat(*args):
... if not args: args = [None] #optionaly to catch empty args
... while 1:
... for x in args: yield x
...
>>> zip(range(10), repeat('even', 'odd'))
[(0, 'even'), (1, 'odd'), (2, 'even'), (3, 'odd'), (4, 'even'), (5, 'odd'),
(6, 'even'), (7, 'odd'), (8, 'even'), (9, 'odd')]
>>>
chris
--
Chris <clie...@gmx.net>
Or on your "generic" version (still untested - I just reinstalled my PC
and don't have Python 2.2 yet):
from __future__ import generators
def make_switch(*args):
"""Return a generator that loops through args."""
if not args:
raise RuntimeError("Missing parameter: args.")
def switch():
i = n = 0
while True:
i = n % len(args)
yield args[i]
n = min(n + 1, len(args))
return switch
or possibly:
def make_switch(*args):
"""Return a generator that loops through args."""
if not args:
raise RuntimeError("Missing parameter: args.")
def switch():
while True:
for a in args:
yield a
return switch
Holy cow, that's much nicer!
Thanks,
// mark
-
Nice. I guess I'm still not too worried about n maxing out. Don't ints
just spill into longs go on forever? Nevertheless, for the generic loop,
your suggestion makes me realize we can just dispense with n altogether:
def make_loop(*args):
"""Return a generator that loops through args."""
if not args:
raise RuntimeError("Missing parameter: args.")
def loop():
i = 0
while True:
i = i % len(args)
yield args[i]
i += 1
return loop
This way, the counter never gets bigger than len(args). <wink>
// m
-
Good point, that certainly could have been explained better. For what it's
worth, in the original post in the other thread, I provided a list of the
features that were used so that anyone who wanted to investigate could do a
little searching to learn more. I could have spent more time compiling that
list. Thanks for the feedback.
> I always seem to have a semi violent reaction when I see locals().
Hmm, this may sound naive, but I don't understand why. Consider these
admittedly cobbled examples:
def colorize1(value, color):
template = "<%(color)s>%(value)s</%(color)s>"
return template % locals()
def colorize2(value, color):
template = "<%(color)s>%(value)s</%(color)s>"
return template % {'color': color, 'value': value}
def colorize3(value, color):
template = "<%s>%s</%s>"
return template % (color, value, color)
That's the precise order in which I'd rank them. <wink>
What could possibly be wrong with the use of locals() in colorize1()?
Cheers,
// mark
-
Wouldn't you get exactly the same observable effect from
the simpler function:
def make_switch(*args):
"""Return an iterator that loops through args."""
if not args:
raise RuntimeError("Missing parameter: args.")
return iter(args)
...?
Alex
...and the answer is "no", because the first function returns an
unbounded iterator, looping around the args forever, while the
second one returns a bounded iterator, looping around the
args just once. I don't think there's any substantially simpler
way to obtain the first function's effect.
Alex
I like repeat(). That is, I like repeat! <wink> Thanks!
> isn't this simpler, more obvious what it does, and still doing what you
> want:
>
> >>> from __future__ import generators
> >>> def repeat(*args):
> ... if not args: args = [None] #optionaly to catch empty args
> ... while 1:
> ... for x in args: yield x
> ...
> >>> zip(range(10), repeat('even', 'odd'))
> [(0, 'even'), (1, 'odd'), (2, 'even'), (3, 'odd'), (4, 'even'),
> (5, 'odd'),
> (6, 'even'), (7, 'odd'), (8, 'even'), (9, 'odd')]
> >>>
Yes, that's very nice. Here's the latest, with the help of all the
suggestions I've gotten.
#! /usr/bin/env python
from __future__ import generators
def make_repeat(*args):
"""Return a generator that repeatedly loops through args."""
if not args:
raise TypeError("make_repeat() takes at least one parameter.")
def repeat():
while args:
for a in args:
yield a
return repeat()
def colorize(value, *colors):
repeat = make_repeat(*colors)
template = "<%(color)s>%(item)s</%(color)s>"
items = []
for item in value:
color = repeat.next()
formatted_item = template % locals()
items.append(formatted_item)
return ''.join(items)
s = colorize("testing", "black", "red", "green", "blue")
print s
expected = "<black>t</black>"\
"<red>e</red>"\
"<green>s</green>"\
"<blue>t</blue>"\
"<black>i</black>"\
"<red>n</red>"\
"<green>g</green>"
assert s == expected
-
No, sadly. The key to make_repeat() (as I've renamed it) is that it
repeatedly loops through args. The above solution would suffice if you only
wanted to iterate over the set at most once.
Of course, I'm straining to come up with real world examples where this is
helpful, but I take that as a sign of my lack of imagination. <wink>
In general, this recipe, if it's worthy to be called such, is useful when
you want to weave together a finite set of values of a given length (n) with
a different set of values of a different length (m) and for the i-th element
of n, you want it to be weaved with the len(m) modulo i-th element of m.
How's that for a fancy definition?
Of course, Alex, if there's a more elegant and simple way to do this, I have
no doubt you'll come up with it!
Cheers,
// mark
p.s. for what it's worth, here's the latest code and demo:
#! /usr/bin/env python
from __future__ import generators
def make_repeat(*args):
"""Return a generator that repeatedly loops through args."""
if not args:
raise TypeError("make_repeat() takes at least one parameter.")
def repeat():
while args:
for a in args:
yield a
[Alex Martelli]
> Wouldn't you get exactly the same observable effect from
> the simpler function:
>
> def make_switch(*args):
> """Return an iterator that loops through args."""
> if not args:
> raise RuntimeError("Missing parameter: args.")
> return iter(args)
The top one generates an unbounded sequence, due to the "while True". The
Icon language calls this "repeated alternation", and actually has a prefix
operator for it (the vertical bar).
What I'm unclear about is why we're writing a function to return a generator
function: why not write a generator function directly? So my candidate for
simplification is:
def make_switch(*args):
"""Generate the elements in args repeatedly."""
if not args:
raise TypeError("at least one argument required")
while True:
for a in args:
yield a
I'd lose the "if not args:" block, though; if the arglist is empty, it will
simply terminate without yielding anything, and that's what I expect the
empty case to do.
Well dang, my original motivation was so I could pass around the generator
without specifying the args. As you point out, that's completely
unnecessary. Thanks!
> So my candidate for simplification is:
>
> def make_switch(*args):
> """Generate the elements in args repeatedly."""
> if not args:
> raise TypeError("at least one argument required")
> while True:
> for a in args:
> yield a
>
> I'd lose the "if not args:" block, though; if the arglist is
> empty, it will
> simply terminate without yielding anything, and that's what I expect the
> empty case to do.
If you leave the while True, then it won't raise StopIteration when you call
it without arguments. The simple fix is to replace that with while args.
Here's the result of my latest tinkering. For what it's worth, I think I
prefer raising the error in the generator when args is not specified rather
than waiting until next() is called the first time--on the principle that
raising the error earlier is better. Still, this version shows the delayed
raise....
#! /usr/bin/env python
from __future__ import generators
import unittest
def repeat(*args):
"""Return an unbounded iterator that repeatedly iterates over args."""
while args:
for a in args:
yield a
def weave(values, repeat, weaver):
for item in values:
yield weaver(item, repeat.next())
def color_item(item, color):
template = "<%(color)s>%(item)s</%(color)s>"
return template % locals()
class test(unittest.TestCase):
def test(self):
values = range(10)
colors = ("red", "organge", "yellow", "green", "blue", "indigo",
"violet")
repeat_colors = repeat(*colors)
expected = ['<red>0</red>',
'<organge>1</organge>',
'<yellow>2</yellow>',
'<green>3</green>',
'<blue>4</blue>',
'<indigo>5</indigo>',
'<violet>6</violet>',
'<red>7</red>',
'<organge>8</organge>',
'<yellow>9</yellow>']
generated = [x for x in weave(values, repeat_colors, color_item)]
self.assertEquals(expected, generated)
def test_empty(self):
r = repeat()
self.assertRaises(StopIteration, r.next)
if __name__ == "__main__":
unittest.main()
-
[Mark McEahern]
> If you leave the while True, then it won't raise StopIteration
> when you call it without arguments.
So true -- and even if you change it to "while 1" <wink>.
> The simple fix is to replace that with while args.
Yes, that's nice!
> Here's the result of my latest tinkering. For what it's worth, I think I
> prefer raising the error in the generator when args is not specified
> rather than waiting until next() is called the first time--on the
> principle that raising the error earlier is better.
Then that's where we differ: I don't consider an empty sequence to be "an
error" in any sense of the word. For the same reason, range(10, 10) doesn't
complain in Python, or string[i:i], etc -- doing nothing gracefully is
important because there's sometimes nothing that needs be done <wink>.
I think I'll continue to scratch my head and profit from that observation
for quite some time. Thank you.
Profit--or better yet, lose. My misconceptions, that is. <wink>
So where I've ended up is a place I'm sure there's some fancy name for--but
I don't know it.
I have two iterators, one is finite, the other is a repeating alternator. I
want to walk through both of them at the same time, which means I'll stop
when the finite one is done. (If I need to stop sooner, I can use a
sentinel on the finite one--but I'd rather ignore this variation for now).
I want to apply some function to the items from each iterator and then yield
the result--which suggests a third iterator.
I guess I wonder whether there's a pattern here that anyone has named--or if
there's a pattern that better expresses what I can only dimly see and
express. I have trouble naming things, so again, any tips on names is
helpful.
In the following, I would express weave_items with the following pseudo
code:
for item, alternate in iterator, alternator:
yield weave_item(item, alternate)
Instead, I have to explicitly call next(), which will raise StopIteration if
I happened to pass in an alternator with no args. Because I have no way (as
far as I can tell) of asking alternator, "Do you have any more bananas?"
That is, I can't peek into it (unless I wrap it in a class or something, I
suppose):
from __future__ import generators
def repeating_alternator(*args):
"""Return a repeating alternator for args."""
while args:
for a in args:
yield a
def weave_items(iterator, alternator, weave_item):
"""Iterate over 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()
Well, that was a bunch of poorly strung together thoughts. But it's late.
And just think, I squeezed all of this out of someone's question about how
they could squelch the "extra" space emitted by print foo<comma>. Perhaps
Liebniz was right.
Cheers,
// mark
-
The thread on zip suggests the term "lockstep iteration." I can't use map
or zip though because I have an unbounded iterator. Of course, I could
easily bound that to the length of the finite iterator.
// m
-
Right. So what about a direct implementation of what you just said?
def weave(setn, setm):
n = len(setn)
m = len(setm)
if not n or not m:
raise ValueError, "Weaved sets cannot be empty"
yield setn[0], setm[0]
i = 1
while i%n or i%m:
yield setn[i%n], setm[i%m]
i += 1
Your approach is more clever (and may have other uses), but this
plain and direct implementation appeals to me. So the function
that uses it might become something like:
def colorize(value, *colors):
return ''.join([ "<%s>%s</%s>" % (color, item, color)
for item, color in weave(value, colors) ])
Alex
> > So where I've ended up is a place I'm sure there's some fancy
> > name for--but I don't know it.
>
> The thread on zip suggests the term "lockstep iteration." I can't use
> map or zip though because I have an unbounded iterator.
Not so. As long as one of the iterators is finite, zip will work:
>>> from __future__ import generators
>>> def repeat(*args):
... """Return a repeating alternator for args."""
... while args:
... for a in args:
... yield a
...
>>> for i, j in zip(range(5), repeat("a","b","c")):
... print i, j
...
0 a
1 b
2 c
3 a
4 b
--
Christian Tanzer tan...@swing.co.at
Glasauergasse 32 Tel: +43 1 876 62 36
A-1130 Vienna, Austria Fax: +43 1 877 66 92
> The thread on zip suggests the term "lockstep iteration." I can't use map
> or zip though because I have an unbounded iterator. Of course, I could
> easily bound that to the length of the finite iterator.
zip stops as soon as the first sequence runs out of items:
class over_and_over_again:
def __init__(self, seq):
self.seq = seq
self.len = len(seq)
def __getitem__(self, index):
return self.seq[index % self.len]
print zip(range(12), over_and_over_again("spam"))
prints
[(0, 's'), (1, 'p'), (2, 'a'), (3, 'm'), (4, 's'), (5, 'p'),
(6, 'a'), (7, 'm'), (8, 's'), (9, 'p'), (10, 'a'), (11, 'm')]
</F>
I just saw Office Space for the first time last night. One of the
characters in the movie develops a prototype for a Jumping To Conclusions
mat. That's certainly what I did about zip, assuming that it had similar
behavior to map when it came to unbounded iterators. As you point out,
that's not true.
Because I want to do something to the zipped items, it doesn't seem like zip
solves all my problems, though. map would, if it had the same behavior as
zip--namely that it stopped once one of the iterators ran out of items.
Below is a version that shows the use of zip. Here are the key lines:
weaved = zip(iterator, alternator)
generated = [color_item(x, y) for x, y in weaved]
Perhaps what I'm still looking for is a way to
weave-two-iterators-with-a-function all at once. One solution would be to
modify or wrap repeating_alternator() so that it was finite--then I could
just use map.
Thanks!
// mark
#! /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 weave_items(iterator, alternator, weave_item):
"""Iterate over 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 test(self):
iterator = range(10)
colors = ("red", "organge", "yellow", "green", "blue", "indigo",
"violet")
alternator = repeating_alternator(*colors)
expected = ['<red>0</red>',
'<organge>1</organge>',
'<yellow>2</yellow>',
'<green>3</green>',
'<blue>4</blue>',
'<indigo>5</indigo>',
'<violet>6</violet>',
'<red>7</red>',
'<organge>8</organge>',
'<yellow>9</yellow>']
weaved = zip(iterator, alternator)
generated = [color_item(x, y) for x, y in weaved]
self.assertEquals(expected, generated)
def test_empty(self):
r = repeating_alternator()
doesn't this do it?
generated = [color_item(x, alternator) for x in iterator]
(BTW, I nominate cycler as a name for alternator ;-)
--
Emile van Sebille
em...@fenx.com
---------
*IF* you know the 'length' of iterator, or the iterable it is based
on, as in the examples used in preious articles in this thread, this
is easy and clean:
def firstn(it, n):
while n:
yield it.next()
n -= 1
n = len(iterator) # or whatever
map(color_item, iterator, firstn(alternator, n))
Terry J. Reedy
Marginally easier and cleaner:
def take(n, it):
for j in xrange(n):
yield it.next()
Alex
I like cycler, too. I'm going to post a summary of solutions shortly. The
above works if you say alternator.next().
// m
-
Cheers,
// mark
#! /usr/bin/env python
from __future__ import generators
import unittest
def weave(setn, setm):
"""Return a generator that iterates over setn and setm until setn is
exhausted. If setn is larger than setm, cycle over setm.
"""
m = len(setm)
if not setn or not setm:
raise ValueError, "Weaved sets cannot be empty"
for i in range(len(setn)):
yield setn[i], setm[i%m]
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 take(n, it):
"""Return n elements from the unbounded iterator."""
for j in xrange(n):
yield it.next()
def repeating_alternator(*args):
"""Return a repeating alternator for args."""
while args:
for a in args:
yield a
def weave_items(iterator, alternator, weave_item):
"""Iterate over 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(self):
colors = self.colors
length = self.length
expected = self.expected
generated = [color_item(x, y) for x, y in weave(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(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_map2(self):
colors = self.colors
length = self.length
expected = self.expected
iterator = range(length)
alternator = repeating_alternator(*colors)
generated = map(color_item, iterator, take(length, 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)]
I like this. The 'or' in the while statment means this will loop until
lcd(n, m)--easily fixed. I modified this slightly:
def weave(setn, setm):
"""Return a generator that iterates over setn and setm until setn is
exhausted. If setn is larger than setm, cycle over setm.
"""
m = len(setm)
if not setn or not setm:
raise ValueError, "Weaved sets cannot be empty"
for i in range(len(setn)):
yield setn[i], setm[i%m]
This means you have to specify the alternator/cycler second--it doesn't
treat the two sets as interchangeable. Slightly less general than the
direction your solution points.
// m
-
> [Alex Martelli]
>> Right. So what about a direct implementation of what you just said?
>>
>> def weave(setn, setm):
>> n = len(setn)
>> m = len(setm)
>> if not n or not m:
>> raise ValueError, "Weaved sets cannot be empty"
>> yield setn[0], setm[0]
>> i = 1
>> while i%n or i%m:
>> yield setn[i%n], setm[i%m]
>> i += 1
>
> I like this. The 'or' in the while statment means this will loop until
> lcd(n, m)--easily fixed. I modified this slightly:
I'm not sure what an lcd is in this context, but I did mean to
loop least-common-multiple(m, n) times, treating the two sets
as equivalent. So I guess I had misunderstood the specs!
> def weave(setn, setm):
> """Return a generator that iterates over setn and setm until setn is
> exhausted. If setn is larger than setm, cycle over setm.
> """
> m = len(setm)
> if not setn or not setm:
> raise ValueError, "Weaved sets cannot be empty"
> for i in range(len(setn)):
> yield setn[i], setm[i%m]
>
> This means you have to specify the alternator/cycler second--it doesn't
> treat the two sets as interchangeable. Slightly less general than the
> direction your solution points.
Yep. Nicer in Python 2.3 of course:
def weave(setn, setm):
"""Return a generator that iterates over setn and setm until setn is
exhausted. If setn is larger than setm, cycle over setm."""
if not setn or not setm:
raise ValueError, "Weaved sets cannot be empty"
m = len(setm)
for index, item in enumerate(setn):
yield item, setm[index%m]
Here, actually, an empty setn SHOULD be quite acceptable -- as no
cycling is done on it, that should be OK, and only an empty setm
should be a problem. So, I'd prefer:
def weave(setn, setm):
"""Return an iterator that iterates over iterator setn and
sequence setm until setn is exhausted. If setn is larger than
setm, cycle over setm."""
m = len(setm)
if not m:
raise ValueError, "Set to be cycled on cannot be empty"
for index, item in enumerate(setn):
yield item, setm[index%m]
Alex
I seem to have a penchant for maligning concepts. Thanks for the correction
on denominator --> multiple. Would it make sense to say lcd (least common
denominator) is a conceptual subset of lcm (least common multiple)? If so,
that explains my confusion.
I like your 2.3 version, especially making it handle empty setn:
> def weave(setn, setm):
> """Return an iterator that iterates over iterator setn and
> sequence setm until setn is exhausted. If setn is larger than
> setm, cycle over setm."""
> m = len(setm)
> if not m:
> raise ValueError, "Set to be cycled on cannot be empty"
> for index, item in enumerate(setn):
> yield item, setm[index%m]
I posted a summary of all the code presented in this thread here:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/139210
Thanks!
// mark
-
> [Alex Martelli]
>> I'm not sure what an lcd is in this context, but I did mean to
>> loop least-common-multiple(m, n) times, treating the two sets
>> as equivalent. So I guess I had misunderstood the specs!
>
> I seem to have a penchant for maligning concepts. Thanks for the
> correction
> on denominator --> multiple. Would it make sense to say lcd (least common
> denominator) is a conceptual subset of lcm (least common multiple)? If
Hmmm, the lcd of minimal-terms fractions A and B is the lcm of A's
denominator and B's denominator, right? Guess it IS a "conceptual
subclass" in that you have to add the concepts of denominator and
minimal-terms:-).
Alex