[Python-Dev] Simple generators, round 2
Neil Schemenauer
nas@arctrix.com
Wed, 21 Mar 2001 21:44:32 -0800
[Tim on comparing fringes of two trees]:
> In Icon you need to create co-expressions to solve this
> problem, because its generators aren't explicitly resumable,
> and Icon has no way to spell "kick a pair of generators in
> lockstep". But explicitly resumable generators are in fact
> "good enough" for this classic example, which is usually used
> to motivate coroutines.
Apparently they are good for lots of other things too. Tonight I
implemented passing values using resume(). Next, I decided to
see if I had enough magic juice to tackle the coroutine example
from Gordon's stackless tutorial. Its turns out that I didn't
need the extra functionality. Generators are enough.
The code is not too long so I've attached it. I figure that some
people might need a break from 2.1 release issues. I think the
generator version is even simpler than the coroutine version.
Neil
# Generator example:
# The program is a variation of a Simula 67 program due to Dahl & Hoare,
# who in turn credit the original example to Conway.
#
# We have a number of input lines, terminated by a 0 byte. The problem
# is to squash them together into output lines containing 72 characters
# each. A semicolon must be added between input lines. Runs of blanks
# and tabs in input lines must be squashed into single blanks.
# Occurrences of "**" in input lines must be replaced by "^".
#
# Here's a test case:
test = """\
d = sqrt(b**2 - 4*a*c)
twoa = 2*a
L = -b/twoa
R = d/twoa
A1 = L + R
A2 = L - R\0
"""
# The program should print:
# d = sqrt(b^2 - 4*a*c);twoa = 2*a; L = -b/twoa; R = d/twoa; A1 = L + R;
#A2 = L - R
#done
# getlines: delivers the input lines
# disassemble: takes input line and delivers them one
# character at a time, also inserting a semicolon into
# the stream between lines
# squasher: takes characters and passes them on, first replacing
# "**" with "^" and squashing runs of whitespace
# assembler: takes characters and packs them into lines with 72
# character each; when it sees a null byte, passes the last
# line to putline and then kills all the coroutines
from Generator import Generator
def getlines(text):
g = Generator()
for line in text.split('\n'):
g.suspend(line)
g.end()
def disassemble(cards):
g = Generator()
try:
for card in cards:
for i in range(len(card)):
if card[i] == '\0':
raise EOFError
g.suspend(card[i])
g.suspend(';')
except EOFError:
pass
while 1:
g.suspend('') # infinite stream, handy for squash()
def squash(chars):
g = Generator()
while 1:
c = chars.next()
if not c:
break
if c == '*':
c2 = chars.next()
if c2 == '*':
c = '^'
else:
g.suspend(c)
c = c2
if c in ' \t':
while 1:
c2 = chars.next()
if c2 not in ' \t':
break
g.suspend(' ')
c = c2
if c == '\0':
g.end()
g.suspend(c)
g.end()
def assemble(chars):
g = Generator()
line = ''
for c in chars:
if c == '\0':
g.end()
if len(line) == 72:
g.suspend(line)
line = ''
line = line + c
line = line + ' '*(72 - len(line))
g.suspend(line)
g.end()
if __name__ == '__main__':
for line in assemble(squash(disassemble(getlines(test)))):
print line
print 'done'