51

How can I encode an integer with base 36 in Python and then decode it again?

Chris
  • 44,602
  • 16
  • 137
  • 156
mistero
  • 5,139
  • 8
  • 29
  • 27
  • possible duplicate of [How to convert an integer to the shortest url-safe string in Python?](http://stackoverflow.com/questions/561486/how-to-convert-an-integer-to-the-shortest-url-safe-string-in-python) – Belldandu May 28 '15 at 04:11

9 Answers9

51

Have you tried Wikipedia's sample code?

def base36encode(number, alphabet='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'):
    """Converts an integer to a base36 string."""
    if not isinstance(number, int):
        raise TypeError('number must be an integer')
 
    base36 = ''
    sign = ''
 
    if number < 0:
        sign = '-'
        number = -number
 
    if 0 <= number < len(alphabet):
        return sign + alphabet[number]
 
    while number != 0:
        number, i = divmod(number, len(alphabet))
        base36 = alphabet[i] + base36
 
    return sign + base36
 
def base36decode(number):
    return int(number, 36)
 
print(base36encode(1412823931503067241))
print(base36decode('AQF8AA0006EH'))
jterrace
  • 64,866
  • 22
  • 157
  • 202
kanngard
  • 947
  • 7
  • 9
  • 36
    Christ if they can do str->int in any base, you'd think they'd let you do int->str in any base with a builtin... – Dubslow Aug 18 '12 at 23:26
  • 4
    to make it even more pythonic, add import of `string` and replace alphabet value with `string.digits+string.lowercase` – DataGreed Mar 08 '16 at 15:33
  • 5
    interface between `base36encode` and `base36decode` is broken, the latter will fail (possibly silently) to decode anything encoded with custom `alphabet` argument – Grozz Oct 31 '16 at 15:20
  • 3
    The encoding function allows the user to specify an alphabet, while the decoding function does not, therefore the decoding function is not a true inverse of the encoding function as it relies on the default alphabet. – Fredrick Brennan Sep 28 '17 at 13:36
  • 1
    For Python3 this solution will encounter errors as it uses the type `long` which is not supported in Python3. You can simply remove the `long` type from the function call above or see @André C. Andersen solution below. – TEK Jul 28 '20 at 06:48
  • 1
    If this sample code came from wikipedia please provide a link to the article – Mike Oct 02 '21 at 01:10
  • It seems that in these 12 years that has passed, the example has been removed from Wikipedia and I can not provide a link to it. That is way the answer was edited and the URL removed. Wayback Machine has stored a variant of it though: https://web.archive.org/web/20090805171144/http://en.wikipedia.org:80/wiki/Base36 – kanngard Oct 09 '21 at 12:26
37

I wish I had read this before. Here is the answer:

def base36encode(number):
    if not isinstance(number, (int, long)):
        raise TypeError('number must be an integer')
    is_negative = number < 0
    number = abs(number)

    alphabet, base36 = ['0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ', '']

    while number:
        number, i = divmod(number, 36)
        base36 = alphabet[i] + base36
    if is_negative:
        base36 = '-' + base36

    return base36 or alphabet[0]


def base36decode(number):
    return int(number, 36)

print(base36encode(1412823931503067241))
print(base36decode('AQF8AA0006EH'))
assert(base36decode(base36encode(-9223372036721928027)) == -9223372036721928027)
Nathan Strong
  • 2,360
  • 13
  • 17
mistero
  • 5,139
  • 8
  • 29
  • 27
  • 4
    For including lowercase alphabet see [How to convert an integer to the shortest url-safe string in Python?](http://stackoverflow.com/questions/561486/how-to-convert-an-integer-to-the-shortest-url-safe-string-in-python/561704#561704) – Alireza Savand Apr 22 '11 at 07:37
  • @Tadeck: Because then you have to reverse `base36` before you return it. – John Y Jul 30 '13 at 16:39
  • @JohnY: My mistake, that would not be the same. – Tadeck Jul 30 '13 at 20:41
35
from numpy import base_repr

num = base_repr(num, 36)
num = int(num, 36)

Here is information about numpy.base_repr.

Cristian Ciupitu
  • 20,270
  • 7
  • 50
  • 76
  • I attempted to improve this answer by making it runnable with example data, and adding more documentation resources. The edit was for some reason rejected with the explanation that it was more suitable as its own answer. Thus I've added my improved version of your answer below: http://stackoverflow.com/questions/1181919/python-base-36-encoding/42331616#42331616 – André C. Andersen Feb 19 '17 at 19:19
28

You can use numpy's base_repr(...) for this.

import numpy as np

num = 2017

num = np.base_repr(num, 36)
print(num)  # 1K1

num = int(num, 36)
print(num)  # 2017

Here is some information about numpy, int(x, base=10), and np.base_repr(number, base=2, padding=0).

(This answer was originally submitted as an edit to @christopher-beland's answer, but was rejected in favor of its own answer.)

André C. Andersen
  • 8,955
  • 3
  • 53
  • 79
15

You could use https://github.com/tonyseek/python-base36.

$ pip install base36

and then

>>> import base36
>>> assert base36.dumps(19930503) == 'bv6h3'
>>> assert base36.loads('bv6h3') == 19930503
Jiangge Zhang
  • 4,298
  • 4
  • 25
  • 33
  • 3
    This is the right answer. I don't know why everyone else wants to reinvent the wheel. – Michael Scheper Aug 07 '17 at 19:03
  • 2
    @MichaelScheper Because dependencies are hard. See `leftpad`. Copy and pasting a trivial function to a file that does what you want is sometimes better than adding a new external dependency. – mbarkhau Nov 13 '17 at 11:19
  • 1
    @mbarkhau You could download third dependencies to your repository vendor or your private PyPI mirror (just like Golang projects). It may be better than just copy-paste code snippets, for separated test coverage and release plan. – Jiangge Zhang Nov 14 '17 at 10:37
  • @MichaelScheper maybe because python-base36 package didn't exist before the end of 2014? – cl0ne Aug 08 '22 at 09:48
11

terrible answer, but was just playing around with this an thought i'd share.

import string, math

int2base = lambda a, b: ''.join(
    [(string.digits +
      string.ascii_lowercase +
      string.ascii_uppercase)[(a // b ** i) % b]
     for i in range(int(math.log(a, b)), -1, -1)]
)

num = 1412823931503067241
test = int2base(num, 36)
test2 = int(test, 36)
print test2 == num
akaihola
  • 26,309
  • 7
  • 59
  • 69
user1036542
  • 149
  • 1
  • 5
7

I benchmarked the example encoders provided in answers to this question. On my Ubuntu 18.10 laptop, Python 3.7, Jupyter, the %%timeit magic command, and the integer 4242424242424242 as the input, I got these results:

  • Wikipedia's sample code: 4.87 µs ± 300 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
  • @mistero's base36encode(): 3.62 µs ± 44.2 ns per loop
  • @user1036542's int2base: 10 µs ± 400 ns per loop (after fixing py37 compatibility)
  • @mbarkhau's int_to_base36(): 3.83 µs ± 28.8 ns per loop

All timings were mean ± std. dev. of 7 runs, 100000 loops each.

Update on 2023-04-14:

I wanted to try out perfpy.com, and here are my results for https://perfpy.com/288: enter image description here

akaihola
  • 26,309
  • 7
  • 59
  • 69
4

This works if you only care about positive integers.

def int_to_base36(num):
    """Converts a positive integer into a base36 string."""
    assert num >= 0
    digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'

    res = ''
    while not res or num > 0:
        num, i = divmod(num, 36)
        res = digits[i] + res
    return res

To convert back to int, just use int(num, 36). For a conversion of arbitrary bases see https://gist.github.com/mbarkhau/1b918cb3b4a2bdaf841c

Community
  • 1
  • 1
mbarkhau
  • 8,190
  • 4
  • 30
  • 34
4

If you are feeling functional

def b36_encode(i):
    if i < 0: return "-" + b36_encode(-i)
    if i < 36: return "0123456789abcdefghijklmnopqrstuvwxyz"[i]
    return b36_encode(i // 36) + b36_encode(i % 36)    

test

n = -919283471029384701938478
s = "-45p3wubacgd6s0fi"
assert int(s, base=36) == n
assert b36_encode(n) == s
Aaron Goldman
  • 829
  • 9
  • 7
  • If you want a one-liner: ```b36 = lambda n: "-" + b36(-n) if n < 0 else "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[n] if n < 36 else b36(n // 36) + b36(n % 36)``` then use e.g. ```b36(-12345)``` – Jamie Deith Dec 24 '21 at 09:13