Подтвердить что ты не робот

Как преобразовать целое число в кратчайшую безопасную строку в Python?

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

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

4b9b3361

Ответ 1

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

  • Он не использует фактический Base64, поэтому нет пронумерованных символов
  • Вместо того, чтобы сначала преобразовать номер в байтовую строку (base 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 человека, поместив string.digits сначала в алфавит (и сделав знак-символ "-" ); Я выбрал порядок, который я сделал на основе Python urlsafe_b64encode.
  • Если вы кодируете множество отрицательных чисел, вы можете повысить эффективность с помощью знакового бита или одного/двух дополнений вместо знакового символа.
  • Вы должны легко адаптировать этот код к различным базам, изменив алфавит, либо ограничив его только буквенно-цифровыми символами, либо добавив дополнительные "URL-безопасные" символы.
  • В большинстве случаев я бы рекомендовал не использовать представление, отличное от базы 10 в URI, - это добавляет сложности и упрощает отладку без существенной экономии по сравнению с накладными расходами HTTP, если вы не собираетесь что-то делать с TinyURL-esque.

Ответ 2

Все ответы, касающиеся 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/hhc

Ответ 3

Вероятно, вам не нужна настоящая кодировка 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 бит / char вместо 4). Каждое удвоение добавляет еще один бит на символ. Если у вас нет реальной потребности в компактности, просто использование hex будет, вероятно, самым простым и быстрым вариантом.

Ответ 4

Чтобы закодировать 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:]

Ответ 5

Легкий бит преобразует байтовую строку в web-safe base64:

import base64
output = base64.urlsafe_b64encode(s)

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

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

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

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

Ответ 6

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

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

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

Ответ 7

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

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

Ответ 8

немного взломанный, но он работает:

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') 

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

Ответ 9

Я поддерживаю небольшую библиотеку с именем 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 для вашего удобства.

Ответ 10

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

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

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

Ответ 11

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

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'

Ответ 12

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

Я рекомендую использовать мой base.py https://github.com/kamijoutouma/bases.py, который был вдохновлен base.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, которые ожидают формальные строки base-64!

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

Ответ 13

Я бы запустил целое число encode в виде двоичной строки, а затем base64 encode, что 'метод, который вы предлагаете, и я бы сделал это, используя 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,)

Ответ 14

Чистый питон, отсутствие зависимостей, отсутствие кодирования строк байтов и т.д., просто превращая базу 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)