0

I am writing a function to increment a 3-letter (a-z) string. For example:
Input: aaa
Output: baa

Input: zba
Output: aca

So the order is as following

aaa
baa
...
zaa
aba
bba
cba
...
zba
aca
bca
cca
...
zca
ada
...
zzz
aaa

I wrote the following function next_code() and it works, but I am wondering if there is a more elegant way to implement it rather than looping through individual letters in the string:

# 0 = a; 25 = z
def digit_to_char(digit):
    return chr(ord('a') + digit)

# a = 0; z = 25
def char_to_digit(char):
    return ord(char)-ord('a')

def next_code(code):
    # if used up all codes, loop from start
    if code == 'zzz':
        return next_code('aaa')
    else:
        code = list(code)
        # loop over letters and see which one we can increment
        for (i, letter) in enumerate(code):
            if letter == 'z':
                # go on to the next letter
                code[i] = 'a'
                continue
            else:
                # increment letter
                code[i] = digit_to_char(char_to_digit(letter) + 1)
                return ("".join(code))
                break



print (next_code('aab'))

2 Answers 2

6

just use itertools product

>>> import itertools
>>> from string import ascii_lowercase
>>> strings = itertools.product(*[ascii_lowercase]*3)
>>> "".join(next(strings,"No More Combos..."))
'aaa'
>>> "".join(next(strings,"No More Combos..."))
'aab'
>>> "".join(next(strings,"No More Combos..."))
'aac'
...

is how I would probably do it

if you want to cycle back to 'aaa' after the end you can just use itertools.cycle

strings = itertools.cycle(itertools.product(*[ascii_lowercase]*3))
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you, +1 for a very neat solution. Unfortunately does not support input (to get the next value based off of it), but shouldn't be hard to add.
2

You can simplify the loop a lot:

def next_code(code):
    code = list(code)
    for i, let in enumerate(code):
        if let != 'z':
            code[i] = chr(ord(let) + 1)
            break
        code[i] = 'a'
    return ''.join(code)

If the goal is to just produce all values one by one, starting with 'aaa', itertools.product can be used to make a generator:

from future_builtins import map  # Only on Python 2
from itertools import product

def allcodes():
    # You want the left side to vary faster, so reverse before joining
    return map(''.join, map(reversed, product(string.ascii_lowercase, repeat=3)))

for code in allcodes():
    print(code)

Or you make it a function that you call as needed to get the next code in the sequence without using it as an iterator:

nextcode = allcodes().__next__  # .next on Py2

And if the generator should be infinite (so it wraps from zzz to aaa), just change allcodes to either:

# Avoid cycle if storing all 26**3 codes in memory is a bad idea
def allcodes():
    while True:
        yield from map(''.join, map(reversed, product(string.ascii_lowercase, repeat=3)))
        # On Py2, change yield from line to:
        # for code in map(''.join, map(reversed, product(string.ascii_lowercase, repeat=3))): yield code

or at higher memory cost but greater simplicity:

from itertools import cycle

def allcodes():
    return cycle(map(''.join, map(reversed, product(string.ascii_lowercase, repeat=3))))

2 Comments

wow great answer ... you covered so many bases for this guy +1 from me :)
@JoranBeasley: TIMTOWTDI? :-) And I like to find them all. I started off with Perl.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.