Je veux la manière la plus courte possible de représenter un entier dans une URL. Par exemple, 11234 peut être raccourci à "2be2" en utilisant hexadécimal. Étant donné que l'utilisation de base64 est un codage de 64 caractères, il devrait être possible de représenter un entier dans base64 en utilisant encore moins de caractères que hexadécimal. Le problème est que je ne peux pas trouver la manière la plus propre de convertir un entier en base64 (et vice-versa) en utilisant Python.
Le module base64 a des méthodes pour traiter les bytestrings - donc peut-être une solution serait de convertir un entier en sa représentation binaire comme une chaîne Python ... mais je ne sais pas comment faire cela Soit.
Cette réponse est similaire dans son esprit à celle de Douglas Leeder, avec les modifications suivantes:
Au lieu de convertir d'abord le nombre en une chaîne d'octets (base 256), il le convertit directement en base 64, ce qui a l'avantage de vous permettre de représenter des nombres négatifs en utilisant un caractère de signe.
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))
'$_____'
Quelques notes annexes:
Toutes les réponses données concernant Base64 sont des solutions très raisonnables. Mais ils sont techniquement incorrects. Pour convertir un nombre entier en chaîne de sécurité URL la plus courte possible, ce que vous voulez est la base 66 (il y a 66 caractères de sécurité URL ).
Ce code ressemble à ceci:
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]
Voici une implémentation complète d'un schéma comme celui-ci, prêt à l'emploi en tant que package installable pip:
Vous ne voulez probablement pas de véritable encodage base64 pour cela - cela ajoutera du rembourrage, etc., ce qui pourrait même entraîner des chaînes plus grandes que hex ne le ferait pour les petits nombres. S'il n'est pas nécessaire d'interagir avec autre chose, utilisez simplement votre propre encodage. Par exemple. voici une fonction qui encodera dans n'importe quelle base (notez que les chiffres sont en fait stockés les moins significatifs en premier pour éviter les appels reverse () supplémentaires:
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
Cela a l'avantage que vous pouvez utiliser la base de votre choix, simplement en ajoutant des caractères appropriés à la chaîne de base de l'encodeur.
Notez que les gains pour les bases plus importantes ne seront pas aussi importants cependant. la base 64 ne réduira la taille qu'à 2/3 de la base 16 (6 bits/caractère au lieu de 4). Chaque doublement n'ajoute qu'un bit de plus par caractère. À moins que vous n'ayez vraiment besoin de compacter les choses, l'utilisation de hexagone sera probablement l'option la plus simple et la plus rapide.
Pour coder n
:
data = ''
while n > 0:
data = chr(n & 255) + data
n = n >> 8
encoded = base64.urlsafe_b64encode(data).rstrip('=')
Pour décoder s
:
data = base64.urlsafe_b64decode(s + '===')
decoded = 0
while len(data) > 0:
decoded = (decoded << 8) | ord(data[0])
data = data[1:]
Dans le même esprit que les autres pour certains encodages "optimaux", vous pouvez utiliser 7 caractères selon la RFC 1738 (en fait 74 si vous comptez "+" comme utilisable):
alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_`\"!$'()*,-."
encoded = ''
while n > 0:
n, r = divmod(n, len(alphabet))
encoded = alphabet[r] + encoded
et le décodage:
decoded = 0
while len(s) > 0:
decoded = decoded * len(alphabet) + alphabet.find(s[0])
s = s[1:]
Le plus simple est de convertir la chaîne d'octets en base64 Web sécurisée:
import base64
output = base64.urlsafe_b64encode(s)
Le bit délicat est la première étape - convertir l'entier en une chaîne d'octets.
Si vos entiers sont petits, vous feriez mieux de les encoder en hexadécimal - voir saua
Sinon (version récursive hacky):
def convertIntToByteString(i):
if i == 0:
return ""
else:
return convertIntToByteString(i >> 8) + chr(i & 255)
Vous ne voulez pas d'encodage en base64, vous voulez représenter un chiffre de base 10 en base numérique X.
Si vous voulez que votre chiffre de base 10 soit représenté dans les 26 lettres disponibles, vous pouvez utiliser: http://en.wikipedia.org/wiki/Hexavigesimal . (Vous pouvez étendre cet exemple pour une base beaucoup plus grande en utilisant tous les caractères d'URL autorisés)
Vous devriez au moins pouvoir obtenir la base 38 (26 lettres, 10 chiffres, +, _)
Base64 prend 4 octets/caractères pour encoder 3 octets et ne peut encoder que des multiples de 3 octets (et ajoute un remplissage sinon).
Donc, représenter 4 octets (votre entier moyen) en Base64 prendrait 8 octets. Encoder les mêmes 4 octets en hexadécimal prendrait également 8 octets. Vous ne gagneriez donc rien pour un seul int.
Je gère une petite bibliothèque nommée zbase62: http://pypi.python.org/pypi/zbase62
Avec lui, vous pouvez convertir un objet Python 2 str en une chaîne codée en base 62 et vice versa:
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'
Cependant, vous devez toujours convertir un entier en str. Cela vient intégré à 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'
Pour convertir de int en octets et vice versa en Python 2, il n'y a pas de méthode standard pratique pour autant que je sache. Je suppose que je devrais peut-être copier une implémentation, comme celle-ci: https://github.com/warner/foolscap/blob/46e3a041167950fa93e48f65dcf106a576ed110e/foolscap/banana.py#L41 dans zbase62 pour votre commodité.
un peu hacky, mais ça marche:
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')
vous pouvez remplacer l'appel à .encode ('base64') par quelque chose dans le module base64, tel que urlsafe_b64encode ()
Si vous cherchez un moyen de raccourcir la représentation entière en utilisant base64, je pense que vous devez chercher ailleurs. Lorsque vous encodez quelque chose avec base64, il ne raccourcit pas, en fait il s'allonge.
Par exemple. 11234 codé avec base64 donnerait MTEyMzQ =
Lorsque vous utilisez base64, vous avez ignoré le fait que vous ne convertissez pas uniquement les chiffres (0-9) en un codage de 64 caractères. Vous convertissez 3 octets en 4 octets, vous êtes donc assuré que votre chaîne encodée en base64 serait 33,33% plus longue.
J'avais besoin d'un entier signé, alors j'ai fini par choisir:
import struct, base64
def b64encode_integer(i):
return base64.urlsafe_b64encode(struct.pack('i', i)).rstrip('=\n')
Exemple:
>>> b64encode_integer(1)
'AQAAAA'
>>> b64encode_integer(-1)
'_____w'
>>> b64encode_integer(256)
'AAEAAA'
Je travaille sur la création d'un package pip pour cela.
Je vous recommande d'utiliser mon bases.py https://github.com/kamijoutouma/bases.py qui a été inspiré par bases.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
se référer à https://github.com/kamijoutouma/bases.py#known-basesalphabets pour quelles bases sont utilisables
Pour votre cas
Je vous recommande d'utiliser soit la base 32, 58 ou 64
Avertissement Base-64: en plus de plusieurs normes différentes, le remplissage n'est pas actuellement ajouté et les longueurs de ligne ne sont pas suivies. Non recommandé pour une utilisation avec des API qui attendent des chaînes formelles en base 64!
Il en va de même pour la base 66 qui n'est actuellement pas prise en charge par bases.js et bases.py, mais cela pourrait dans le futur
Je choisirais la méthode 'encoder un entier sous forme de chaîne binaire, puis encoder en base64', et je le ferais en utilisant struct:
>>> import struct, base64
>>> base64.b64encode(struct.pack('l', 47))
'LwAAAA=='
>>> struct.unpack('l', base64.b64decode(_))
(47,)
Modifier à nouveau: pour supprimer les 0 supplémentaires sur les nombres trop petits pour avoir besoin d'une précision complète de 32 bits, essayez ceci:
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,)
Pure python, pas de dépendances, pas d'encodage de chaînes d'octets, etc., juste transformer une base 10 int en base 64 int avec les bons caractères 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)