Incrementing a string

J

John Velman

I've used perl for a lot of 'throw away' scripts; I like Python better in
principle, from reading about it, but it was always easier to just use
perl rather than learn python.

Now I'm writing a smallish program that I expect to keep around, so am
taking this opportunity to try to learn some Python. I have a need for
computer generated set of simple string lables. I don't know how many in
advance---each is produced as a result of a user action.

In perl I simply initiated

$label= "a";

Then, after using it doing

$label++;

This conveniently steps through the alphabet, then goes on to aa, ab,ac,
....

In Python I can get from a to z with a generator as so:
.... for i in range(97,123):
.... yield chr(i)
....'c'

But it looks like going beyond z to aa and so on is (relatively) complicated.

In truth, it seems unlikely that I would ever go beyond z in using my
application, and certainly not beyond zz which wouldn't be too hard to
program. But I hate to build in limitations no matter how reasonable.

It seems like there should be a better way that I'm missing because I'm
thinking in perl, not thinking in Python. :)

Best,

John Velman
 
P

Paul Rubin

John Velman said:
But it looks like going beyond z to aa and so on is (relatively)
complicated.

In truth, it seems unlikely that I would ever go beyond z in using my
application, and certainly not beyond zz which wouldn't be too hard to
program. But I hate to build in limitations no matter how reasonable.

Python doesn't have that feature built in, so you have to think about
how Perl really does it, and implement something similar in Python.
Yes, it will be a little bit complicated, but the Perl implementers
had to deal with the same complexity, and it's a decent exercise for
you to do the same.
 
K

Kjetil Torgrim Homme

[John Velman]:
[how do I implement "$a = 'z'; $a++;" in Python?]

is there any reason you need your labels to be on this format?
usually, I would find a plain "L42" more readable.

def gen_label():
num = 0
while True:
num += 1
yield "L" + str(num)


Perl's increment operator isn't a generator, it just changes the
current value so you can jump back and forth. here's one quick go at
it. I'm sure it can be done more prettily, and probably without
assuming the string is ASCII :)

def incr(s):
i = 1
while i <= len(s):
if 'a' <= s[-i] < 'z':
return s[:-i] + chr(88 + int(s[-i], 36)) + s[len(s)+1-i:]
elif s[-i] == 'z':
s = s[:-i] + 'a' + s[len(s)+1-i:]
else:
raise ValueError
i += 1
return 'a' + s
 
P

Phil Frost

#### begin sample program ####

import string

class LabelCounter(object):
digits = string.lowercase

def __init__(self, value=0):
self.value = value

def __str__(self):
letters = []
i = self.value
while i:
d, m = divmod(i, len(self.digits))
letters.append(self.digits[m])
i = d
return ''.join(letters[::-1]) or self.digits[0]

def __add__(self, other):
return LabelCounter(self.value + other)

def __lt__(self, other):
return self.value < other

# define other operators as needed; it's a shame this can't inherit from
# int and get these for free. It can't do this because all of int's
# operators return ints, not LabelCounters.


i = LabelCounter(0)
while i < 50:
print i
i += 1

#### end sample program ####

You can set 'digits' to any sequence at all. Set it to '01' to get
output in binary, or to ['01','23','45','67','89'] to get base 5 in a
very confusing notation *g*
 
J

Jeffrey Froman

John said:
$label++;

This conveniently steps through the alphabet, then goes on to aa, ab,ac,
...

In Python I can get from a to z with a generator as so: [snip]
But it looks like going beyond z to aa and so on is (relatively)
complicated.

Here's one way, perhaps it is simple enough for you:

import string

def strpp(s):
for i in s:
yield i
for i in strpp(s):
for j in s:
yield i + j

labels = strpp(string.lowercase)
label = label.next() # ad infinitum


Hope you enjoy Python,
Jeffrey
 
P

Pierre Fortin

This conveniently steps through the alphabet, then goes on to aa, ab,ac,

Puzzling... I hacked at this problem as a flexibility learning exercise
and actually got this to work; but... does 'while True' in a generator
have side-effects...?? Or generator recursion?

Maybe I'm just tired -- I don't see how the string actually _grows_ inside
the while.... :^?

I was just expecting the prefix to be '', then 'a'...'z' giving a..z,
aa..az, ba..bz, ... za..zz -- not continuing through aaa...azz and
onwards... It's cool; but boggles my mind at the moment... not that
that's a stretch... :^)

def ascinc(start='a',end='z'):
g = ascinc(start,end)
prefix = ''
while True:
for i in range(ord(start[-1]),ord(end[-1])+1):
yield (prefix + chr(i))
prefix = g.next()


print "what you wanted..."
g = ascinc('a')

for i in range(100):
print g.next(),

print
print "including some flexibility..."

g = ascinc('0','1')

for i in range(100):
print g.next(),
 
P

Paul Rubin

Here's my generator version:

def gen_alph():
def convert(k,n):
# convert integer k to n-letter string
out = []
for i in xrange(n):
k,r = divmod(k,26)
out.append(chr(97+r))
out.reverse()
return ''.join(out)

nletters = 1
while True:
for k in xrange(26**nletters):
yield convert(k, nletters)
nletters += 1

To test it:

g = gen_alph()
for i in range(40):
print i, g.next()
 
A

Andrew Dalke

Phil said:
import string

class LabelCounter(object):
digits = string.lowercase ....
# define other operators as needed; it's a shame this can't inherit from
# int and get these for free. It can't do this because all of int's
# operators return ints, not LabelCounters.

Try this instead. As usual with clever solutions, you
probably shouldn't use this in real code.

import string

def fixup(klass):
for name, meth in inspect.getmembers(klass):
# Find all the methods except for a few special ones
# we know won't return integers
if (callable(meth) and name not in
["__class__", "__new__", "__init__", "__str__", "__repr__"]):

# Need to make a wrapper function (g) for each of these methods.
# The wrapper function needs to know the original method, which
# is stored in the newly created scope (f).
def f(meth = meth):
def g(self, *args, **kwargs):
retval = meth(self, *args, **kwargs)
if isinstance(retval, int):
return LabelCounter(retval)
return retval
return g
g = f()
setattr(klass, name, g) # replace with the wrapped version

class LabelCounter(int):
digits = string.ascii_lowercase
def __str__(self):
letters = []
i = int(self)
while i:
d, m = divmod(i, len(self.digits))
letters.append(self.digits[m])
i = d
return "".join(letters[::-1]) or self.digits[0]
__repr__ = __str__


Here it is in use.
You can set 'digits' to any sequence at all. Set it to '01' to get
output in binary, or to ['01','23','45','67','89'] to get base 5 in a
very confusing notation *g*

I set it to ascii_lowercase since string.lowercase is locale
dependent.

OTOH, that might be what you wanted ...

Andrew
(e-mail address removed)
 
D

Duncan Booth

Jeffrey said:
import string

def strpp(s):
for i in s:
yield i
for i in strpp(s):
for j in s:
yield i + j

labels = strpp(string.lowercase)
label = label.next() # ad infinitum

string.ascii_lowercase may be preferable here.
 
J

John Velman

Thanks to everyone who responded! I've learned a good deal by reading
through the solutions. Don't know which one I'll use yet (or perhaps I'll
use something suggested by them), but these take me another step toward
'thinking in Python'. Also, I might add, it's really hard to know of all
the machinery that is available --- there is so much of it. I now know
about the itertools module, for example.

Kjetil Torgrim Homme asked why not just make the labels L1, L2, ..., Paul
Foley proposed similarly: label1, label2, ..., and also suggested that if
$label="one" it would make sense for $label++ to be "two".

Either of these would work (although "one","two",... would be yet a
different programming problem). I prefer my initial choice because the
labels will be used interactively, and I prefer to be able to do
everything from the keyboard. It's easier for me to type "aq" than "L42".
I'm a pretty good touch typist as far as lower case letters go, but
usually have to look at the keyboard to type numerals. So "m" is
preferable to "L12" but, to me, there isn't much difference between
typing "l12" and "label12".

I think some of the proposed solutions would make it easy to taylor the
kind of label as a user preference if I ever get this application to the
point that I think someone else might be interested in using it.

And yes, I think I'm going to enjoy Python.

Best,

John Velman
 
T

Terry Reedy

Pierre Fortin said:
I hacked at this problem as a flexibility learning exercise
and actually got this to work; but... does 'while True' in a generator
have side-effects...??

No. Note that the 'while True' and the rest of the body code end up in/as
the body of the .next method of the generator object produced by calling
the generator function. You don't have to understand all this to use
generators in for statements, but you should when using them with while
loops.
Or generator recursion?

I has a main of sometimes simplifying variable depth recursion ;-)
I don't see how the string actually _grows_ inside the while.... :^?
I was just expecting the prefix to be '', then 'a'...'z' giving a..z,
aa..az, ba..bz, ... za..zz -- not continuing through aaa...azz and
onwards... It's cool; but boggles my mind at the moment... not that
that's a stretch... :^)

The following is an never-ending generator, with unbounded levels of
recursion. The generator on each level of recursion is the same and
therefore generates the same sequence. Each is 'behind' the level above it
and 'ahead' of the level beneath (counting the first as the top level) in
terms of where it is in the sequence.

Prefix grows from length 0 to 1 on the first call to the next method of the
level beneath it. That same call causes the creation of another 'instance'
(still inactive) two levels down. When a later g.next call causes the
prefix one level down to grow from 0 to 1, the prefix as the current level
grows from 1 to 2. And so on. At any time (except transitions), there is
a stack of n+1 identical instances of the generator (and its .next methods)
with prefixes ranging from length n-1 down to 0 with the bottom instance
having no prefix at all.
def ascinc(start='a',end='z'):
g = ascinc(start,end)

This creates a new generator object but does NOT call its .next method.
prefix = ''
while True:
for i in range(ord(start[-1]),ord(end[-1])+1):
yield (prefix + chr(i))
prefix = g.next()

Increments in prefix length bubble up from the bott
g = ascinc('a')

This 'kickstarts' the process by creating the top generator without calling
it.
for i in range(100):
print g.next(),

while True: print g.next() # would theorectically continue indefinitely, as
would
for s in ascinc('a'): print s # both would croak on the recursion limit

In each case, the first call of .next() causes creation of a second
generator and a blank prefix. I hope the mechanism is now clearer for you.

Terry J. Reedy
 
P

Pierre Fortin

I hope the mechanism is now clearer for you.

Yes, thank you... I was getting pretty sleepy at the time and I thought
this was likely -- obviously since I coded it... :^) I was wanting the
effect I finally got, just was unclear how it was actually occurring....

One more notch on the road to expert level... :>

-30-

BTW, someone wondered about "-30-"...

From journalism handbooks:
At the end of a story, write "End" or "30" -- a journalism symbol
for "finish".

I use it when I'm pretty sure I'm done with a thread...

Regards,
Pierre
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top