How to count sequentially using letters instead of numbers?
Question:
Is there a simple way to count using letters in Python? Meaning, ‘A’ will be used as 1, ‘B’ as 2 and so on, and after ‘Z’ will be ‘AA’, ‘AB’ and so on. So below code would generate:
def get_next_letter(last_letter):
return last_letter += 1 # pseudo
>>> get_next_letter('a')
'b'
>>> get_next_letter('b')
'c'
>>> get_next_letter('c')
'd'
...
>>> get_next_letter('z')
'aa'
>>> get_next_letter('aa')
'ab'
>>> get_next_letter('ab')
'ac'
...
>>> get_next_letter('az')
'ba'
>>> get_next_letter('ba')
'bb'
...
>>> get_next_letter('zz')
'aaa'
Answers:
Based on @Charlie Clark‘s implementation of the openpyxl util get_column_letter
, we can have:
def get_number_letter(n):
letters = []
while n > 0:
n, remainder = divmod(n, 26)
# check for exact division and borrow if needed
if remainder == 0:
remainder = 26
n-= 1
letters.append(chr(remainder+64))
return ''.join(reversed(letters))
This gives the letter representation of a number. Now, to increment, we need the reverse. Based on that logic (and the general number base logic), I wrote:
def number_from_string(letters):
n = 0
for i, c in enumerate(reversed(letters)):
n += (ord(c)-64)*26**i
return n
And now we can combine them to:
def get_next_letter(letters):
return get_number_letter(number_from_string(letters)+1)
Original answer:
This kind of "counting" is very similar to how Excel indexes its columns. Therefore it is possible to take advantage of the openpyxl package, which has two utility functions: get_column_letter
and column_index_from_string
:
from openpyxl.utils import get_column_letter, column_index_from_string
def get_next_letter(letters):
return get_column_letter(column_index_from_string(letters)+1)
NOTE: as this is based on Excel, it is limited to count up-to 'ZZZ'
. i.e. calling the function with 'ZZZ'
will raise an exception.
Output example for both implementations:
>>> get_next_letter('A')
'B'
>>> get_next_letter('Z')
'AA'
>>> get_next_letter('BD')
'BE'
Let’s start with the simple special case of getting just the single-character strings.
from string import ascii_lowercase
def population():
yield from ascii_lowercase
Then
>>> x = population()
>>> list(x)
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
>>> x = population()
>>> next(x)
'a'
>>> next(x)
'b'
So we’d like to add the two-character sequences next:
from string import ascii_lowercase
from itertools import product
def population():
yield from ascii_lowercase
yield from map(''.join, product(ascii_lowercase, repeat=2)
Note that the single-character strings are just a special case of the product with repeat=1
, so we could have written
from string import ascii_lowercase
from itertools import product
def population():
yield from map(''.join, product(ascii_lowercase, repeat=1)
yield from map(''.join, product(ascii_lowercase, repeat=2)
We can write this with a loop:
def population():
for k in range(1, 3):
yield from map(''.join, product(ascii_lowercase, repeat=k)
but we don’t necessarily want an artificial upper limit on what strings we can produce; we want, in theory, to produce all of them. For that, we replace range
with itertools.count
.
from string import ascii_lowercase
from itertools import product, count
def population():
for k in count(1):
yield from map(''.join, product(ascii_lowercase, repeat=k)
all proposed are just way too complicated
I came up with below, using a recursive call,
this is it!
def getNextLetter(previous_letter):
"""
'increments' the provide string to the next letter recursively
raises TypeError if previous_letter is not a string
returns "a" if provided previous_letter was emtpy string
"""
if not isinstance(previous_letter, str):
raise TypeError("the previous letter should be a letter, doh")
if previous_letter == '':
return "a"
for letter_location in range(len(previous_letter) - 1, -1, -1):
if previous_letter[letter_location] == "z":
return getNextLetter(previous_letter[:-1])+"a"
else:
characters = "abcdefghijklmnopqrstuvwxyz"
return (previous_letter[:-1])
+characters[characters.find(previous_letter[letter_location])+1]
# EOF
Is there a simple way to count using letters in Python? Meaning, ‘A’ will be used as 1, ‘B’ as 2 and so on, and after ‘Z’ will be ‘AA’, ‘AB’ and so on. So below code would generate:
def get_next_letter(last_letter):
return last_letter += 1 # pseudo
>>> get_next_letter('a')
'b'
>>> get_next_letter('b')
'c'
>>> get_next_letter('c')
'd'
...
>>> get_next_letter('z')
'aa'
>>> get_next_letter('aa')
'ab'
>>> get_next_letter('ab')
'ac'
...
>>> get_next_letter('az')
'ba'
>>> get_next_letter('ba')
'bb'
...
>>> get_next_letter('zz')
'aaa'
Based on @Charlie Clark‘s implementation of the openpyxl util get_column_letter
, we can have:
def get_number_letter(n):
letters = []
while n > 0:
n, remainder = divmod(n, 26)
# check for exact division and borrow if needed
if remainder == 0:
remainder = 26
n-= 1
letters.append(chr(remainder+64))
return ''.join(reversed(letters))
This gives the letter representation of a number. Now, to increment, we need the reverse. Based on that logic (and the general number base logic), I wrote:
def number_from_string(letters):
n = 0
for i, c in enumerate(reversed(letters)):
n += (ord(c)-64)*26**i
return n
And now we can combine them to:
def get_next_letter(letters):
return get_number_letter(number_from_string(letters)+1)
Original answer:
This kind of "counting" is very similar to how Excel indexes its columns. Therefore it is possible to take advantage of the openpyxl package, which has two utility functions: get_column_letter
and column_index_from_string
:
from openpyxl.utils import get_column_letter, column_index_from_string
def get_next_letter(letters):
return get_column_letter(column_index_from_string(letters)+1)
NOTE: as this is based on Excel, it is limited to count up-to 'ZZZ'
. i.e. calling the function with 'ZZZ'
will raise an exception.
Output example for both implementations:
>>> get_next_letter('A')
'B'
>>> get_next_letter('Z')
'AA'
>>> get_next_letter('BD')
'BE'
Let’s start with the simple special case of getting just the single-character strings.
from string import ascii_lowercase
def population():
yield from ascii_lowercase
Then
>>> x = population()
>>> list(x)
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
>>> x = population()
>>> next(x)
'a'
>>> next(x)
'b'
So we’d like to add the two-character sequences next:
from string import ascii_lowercase
from itertools import product
def population():
yield from ascii_lowercase
yield from map(''.join, product(ascii_lowercase, repeat=2)
Note that the single-character strings are just a special case of the product with repeat=1
, so we could have written
from string import ascii_lowercase
from itertools import product
def population():
yield from map(''.join, product(ascii_lowercase, repeat=1)
yield from map(''.join, product(ascii_lowercase, repeat=2)
We can write this with a loop:
def population():
for k in range(1, 3):
yield from map(''.join, product(ascii_lowercase, repeat=k)
but we don’t necessarily want an artificial upper limit on what strings we can produce; we want, in theory, to produce all of them. For that, we replace range
with itertools.count
.
from string import ascii_lowercase
from itertools import product, count
def population():
for k in count(1):
yield from map(''.join, product(ascii_lowercase, repeat=k)
all proposed are just way too complicated
I came up with below, using a recursive call,
this is it!
def getNextLetter(previous_letter):
"""
'increments' the provide string to the next letter recursively
raises TypeError if previous_letter is not a string
returns "a" if provided previous_letter was emtpy string
"""
if not isinstance(previous_letter, str):
raise TypeError("the previous letter should be a letter, doh")
if previous_letter == '':
return "a"
for letter_location in range(len(previous_letter) - 1, -1, -1):
if previous_letter[letter_location] == "z":
return getNextLetter(previous_letter[:-1])+"a"
else:
characters = "abcdefghijklmnopqrstuvwxyz"
return (previous_letter[:-1])
+characters[characters.find(previous_letter[letter_location])+1]
# EOF