Как преобразовать целое число в самую короткую строку url-safe в Python?



Я хочу самый короткий способ представления целого числа в URL. Например, 11234 можно сократить до '2be2' с помощью шестнадцатеричного числа. Поскольку base64 использует 64-символьную кодировку, должно быть возможно представить целое число в base64, используя даже меньше символов, чем шестнадцатеричное. Проблема в том, что я не могу найти самый чистый способ преобразования целого числа в base64 (и обратно) с помощью Python.



модуль base64 имеет методы для работы с bytestrings-так что, возможно, один решением было бы преобразовать целое число в двоичное представление в виде строки в Python... но я тоже не знаю, как это сделать.

724   14  

14 ответов:

этот ответ аналогичен по духу Дугласу лидеру, со следующими изменениями:

  • он не использует фактический Base64, поэтому нет символов заполнения
  • вместо преобразования числа сначала в байтовую строку (база 256), он преобразует его непосредственно в базу 64, которая имеет то преимущество, что позволяет представлять отрицательные числа, используя знаковый символ.

    import string
    ALPHABET = string.ascii_uppercase + string.ascii_lowercase + \
               string.digits + '-_'
    ALPHABET_REVERSE = dict((c, i) for (i, c) in enumerate(ALPHABET))
    BASE = len(ALPHABET)
    SIGN_CHARACTER = '$'
    
    def num_encode(n):
        if n < 0:
            return SIGN_CHARACTER + num_encode(-n)
        s = []
        while True:
            n, r = divmod(n, BASE)
            s.append(ALPHABET[r])
            if n == 0: break
        return ''.join(reversed(s))
    
    def num_decode(s):
        if s[0] == SIGN_CHARACTER:
            return -num_decode(s[1:])
        n = 0
        for c in s:
            n = n * BASE + ALPHABET_REVERSE[c]
        return n
    

    >>> num_encode(0)
    'A'
    >>> num_encode(64)
    'BA'
    >>> num_encode(-(64**5-1))
    '$_____'

несколько побочных Примечания:

  • вы могли бы (умеренно) увеличьте удобочитаемость базы-64 числа, поставив строку.цифры сначала в алфавите (и делая знак символа '-'); я выбрал порядок, который я сделал на основе Urlsafe_b64encode Python.
  • если вы кодируете много отрицательных чисел,вы можете увеличить эффективность, используя знаковый бит или дополнение одного/двух вместо знака.
  • вы должны быть в состоянии легко адаптируйте этот код к различным основаниям, изменив алфавит, либо ограничив его только буквенно-цифровыми символами, либо добавив дополнительные символы "URL-safe".
  • Я бы порекомендовал против использование представления, отличного от базы 10 в URI в большинстве случаев-это добавляет сложности и делает отладку сложнее без значительной экономии по сравнению с накладными расходами HTTP-если вы не собираетесь что-то TinyURL-esque.

все ответы, данные относительно Base64, являются очень разумными решениями. Но они технически неверны. Чтобы преобразовать целое число в самая короткая безопасная строка URL возможно, то, что вы хотите-это база 66 (есть 66 URL безопасные символы).

этот код выглядит так:

from io import StringIO
import urllib

BASE66_ALPHABET = u"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_.~"
BASE = len(BASE66_ALPHABET)

def hexahexacontadecimal_encode_int(n):
    if n == 0:
        return BASE66_ALPHABET[0].encode('ascii')

    r = StringIO()
    while n:
        n, t = divmod(n, BASE)
        r.write(BASE66_ALPHABET[t])
    return r.getvalue().encode('ascii')[::-1]

вот полная реализация с исходным кодом и готовым к установке пакетом pip:

https://github.com/aljungberg/hexahexacontadecimal

вы, вероятно, не хотите реальной кодировки base64 для этого - он добавит заполнение и т. д., потенциально даже приводя к большим строкам, чем hex для небольших чисел. Если нет необходимости взаимодействовать с чем-либо еще, просто используйте свою собственную кодировку. Например. вот функция, которая будет кодировать любую базу (обратите внимание, что цифры на самом деле хранятся наименее значимыми первыми, чтобы избежать дополнительных обратных вызовов:

def make_encoder(baseString):
    size = len(baseString)
    d = dict((ch, i) for (i, ch) in enumerate(baseString)) # Map from char -> value
    if len(d) != size:
        raise Exception("Duplicate characters in encoding string")

    def encode(x):
        if x==0: return baseString[0]  # Only needed if don't want '' for 0
        l=[]
        while x>0:
            l.append(baseString[x % size])
            x //= size
        return ''.join(l)

    def decode(s):
        return sum(d[ch] * size**i for (i,ch) in enumerate(s))

    return encode, decode

# Base 64 version:
encode,decode = make_encoder("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")

assert decode(encode(435346456456)) == 435346456456

Это имеет то преимущество, что вы можете использовать все, что вы хотите, просто добавление соответствующего символы в базовой строке кодера.

обратите внимание, что прибыль для больших баз не будет таким же большим. база 64 уменьшит размер только до 2/3rds базы 16 (6 бит/чар вместо 4). Каждое удвоение добавляет только один бит на символ. Если у вас нет реальной потребности в компактных вещах, просто использование hex, вероятно, будет самым простым и быстрым вариантом.

кодирование n:

data = ''
while n > 0:
    data = chr(n & 255) + data
    n = n >> 8
encoded = base64.urlsafe_b64encode(data).rstrip('=')

раскодировать s:

data = base64.urlsafe_b64decode(s + '===')
decoded = 0
while len(data) > 0:
    decoded = (decoded << 8) | ord(data[0])
    data = data[1:]

в том же духе, что и другие для некоторой "оптимальной" кодировки, вы можете использовать 73 символы в соответствии с RFC 1738 (на самом деле 74, если вы считаете "+" пригодным для использования):

alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_`\"!$'()*,-."
encoded = ''
while n > 0:
    n, r = divmod(n, len(alphabet))
    encoded = alphabet[r] + encoded

и декодирования:

decoded = 0
while len(s) > 0:
    decoded = decoded * len(alphabet) + alphabet.find(s[0])
    s = s[1:]

самая легкая, это преобразовать байтовую строку для веб-просмотра в base64:

import base64
output = base64.urlsafe_b64encode(s)

хитрый бит-это первый шаг-преобразование целого числа в байтовую строку.

Если ваши целые числа малы, вам лучше использовать шестнадцатеричное кодирование - см. saua

в противном случае (hacky рекурсивная версия):

def convertIntToByteString(i):
    if i == 0:
        return ""
    else:
        return convertIntToByteString(i >> 8) + chr(i & 255)

вы не хотите кодировку base64, вы хотите представить базовую цифру 10 в цифровой базе X.

Если вы хотите, чтобы ваша базовая цифра 10 была представлена в 26 доступных буквах, вы можете использовать:http://en.wikipedia.org/wiki/Hexavigesimal. (Вы можете расширить этот пример для гораздо большей базы, используя все законные символы url)

вы должны по крайней мере быть в состоянии получить базу 38 (26 букв, 10 цифр, +, _)

Base64 занимает 4 байта / символов для кодирования 3 байта и может кодировать только кратные 3 байта (и добавляет заполнение в противном случае).

таким образом, представление 4 байт (ваш средний int) в Base64 займет 8 байт. Кодирование же 4 байта в шестнадцатеричном формате также займет 8 байт. Так что вы ничего не получите за один int.

немного hacky, но это работает:

def b64num(num_to_encode):
  h = hex(num_to_encode)[2:]     # hex(n) returns 0xhh, strip off the 0x
  h = len(h) & 1 and '0'+h or h  # if odd number of digits, prepend '0' which hex codec requires
  return h.decode('hex').encode('base64') 

вы можете заменить вызов .кодировать ('base64') с чем-то в модуле base64, например urlsafe_b64encode ()

Я поддерживаю небольшую библиотеку с именем zbase62:http://pypi.python.org/pypi/zbase62

С его помощью вы можете конвертировать из объекта Python 2 str в кодированную строку base-62 и наоборот:

Python 2.7.1+ (r271:86832, Apr 11 2011, 18:13:53) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> d = os.urandom(32)
>>> d
'C$\x8f\xf9\x92NV\x97\x13H\xc7F\x0c\x0f\x8d9}\xf5.u\xeeOr\xc2V\x92f\x1b=:\xc3\xbc'
>>> from zbase62 import zbase62
>>> encoded = zbase62.b2a(d)
>>> encoded
'Fv8kTvGhIrJvqQ2oTojUGlaVIxFE1b6BCLpH8JfYNRs'
>>> zbase62.a2b(encoded)
'C$\x8f\xf9\x92NV\x97\x13H\xc7F\x0c\x0f\x8d9}\xf5.u\xeeOr\xc2V\x92f\x1b=:\xc3\xbc'

тем не менее, вам все равно нужно конвертировать из integer в str. Это приходит встроенный в Python 3:

Python 3.2 (r32:88445, Mar 25 2011, 19:56:22)
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> d = os.urandom(32)
>>> d
b'\xe4\x0b\x94|\xb6o\x08\xe9oR\x1f\xaa\xa8\xe8qS3\x86\x82\t\x15\xf2"\x1dL%?\xda\xcc3\xe3\xba'
>>> int.from_bytes(d, 'big')
103147789615402524662804907510279354159900773934860106838120923694590497907642
>>> x= _ 
>>> x.to_bytes(32, 'big')
b'\xe4\x0b\x94|\xb6o\x08\xe9oR\x1f\xaa\xa8\xe8qS3\x86\x82\t\x15\xf2"\x1dL%?\xda\xcc3\xe3\xba'

для преобразования из int в байты и наоборот в Python 2, Насколько я знаю, нет удобного, стандартного способа. Наверное, мне лучше скопируйте некоторые реализации, такие как эта: https://github.com/warner/foolscap/blob/46e3a041167950fa93e48f65dcf106a576ed110e/foolscap/banana.py#L41 в zbase62 для вашего удобства.

Если вы ищете способ сокращения целочисленное представление с помощью base64, я думаю, вам нужно искать в другом месте. Когда вы кодируете что-то с base64, он не становится короче, на самом деле он становится длиннее.

например, 11234, закодированный с помощью base64, даст MTEyMzQ=

при использовании base64 вы упустили из виду тот факт, что вы не конвертируете только цифры (0-9) в 64-символьную кодировку. Вы преобразуете 3 байта в 4 байта, так что вы гарантированная строка в кодировке base64 будет на 33.33% длиннее.

мне нужно было целое число со знаком, поэтому я закончил с:

import struct, base64

def b64encode_integer(i):
   return base64.urlsafe_b64encode(struct.pack('i', i)).rstrip('=\n')

пример:

>>> b64encode_integer(1)
'AQAAAA'
>>> b64encode_integer(-1)
'_____w'
>>> b64encode_integer(256)
'AAEAAA'

Я работаю над созданием пакета pip для этого.

Я рекомендую вам использовать мой bases.py https://github.com/kamijoutouma/bases.py который был вдохновлен основами.js

from bases import Bases
bases = Bases()

bases.toBase16(200)                // => 'c8'
bases.toBase(200, 16)              // => 'c8'
bases.toBase62(99999)              // => 'q0T'
bases.toBase(200, 62)              // => 'q0T'
bases.toAlphabet(300, 'aAbBcC')    // => 'Abba'

bases.fromBase16('c8')               // => 200
bases.fromBase('c8', 16)             // => 200
bases.fromBase62('q0T')              // => 99999
bases.fromBase('q0T', 62)            // => 99999
bases.fromAlphabet('Abba', 'aAbBcC') // => 300

см.https://github.com/kamijoutouma/bases.py#known-basesalphabets для каких баз можно использовать

для вашего случая

Я рекомендую вам использовать либо 32, 58 и 64

Base-64 предупреждение: кроме того, существует несколько различных стандарты, отступы в настоящее время не добавляются и длины линий не отслеживаются. Не рекомендуется использовать с API, которые ожидают формальную базу-64 строки!

то же самое касается базы 66, которая в настоящее время не поддерживается обеими базами.js и bases.py но это может быть в будущее

Я бы пошел "кодировать целое число как двоичную строку, а затем base64 кодировать этот" метод, который вы предлагаете, и я бы сделал это с помощью struct:

>>> import struct, base64
>>> base64.b64encode(struct.pack('l', 47))
'LwAAAA=='
>>> struct.unpack('l', base64.b64decode(_))
(47,)

редактировать еще раз: Чтобы удалить лишние 0 на числах, которые слишком малы, чтобы нуждаться в полной 32-разрядной точности, попробуйте следующее:

def pad(str, l=4):
    while len(str) < l:
        str = '\x00' + str
    return str

>>> base64.b64encode(struct.pack('!l', 47).replace('\x00', ''))
'Lw=='
>>> struct.unpack('!l', pad(base64.b64decode('Lw==')))
(47,)

чистый python, без зависимостей, без кодирования байтовых строк и т. д. , просто превратив базу 10 int в базу 64 int с правильными символами RFC 4648:

def tetrasexagesimal(number):
    out=""
    while number>=0:
        if number == 0:
            out = 'A' + out
            break
        digit = number % 64
        out = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[digit] + out
        number /= 64 # //= 64 for py3 (thank spanishgum!)
        if number == 0:
            break
    return out

tetrasexagesimal(1)

Comments

    Ничего не найдено.