Converting numbers to Excel column headers
Question:
This is a problem on LeetCode and it’s classified as "easy." I’ve been at this for hours, even called in a colleague. I can’t figure out the fault in my logic. I’m not looking for a completely different solution to the problem. I’d just be grateful if someone could point out what’s wrong with my approach.
The idea is to convert an int to a string that is represented as an Excel column header (1=’A’, 2=’B’ … 27=’AA’, etc.). Here is my code, with comments. The code works for many inputs (e.g., 735 -> ‘ABG’), but fails on others (e.g., 702 -> ‘ZZ’).
def numToCol(n):
# Generate a key such that {1:'A', 2:'B', 3:'C'... 26:'Z'} (this works fine)
key = {}
for i in range(65, 91):
key[i-64] = chr(i)
# According to Wikipedia, the number of digits in the bijective base-k
# numeral representing a nonnegative integer n is floor(logk((n+1)*(k-1)))
# exp = num of letters in final string
exp = int(math.log((n+1)*25, 26)) # int() rounds it down
col_name = ''
num = n
# The final number is represented by a(26**0) + b(26**1) + c(26**2) + ...
# If exp = 3, then there are 3 letters in the final string, so we need to find
# a(26**2) + b(26**1) + c(26**0).
# If exp = 3, i iterates over 2, 1, 0.
for i in range(exp-1, -1, -1):
# factor = how many 26**i's there are in num, rounded down
factor = int(num/(26**i))
# add to the string the letter associated with that factor
col_name = col_name + key[factor]
# update the number for next iteration
num = num - (factor*(26**i))
return col_name
Here is a function I wrote to go the reverse direction (convert string to int). This helps to see what the expected result should be. It’s confirmed to work.
def colToNum(string):
'''
Converts an upper-case string (e.g., 'ABC') to numbers as if they were
column headers in Excel.
'''
key = {}
for i in range(65, 91):
key[chr(i)] = i-64
new = []
for idx, val in enumerate(string[::-1]):
new.append(key[val] * 26**idx)
return sum(new)
Answers:
Did you check this answer? That one looks like what you area asking for. I am sharing the code as following as well.
def excel_column_number_to_name(column_number):
output = ""
index = column_number - 1
while index >= 0:
character = chr((index % 26) + ord('A'))
output = output + character
index = index // 26 - 1
return output[::-1]
for i in xrange(1, 1024):
print "%4d : %s" % (i, excel_column_number_to_name(i))
Was able to find the fault, but didn’t find an elegant way of solving the problem. As you might have noticed, the code always fails whenever you get to Z and there is more than one letter. That’s because the factors that should be, for example, 2 and 26, meaning BZ, become 3 and 0. And this zero leads to the problem. Here is the hack that manages to make the code work. Everything is the same except for the if-statement (and num = num - (factor*(26**i))
has been moved up)
for i in range(exp-1, -1, -1):
# factor = how many 26**i's there are in num, rounded down
factor = int(num/(26**i))
num = num - (factor*(26**i))
if num == 0 and i!=0:
factor -=1
num = 26
# add to the string the letter associated with that factor
col_name = col_name + key[factor]
This if-statements just fixes these "Z" cases that happen whenever there is more than one letter in the result.
Subtract 1 and generate a remainder modulo 26. 0-25 represents A-Z. Repeat:
def num_to_col(num):
if num < 1:
raise ValueError('num must be >= 1')
result = ''
while num:
num, rem = divmod(num - 1, 26)
result = chr(ord('A') + rem) + result
return result
# tests
for num in (1, 26, 26+1, 26*2, 26*2+1, 26*26+25,
26*26+26, 26*26+26+1, 735,
26*26*26+26*26+26, 26*26*26+26*26+26+1):
print(f'{num:5} {num_to_col(num)}')
Output:
1 A
26 Z
27 AA
52 AZ
53 BA
701 ZY
702 ZZ
703 AAA
735 ABG
18278 ZZZ
18279 AAAA
This is a problem on LeetCode and it’s classified as "easy." I’ve been at this for hours, even called in a colleague. I can’t figure out the fault in my logic. I’m not looking for a completely different solution to the problem. I’d just be grateful if someone could point out what’s wrong with my approach.
The idea is to convert an int to a string that is represented as an Excel column header (1=’A’, 2=’B’ … 27=’AA’, etc.). Here is my code, with comments. The code works for many inputs (e.g., 735 -> ‘ABG’), but fails on others (e.g., 702 -> ‘ZZ’).
def numToCol(n):
# Generate a key such that {1:'A', 2:'B', 3:'C'... 26:'Z'} (this works fine)
key = {}
for i in range(65, 91):
key[i-64] = chr(i)
# According to Wikipedia, the number of digits in the bijective base-k
# numeral representing a nonnegative integer n is floor(logk((n+1)*(k-1)))
# exp = num of letters in final string
exp = int(math.log((n+1)*25, 26)) # int() rounds it down
col_name = ''
num = n
# The final number is represented by a(26**0) + b(26**1) + c(26**2) + ...
# If exp = 3, then there are 3 letters in the final string, so we need to find
# a(26**2) + b(26**1) + c(26**0).
# If exp = 3, i iterates over 2, 1, 0.
for i in range(exp-1, -1, -1):
# factor = how many 26**i's there are in num, rounded down
factor = int(num/(26**i))
# add to the string the letter associated with that factor
col_name = col_name + key[factor]
# update the number for next iteration
num = num - (factor*(26**i))
return col_name
Here is a function I wrote to go the reverse direction (convert string to int). This helps to see what the expected result should be. It’s confirmed to work.
def colToNum(string):
'''
Converts an upper-case string (e.g., 'ABC') to numbers as if they were
column headers in Excel.
'''
key = {}
for i in range(65, 91):
key[chr(i)] = i-64
new = []
for idx, val in enumerate(string[::-1]):
new.append(key[val] * 26**idx)
return sum(new)
Did you check this answer? That one looks like what you area asking for. I am sharing the code as following as well.
def excel_column_number_to_name(column_number):
output = ""
index = column_number - 1
while index >= 0:
character = chr((index % 26) + ord('A'))
output = output + character
index = index // 26 - 1
return output[::-1]
for i in xrange(1, 1024):
print "%4d : %s" % (i, excel_column_number_to_name(i))
Was able to find the fault, but didn’t find an elegant way of solving the problem. As you might have noticed, the code always fails whenever you get to Z and there is more than one letter. That’s because the factors that should be, for example, 2 and 26, meaning BZ, become 3 and 0. And this zero leads to the problem. Here is the hack that manages to make the code work. Everything is the same except for the if-statement (and num = num - (factor*(26**i))
has been moved up)
for i in range(exp-1, -1, -1):
# factor = how many 26**i's there are in num, rounded down
factor = int(num/(26**i))
num = num - (factor*(26**i))
if num == 0 and i!=0:
factor -=1
num = 26
# add to the string the letter associated with that factor
col_name = col_name + key[factor]
This if-statements just fixes these "Z" cases that happen whenever there is more than one letter in the result.
Subtract 1 and generate a remainder modulo 26. 0-25 represents A-Z. Repeat:
def num_to_col(num):
if num < 1:
raise ValueError('num must be >= 1')
result = ''
while num:
num, rem = divmod(num - 1, 26)
result = chr(ord('A') + rem) + result
return result
# tests
for num in (1, 26, 26+1, 26*2, 26*2+1, 26*26+25,
26*26+26, 26*26+26+1, 735,
26*26*26+26*26+26, 26*26*26+26*26+26+1):
print(f'{num:5} {num_to_col(num)}')
Output:
1 A
26 Z
27 AA
52 AZ
53 BA
701 ZY
702 ZZ
703 AAA
735 ABG
18278 ZZZ
18279 AAAA