How to create a passkey of 4 digits in python? - python

I am designing a 4 digit alphanumeric key where the characters starts from A001, A002 till Z999. After Z999 it goes to AA01 till AA99. After AA99 it goes to AB00 or AB01 . The problem which I am facing is when I increase the range, after AA99 it is not going to AB01. It start again with AA01 and ends at AA99 and the same thing keep on repeating. Any help regarding this will be appreciated.
Many Thanks!
What I tried -
def excel_format(num):
res = ""
while num:
mod = (num - 1) % 26
res = chr(65 + mod) + res
num = (num - mod) // 26
return res
def full_format(num, d=3):
chars = num // (10**d-1) + 1 # this becomes A..ZZZ
c = excel_format(chars)
digit = num % (10**d-1) + 1 # this becomes 001..999
return c + str(digit).zfill(d+1-len(c))[len(c)-d-1:]
for i in range(0, 28000):
print(full_format(i, d=3))

Below converts 0-92897 from A001-Z999, AA01-ZZ99 and prints rollover points for demonstration:
def full_format(i):
# limit of first range is 26 letters (A-Z) times 999 numbers (001-999)
if i < 26 * 999:
c,n = divmod(i,999) # quotient c is index of letter 0-25, remainder n is 0-998
c = chr(ord('A') + c) # compute letter
n += 1
return f'{c}{n:03}'
# After first range, second range is 26 letters times 26 letters * 99 numbers (01-99)
elif i < 26*999 + 26*26*99:
i -= 26*999 # remove first range offset
cc,n = divmod(i,99) # remainder n is 0-98, use quotient cc to compute two letters
c1,c2 = divmod(cc,26) # c1 is index of first letter, c2 is index of second letter
c1 = chr(ord('A') + c1) # compute first letter
c2 = chr(ord('A') + c2) # compute second letter
n += 1
return f'{c1}{c2}{n:02}'
else:
raise OverflowError(f'limit is {26*999+26*26*99}')
# Generate all possibilities into a list for demonstration.
L = [full_format(i) for i in range(92898)]
print(L[0])
print(L[999-1])
print(L[999])
print(L[26*999-1])
print(L[26*999])
print(L[26*999+99])
print(L[26*999+99*26-1])
print(L[26*999+99*26])
print(L[26*999+99*26*26-1])
full_format(92898) # overflow
A001
A999
B001
Z999
AA01
AB01
AZ99
BA01
ZZ99
Traceback (most recent call last):
File "C:\test.py", line 31, in <module>
full_format(92898) # overflow
File "C:\test.py", line 18, in full_format
raise OverflowError(f'limit is {26*999+26*26*99}')
OverflowError: limit is 92898

Related

Optimizing algorithm for finding palindromic primes in Python

I've been trying to solve a LeetCode problem which takes an input number (less than 10^8) and returns the next palindromic prime. Also, the answer is guaranteed to exist and is less than 2 * 10^8. My approach seems to work fine for most numbers, but the runtime increases significantly and LeetCode tells me I've exceeded the time limit when a specific number is entered (like 9989900). Is it because the gap between palindromic primes is large in that range?
This is the code I've written.
import time
start = time.time()
def is_prime(num: int) -> bool:
if num < 2:
return False
elif num == 2 or num == 3:
return True
if num % 6 != 1 and num % 6 != 5:
return False
else:
for i in range(3, int(num ** 0.5) + 1, 2):
if num % i == 0:
return False
else:
return True
def is_palindrome(num: int) -> bool:
return str(num) == str(num)[::-1]
class Solution:
def primePalindrome(self, N: int):
if N == 1:
return 2
elif 8 <= N < 11:
return 11
elif is_prime(N) and is_palindrome(N):
return N
# To skip even numbers, take 2 cases, i.e., when N is even and when N is odd
elif N % 2 == 0:
for i in range(N + 1, 2 * 10 ** 8, 2):
if len(str(i)) % 2 == 0: # Because even length palindromes are divisible by 11
i += 2
elif is_palindrome(i):
if is_prime(i):
return i
else:
continue
else:
for i in range(N, 2 * 10 ** 8, 2):
if len(str(i)) % 2 == 0:
i += 2
elif is_palindrome(i):
if is_prime(i):
return i
else:
continue
obj = Solution()
print(obj.primePalindrome(9989900)) # 100030001
print(time.time() - start) # 9 seconds
Is my solution slow because of too many loops and conditional statements? How do I reduce the runtime? Solving this without using any external libraries/packages would be preferable. Thank you.
Given that checking primes/palindromes sequentially isn't fast enough, I thought of this "number assembly" approach:
Given that prime numbers can only end with digits 1, 3, 7 or 9. The palindrome numbers also can only begin with these digits. So, if we generate palindrom digits in between the first and last we will get a lot fewer numbers to chck for "primality".
For example: 1xxxxxx1, 3xxxxxx3, 7xxxxxx7 and 9xxxxxx9
These middle parts must also be palindromes so we only have half the digits to consider: 1xxxyyy1 where yyy is a mirror of xxx. For odd sized middle we will have xxzyy where zyy is a mirror of xxz.
Combining this with a sequential generation of the first/last digits and digits in the middle, we can get the next number after N. By generating the most significant digits sequentially (i.e. the xxx part) we are certain that the composed numbers will be generated in an increasing sequence.
def isPrime(n):
return n==2 if n<3 or n%2==0 else all(n%d for d in range(3,int(n**0.5)+2,2))
def nextPalPrime(N):
digits = list(map(int,str(N)))
while True:
if digits[0] not in (1,3,7,9): # advance first/last digits
digits[0] = [1,1,3,3,7,7,7,7,9,9][digits[0]]
digits[1:] = [0]*(len(digits)-1)
digits[-1] = digits[0]
midSize = (len(digits)-1)//2
midStart = int("".join(map(str,digits[1:1+midSize] or [0])))
for middle in range(midStart,10**midSize): # generate middle digits
if midSize:
midDigits = list(map(int,f"{middle:0{midSize}}")) # left half of middle
digits[1:1+midSize] = midDigits # set left half
digits[-midSize-1:-1] = midDigits[::-1] # set mirrored right half
number = int("".join(map(str,digits)))
if number>N and isPrime(number): # check for prime
return number
digits[0] += 1 # next first digit
if digits[0] > 9: digits = [1]+[0]*len(digits) # need more digits
output:
pp = 1000
for _ in range(20):
pp = nextPalPrime(pp)
print(pp)
10301
10501
10601
11311
11411
12421
12721
12821
13331
13831
13931
14341
14741
15451
15551
16061
16361
16561
16661
17471
Performance:
from time import time
start=time()
print(nextPalPrime(9989900),time()-start)
100030001 0.023847103118896484
No even number of digits
Initially I was surprised that the solutions never produced a prime number with an even number of digits. but analyzing the composition of palindrome numbers I realized that those would always be multiples of 11 (so not prime):
abba = a*1001 + b*110
= a*11*91 + b*11*10
= 11*(a*91 + b*10)
abccba = a*100001 + b*10010 + c*1100
= a*11*9091 + b*11*910 + c*11*100
= 11*(a*9091 + b*910 + c*100)
abcddcba = a*10000001 + b*1000010 + c*100100 + d*110000
= a*11*909091 + b*11*90910 + c*11*9100 + d*11*10000
= 11*(a*909091 + b*90910 + c*9100 + d*10000)
abcdeedcba = a*1000000001 + b*100000010 + c*10000100 + d*10010000 + e*11000000
= a*11*90909091 + b*11*9090910 + c*11*909100 + d*11*910000 + e*11*1000000
= 11*(a*90909091 + b*9090910 + c*909100 + d*910000 + e*1000000)
Using this observation and a more numerical approach, we get a nice performance boost:
def nextPalPrime(N):
for width in range(len(str(N)),10):
if width%2==0: continue
size = width//2
factors = [(100**(size-n)+1)*10**n for n in range(size)]+[10**size]
for firstDigit in (1,3,7,9):
if (firstDigit+1)*factors[0]<N: continue
for middle in range(10**size):
digits = [firstDigit]+[*map(int,f"{middle:0{size}}")]
number = sum(f*d for f,d in zip(factors,digits))
if number>N and isPrime(number):
return number
from time import time
start=time()
print(nextPalPrime(9989900),time()-start)
100030001 0.004210948944091797

10 digit number whose first n digits are divisible by n

So I came upon this little problem and I challenged myself to write my first program to solve it. The problem is to find a 10 digit number, for which if you take the first n digits, the resulting number must be divisible by n (eg. 1236, where 1 is divisible by 1, 12 by 2, 123 by 3 and 1236 by 4). My code is a little clumsy which i don't mind, but I'm getting error messages I don't understand.
from itertools import permutations
oddperm = permutations([1,3,7,9])
evenperm = permutations([2,4,6,8])
for odd in oddperm:
for even in evenperm:
num1 = (even[0]*(10**7)) + (even[1]*(10**5)) + (even[2]*10**3) + (even[3]*10)
num2 = (odd[0]*10**8 )+ (odd[1]*10**6) + (5*10**4) + (odd[2]*10**2) + (odd[3])
num = str((num1+num2)*10)
if (num[0]*10 + num[1]) % 2 == 0 and #etc etc etc and (num[0]*10**8 + num[1]*10**7 + num[2]*10**6 + num[3]*10**5 + 5*10**4 + num[5]*10**3 + num[6]*10**2 + num[7]*10 + num[8]) % 9 == 0:
print(num)
break
else:
continue
The trouble is im getting
TypeError Traceback (most recent call last)
<ipython-input-75-cb75172b012c> in <module>
10 num2 = (odd[0]*10**8 )+ (odd[1]*10**6) + (5*10**4) + (odd[2]*10**2) + (odd[3])
11 num = str((num1+num2)*10)
---> 12 if (num[0]*10 + num[1]) % 2 == 0 and ... and (num[0]*10**8 + num[1]*10**7 + num[2]*10**6 + num[3]*10**5 + 5*10**4 + num[5]*10**3 + num[6]*10**2 + num[7]*10 + num[8]) % 9 == 0:
13 print(num)
14 break
TypeError: not all arguments converted during string formatting
Also if someone has an idea on how to make that line a touch more elegant I'm all ears.
Thanks in advance for any and all contributions!
It looks to me like the error you describe is coming from a type conversion. You are converting num to a string, and then using indexing to get a certain digit of the number (which is fine), but before you can do any math with the digit, you need to convert it back into an int.
# num gets converted to a string
num = str((num1+num2)*10)
# num's digits get converted back into integers
if (int(num[0])*10 + int(num[1])) % 2 == 0:
print(num)
Additionally, to make your checking of each digit more elegant, you can use a for loop and check for failure rather than success. This is an interesting problem so I spent a bit of time on it, haha. The following function can be called in place of the long if (int(num[0])*10 + int(num[1])) % 2 == 0 and ... etc:, changing it to simply if check_num(num):.
def check_num(num:str):
# define powers in advance for convenience
powers = [10**p for p in range(len(num))]
# check that the number satisfies the desired property
place = 1
while place < len(num):
sum = 0
# check each digit
for i in range(place+1):
sum += int(num[i]) * powers[place - i]
# check for failure
if sum % (place+1) != 0:
return False
# check the next place
place += 1
# we made it all the way through
return True
Hope this is enlightening.

Codewars. Some tests are passed, but i need to get tests which outputs the following mistake: 3263 should equal -1

Can you explain it what problems are here? To my mind, this code is like a heap of crap but with the right solving. I beg your pardon for my english.
the task of this kata:
Some numbers have funny properties. For example:
89 --> 8¹ + 9² = 89 * 1
695 --> 6² + 9³ + 5⁴= 1390 = 695 * 2
46288 --> 4³ + 6⁴+ 2⁵ + 8⁶ + 8⁷ = 2360688 = 46288 * 51
Given a positive integer n written as abcd... (a, b, c, d... being digits) and a positive integer p we want to find a positive integer k, if it exists, such as the sum of the digits of n taken to the successive powers of p is equal to k * n. In other words:
Is there an integer k such as : (a ^ p + b ^ (p+1) + c ^(p+2) + d ^ (p+3) + ...) = n * k
If it is the case we will return k, if not return -1.
Note: n, p will always be given as strictly positive integers.
dig_pow(89, 1) should return 1 since 8¹ + 9² = 89 = 89 * 1
dig_pow(92, 1) should return -1 since there is no k such as 9¹ + 2² equals 92 * k
dig_pow(695, 2) should return 2 since 6² + 9³ + 5⁴= 1390 = 695 * 2
dig_pow(46288, 3) should return 51 since 4³ + 6⁴+ 2⁵ + 8⁶ + 8⁷ = 2360688 = 46288 * 51
def dig_pow(n, p):
if n > 0 and p > 0:
b = []
a = str(n)
result = []
for i in a:
b.append(int(i))
for x in b:
if p != 1:
result.append(x ** p)
p += 1
else:
result.append(x ** (p + 1))
if int((sum(result)) / n) < 1:
return -1
elif int((sum(result)) / n) < 2:
return 1
else:
return int((sum(result)) / n)
test results:
Test Passed
Test Passed
Test Passed
Test Passed
3263 should equal -1
I don't know what exact version of Python you're using. This following code are in Python 3. And if I get you correctly, the code can be as simple as
def dig_pow(n, p):
assert n > 0 and p > 0
digits = (int(i) for i in str(n)) # replaces your a,b part with generator
result = 0 # you don't use result as a list, so an int suffice
for x in digits: # why do you need if in the loop? (am I missing something?)
result += x ** p
p += 1
if result % n: # you just test for divisibility
return -1
else:
return result // n
The major problem is that, in your objective, you have only two option of returning, but you wrote if elif else, which is definitely unnecessary and leads to problems and bugs. The % is modulus operator.
Also, having an if and not returning anything in the other branch is often not a good idea (see the assert part). Of course, if you don't like it, just fall back to if.
I believe this could work as well and I find it a little easier to read, however it can definitely be improved:
def dig_pow(n, p):
value = 0
for digit in str(n):
value += int(digit)**p
p += 1
for k in range(1,value):
if value/k == n:
return k
return -1
this is some example simple example than using:
digits = (int(i) for i in str(n))
I'm opting to use this version since I am still a beginner which can be done with this alt way:
result = 0
for digits in str(n):
#iterate through each digit from n
# single of digits turn to int & power to p
for number in digits:
result += int(number) ** p
p += 1
as for the full solution, it goes like this:
def dig_pow(n, p):
# example n = 123 , change it to string = 1, 2, 3
# each string[] **p, and p iterate by 1
# if n % p not equal to p return - 1
result = 0
for digits in str(n):
#iterate through each digit from n
# single digit turn to int & power to p
for number in digits:
result += int(number) ** p
p += 1
if result % n:
return -1
else:
return result // n

Speeding Up Python 3.4 Code

I have a problem where I need to find the next largest palindrome after a given number, however I am running into problems with the runtime being over 1 second. Is there any way I can speed this code up?
inp = input()
if inp == '9' * len(inp):
print('1' + ('0' * (len(inp) - 1)) + '1') #ran into a problem where 999 would give 9109
else:
num = list(inp)
oldnum = int(inp)
if len(num) % 2 == 0: #for even length numbers
for i in range(len(num) // 2):
num[len(num) // 2 + i] = num[len(num) // 2 - 1 - i]
if int("".join(num)) > oldnum:
print("".join(num))
else:
#sometimes the palindrome was smaller eg: 1199 --> 1111
num[len(num) // 2 - 1] = str(int(num[len(num) // 2 - 1]) + 1)
num[len(num) // 2] = str(int(num[len(num) // 2]) + 1)
print("".join(num))
else: #basically the same but for odd length numbers
for i in range(len(num) // 2):
num[len(num) // 2 + 1 + i] = num[len(num) // 2 - 1 - i]
if int("".join(num)) > oldnum:
print("".join(num))
else:
num[len(num) // 2] = str(int(num[len(num) // 2]) + 1)
print("".join(num))
Here's how I would break it down,
# simple version, easy to understand and fast enough
# up to maybe a thousand digits
def next_palindrome(n):
"""
Find the first integer p such that p > n and p is palindromic
"""
# There are two forms of palindrome:
# even number of digits, abccba
# odd number of digits, abcba
# Find abc
s = str(n)
abc = s[:(len(s) + 1) // 2]
# There are six possibilites for p:
#
# abcpq < abcba -> p = abcba
# abcpq >= abcba -> p = ab(c + 1)ba (with carries as needed)
# abcpqr == 999999 -> p = 1000001 *(num digits + 1)
#
# abcpqr < abccba -> p = abccba
# abcpqr >= abccba -> p = ab(c+1)(c+1)ba (with carries as needed)
# abcpq == 99999 -> p = 100001 *(num digits + 1)
#
# *Note that the even-number-of-9s case is properly handled by
# odd-digits-with-carry, but odd-number-of-9s needs special handling
#
# Make basis substrings
cba = abc[::-1]
ba = cba[1:]
abc1 = str(int(abc) + 1)
cba1 = abc1[::-1]
ba1 = cba1[1:]
# Assemble candidate values
candidates = [
int(abc + ba), # abcba
int(abc1 + ba1), # ab(c+1)ba
int(abc + cba), # abccba
int(abc1 + cba1), # ab(c+1)(c+1)ba
int(abc1[:-1] + ba1) # handles odd-number-of-9s
]
# Find the lowest candidate > n
return min(c for c in candidates if c > n)
def main():
while True:
n = int(input("\nValue for n (or -1 to quit): "))
if n == -1:
break
else:
print("Next palindrome is {}".format(next_palindrome(n)))
if __name__ == "__main__":
main()
which runs like
Value for n (or -1 to quit): 12301
Next palindrome is 12321
Value for n (or -1 to quit): 12340
Next palindrome is 12421
Value for n (or -1 to quit): 99999
Next palindrome is 100001
Value for n (or -1 to quit): 123001
Next palindrome is 123321
Value for n (or -1 to quit): 123400
Next palindrome is 124421
Value for n (or -1 to quit): 999999
Next palindrome is 1000001
Value for n (or -1 to quit): -1
Edit: I thought you were talking about maybe 100 digits. A million digits makes it worth spending more time minimizing the number of string operations and typecasts, like so:
# Super-efficient version
# for playing with million-digit palindromes
def str_lt(x, y):
"""
Take two integer strings, `x` and `y`,
return int(`x`) < int(`y`)
"""
return len(x) < len(y) or x < y
def str_add_1(n):
"""
Given an integer string `n`,
return str(int(n) + 1)
"""
# find the first non-9 digit, starting from the right
for i in range(len(n) - 1, -1, -1):
if n[i] != '9':
return n[:i] + str(int(n[i]) + 1) + '0' * (len(n) - i - 1)
# string was all 9s - overflow
return '1' + '0' * len(n)
def next_palindrome(n):
"""
For non-negative integer `n` (as int or str)
find the first integer p such that p > n and p is palindromic
Return str(p)
Note: `n` must be well-formed, ie no leading 0s or non-digit characters
"""
# Make sure n is a string
if not isinstance(n, str):
n = str(n)
# There are three forms of palindrome:
# single digit, x (ab == '')
# even number of digits, abba ( x == '')
# odd number of digits, abxba ( x is single digit)
#
if len(n) == 1:
# take care of single digit case
return '11' if n == '9' else str_add_1(n)
else:
# There are six possibilites for p:
#
# (1) abqr < abba -> p = abba
# (2) abqr >= abba -> p = a(b+1)(b+1)a (with carries as needed)
# (3) abqr == 9999 -> p = 10001 (carry results in overflow)
#
# (4) abxqr < abxba -> p = abxba
# (5) abxqr >= abxba -> p = ab(x + 1)ba (with carries as needed)
# (6) abxqr == 99999 -> p = 100001 (carry results in overflow)
#
# Find ab, x, qr
half = len(n) // 2
ab = n[ : half]
x = n[ half:-half] # a 0- or 1-digit string
qr = n[-half: ]
ba = ab[::-1]
if str_lt(qr, ba):
# solve cases (1) and (4)
return "".join([ab, x, ba])
if x == '9':
# do manual carry from x
ab1 = str_add_1(ab)
ba1 = ab1[::-1]
if len(ab1) > len(ab):
# carry results in overflow - case (6)
return ab1 + ba1
else:
# carry but no overflow - case (5)
return "".join([ab1, '0', ba1])
if x:
# no carry - case (5)
return "".join([ab, str_add_1(x), ba])
# x == ''
ab1 = str_add_1(ab)
ba1 = ab1[::-1]
if len(ab1) > len(ab):
# carry results in overflow - case (3)
return ab1[:-1] + ba1
else:
# no overflow - case (2)
return ab1 + ba1
On my machine, this finds a million-digit palindrome in less than 0.002 seconds (vs about 18.5 seconds for your code).
Although your code took less than a second for me. But why so much code for just finding a plaindrome. Why not simple do
def next_palindrome(num):
while True:
if str(num) == str(num)[::-1]:
break
num += 1
return num

Convert an excel or spreadsheet column letter to its number in Pythonic fashion

Is there a more pythonic way of converting excel-style columns to numbers (starting with 1)?
Working code up to two letters:
def column_to_number(c):
"""Return number corresponding to excel-style column."""
number=-25
for l in c:
if not l in string.ascii_letters:
return False
number+=ord(l.upper())-64+25
return number
Code runs:
>>> column_to_number('2')
False
>>> column_to_number('A')
1
>>> column_to_number('AB')
28
Three letters not working.
>>> column_to_number('ABA')
54
>>> column_to_number('AAB')
54
Reference: question answered in C#
There is a way to make it more pythonic (works with three or more letters and uses less magic numbers):
def col2num(col):
num = 0
for c in col:
if c in string.ascii_letters:
num = num * 26 + (ord(c.upper()) - ord('A')) + 1
return num
And as a one-liner using reduce (does not check input and is less readable so I don't recommend it):
col2num = lambda col: reduce(lambda x, y: x*26 + y, [ord(c.upper()) - ord('A') + 1 for c in col])
One-liners tested in Python 2.7.1 and 3.5.2
excel_col_num = lambda a: 0 if a == '' else 1 + ord(a[-1]) - ord('A') + 26 * excel_col_num(a[:-1])
excel_col_name = lambda n: '' if n <= 0 else excel_col_name((n - 1) // 26) + chr((n - 1) % 26 + ord('A'))
Multi-liners likewise
def excel_column_name(n):
"""Number to Excel-style column name, e.g., 1 = A, 26 = Z, 27 = AA, 703 = AAA."""
name = ''
while n > 0:
n, r = divmod (n - 1, 26)
name = chr(r + ord('A')) + name
return name
def excel_column_number(name):
"""Excel-style column name to number, e.g., A = 1, Z = 26, AA = 27, AAA = 703."""
n = 0
for c in name:
n = n * 26 + 1 + ord(c) - ord('A')
return n
def test (name, number):
for n in [0, 1, 2, 3, 24, 25, 26, 27, 702, 703, 704, 2708874, 1110829947]:
a = name(n)
n2 = number(a)
a2 = name(n2)
print ("%10d %-9s %s" % (n, a, "ok" if a == a2 and n == n2 else "error %d %s" % (n2, a2)))
test (excel_column_name, excel_column_number)
test (excel_col_name, excel_col_num)
All tests print
0 ok
1 A ok
2 B ok
3 C ok
24 X ok
25 Y ok
26 Z ok
27 AA ok
702 ZZ ok
703 AAA ok
704 AAB ok
2708874 EXCEL ok
1110829947 COLUMNS ok
You could just add the following to the console after installing the openpyxl module:
>>> from openpyxl.utils import get_column_letter, column_index_from_string
>>> get_column_letter(1)
'A'
>>> column_index_from_string('A')
1
Just change the letters and number to suit your needs.
Here is one way to do it. It is a variation on code in the XlsxWriter module:
def col_to_num(col_str):
""" Convert base26 column string to number. """
expn = 0
col_num = 0
for char in reversed(col_str):
col_num += (ord(char) - ord('A') + 1) * (26 ** expn)
expn += 1
return col_num
>>> col_to_num('A')
1
>>> col_to_num('AB')
28
>>> col_to_num('ABA')
729
>>> col_to_num('AAB')
704
Using openpyxl
import openpyxl
(column_string, row) = openpyxl.cell.coordinate_from_string(address)
column = openpyxl.cell.column_index_from_string(column_string)
This should do, in VBA, what you're looking for:
Function columnNumber(colLetter As String) As Integer
Dim colNumber As Integer
Dim i As Integer
colLetter = UCase(colLetter)
colNumber = 0
For i = 1 To Len(colLetter)
colNumber = colNumber + (Asc(Mid(colLetter, Len(colLetter) - i + 1, 1)) - 64) * 26 ^ (i - 1)
Next
columnNumber = colNumber
End Function
You can use it as you would an Excel formula--enter column, in letters, as a string (eg, "AA") and should work regardless of column length.
Your code breaks when dealing with three letters because of the way you're doing the counting--you need to use base 26.
After reading this, I decided to find a way to do it directly in Excel cells. It even accounts for columns after Z.
Just paste this formula into a cell of any row of any column and it will give you the corresponding number.
=IF(LEN(SUBSTITUTE(ADDRESS(ROW(),COLUMN(),4),ROW(),""))=2,
CODE(LEFT(SUBSTITUTE(ADDRESS(ROW(),COLUMN(),4),ROW(),""),1))-64*26)+
CODE(RIGHT(SUBSTITUTE(ADDRESS(ROW(),COLUMN(),4),ROW(),""),1)-64),
CODE(SUBSTITUTE(ADDRESS(ROW(),COLUMN(),4),ROW(),""))-64)
The theme here was to grab the letter of the column, get the Code() of it and subtract 64, based on the fact that the ASCII character code for letter A is 64.
I made this one-liner:
colNameToNum = lambda cn: sum([((ord(cn[-1-pos]) - 64) * 26 ** pos) for pos in range(len(cn))])
It works by iterating through the letters in reverse order and multiplying by 1, 26, 26 * 26 etc, then summing the list. This method would be compatible with longer strings of letters, too.
I call it with:
print(colNameToNum("AA")) # 27
or
print(colNameToNum("XFD")) # the highest column allowed, I believe. Result = 16384
You could use this oneliner using comprehension and string that is fairly easy to use:
sum([string.ascii_lowercase.index(c) + 26 ** i for i,c in enumerate(col_letters)])
Here's what I use (wrote before I found this page):
def col_to_index(col):
return sum((ord(c) - 64) * 26**i for i, c in enumerate(reversed(col))) - 1
And some runs:
>>> col_to_index('A')
1
>>> col_to_index('AB')
28
>>> col_to_index('ABCD')
19010
Use:
LETTERS = list(string.ascii_uppercase)
def column_number(column_id):
return sum([(LETTERS.index(j)+1)*(26**i) for i,j in enumerate(column_id[::-1])])
There are several parts to this one-liner, so here's the explanation:
column_id[::-1]: reverses the string, e.g. converts 'AZ' to 'ZA', there's a good reason to do so, which we will see in a bit.
enumerate(): produces a iterable, e.g. (0, 'Z'), (1, 'A')
With some observation:
A -> 1 = (26**0)*1 # ** is the exponential operator
B -> 2 = (26**0)*2
Z -> 26 = (26**0)*26
AA -> 27 = (26**0)*1 + (26**1)*1
AB -> 28 = (26**0)*2 + (26**1)*1
AZ -> 52 = (26**0)*26 + (26**1)*1 # recall that we have (0, 'Z'), (1, 'A')
Reversing the column_id and enumerate() allows us to use the index as the exponent for 26. The rest is now trivial.
LETTERS.index(j): gives us the index of the letter in LETTERS
sum(): takes a list of numbers and returns the total.
Here is a recursive solution:
def column_string_to_num(s):
n = ord(s[-1]) - 64
if s[:-1]:
return 26 * (column_string_to_num(s[:-1])) + n
else:
return n
column_string_to_num("AB")
#output: 28
The inverse can also be defined recursively, in a similar way:
def column_num_to_string(n):
n, rem = divmod(n - 1, 26)
next_char = chr(65 + rem)
if n:
return column_string(n) + next_char
else:
return next_char
column_num_to_string(28)
#output: 'AB'
Concise and elegant Ruby version:
def col_num(col_name)
col_name.split(//).inject(0) { |n, c| n * 26 + c.upcase.ord - "A".ord + 1 }
end
I'm not sure I understand properly, do you want to "translate" the referenced C# code to python? If so, you were on the right track; just modify it so:
def column_to_number(c):
"""Return number corresponding to excel-style column."""
sum = 0
for l in c:
if not l in string.ascii_letters:
return False
sum*=26
sum+=ord(l.upper())-64
return sum
just do :
print ws.Range("E2").Column
call example :
from win32com import client
xl = client.Dispatch("Excel.Application")
wb = xl.Workbooks.Open("c:/somePath/file.xls")
xl.Visible = 1
ws = wb.Sheets("sheet 1")
print ws.Range("E2").Column
result :
>>5
For index that starts from zero (e.g. A = 0, B = 1, and so on):
def col_to_index(col):
A = ord('A')
return sum(i * 26 + (ord(c) - A) for i, c in enumerate(col[::-1].upper()))
You could also do it by a series of multiplies and adds as follows. Here "A" will equal to 1. Running time is O(n) where n is the length of the column, col.
import functools
def spreadsheet_column_encoding(col):
return functools.reduce(
lambda result, char: result * 26 + ord(char) - ord("A") + 1, col, 0
)
E.g ZZ = 702:
0 * 26 + 90 - 65 + 1 = 26
26 * 26 + 90 - 65 + 1 = 702
P.S: ord('Z') = 90
To convert number to column letter, kindly see my answer here. You get to do the opposite using division and modulus calculations.

Categories