108 votes

Conversion de la base 62

Comment convertir un nombre entier en base 62 (comme l'hexadécimal, mais avec ces chiffres : '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ').

J'ai essayé de trouver une bonne bibliothèque Python pour cela, mais elles semblent toutes occupées à convertir des chaînes de caractères. Le module Python base64 n'accepte que les chaînes de caractères et transforme un seul chiffre en quatre caractères. Je cherchais quelque chose qui ressemble à ce qu'utilisent les raccourcisseurs d'URL.

0 votes

On dirait que quelqu'un vient de trouver une idée de projet open source :) Faites-moi savoir si vous trouvez quelque chose ou si vous décidez de créer le vôtre...

1 votes

Si vous souhaitez créer des URL courtes, vous pouvez utiliser l'ensemble des caractères qui n'ont pas besoin d'être codés : fr.wikipedia.org/wiki/Percent-encoding#Types_de_caractères_URI . Cela fait 66 caractères.

0 votes

Je pense que je vais passer sur le point et le tilde, juste pour éviter la confusion des utilisateurs, mais le tiret et les underscores devraient être des ajouts intéressants, merci.

195voto

Baishampayan Ghose Points 9414

Il n'existe pas de module standard pour cela, mais j'ai écrit mes propres fonctions pour y parvenir.

BASE62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

def encode(num, alphabet):
    """Encode a positive number into Base X and return the string.

    Arguments:
    - `num`: The number to encode
    - `alphabet`: The alphabet to use for encoding
    """
    if num == 0:
        return alphabet[0]
    arr = []
    arr_append = arr.append  # Extract bound-method for faster access.
    _divmod = divmod  # Access to locals is faster.
    base = len(alphabet)
    while num:
        num, rem = _divmod(num, base)
        arr_append(alphabet[rem])
    arr.reverse()
    return ''.join(arr)

def decode(string, alphabet=BASE62):
    """Decode a Base X encoded string into the number

    Arguments:
    - `string`: The encoded string
    - `alphabet`: The alphabet to use for decoding
    """
    base = len(alphabet)
    strlen = len(string)
    num = 0

    idx = 0
    for char in string:
        power = (strlen - (idx + 1))
        num += alphabet.index(char) * (base ** power)
        idx += 1

    return num

Notez le fait que vous pouvez lui donner n'importe quel alphabet à utiliser pour l'encodage et le décodage. Si vous laissez le alphabet argument, vous allez avoir l'alphabet de 62 caractères défini sur la première ligne de code, et donc l'encodage/décodage vers/depuis la base 62.

J'espère que cela vous aidera.

PS - Pour les raccourcisseurs d'URL, j'ai constaté qu'il est préférable de laisser de côté quelques caractères déroutants comme 0Ol1oI, etc. J'utilise donc cet alphabet pour mes besoins de raccourcissement d'URL. "23456789abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"

Amusez-vous bien.

6 votes

+1 : Bien ! Cette méthode peut être étendue avec des caractères plus conviviaux pour les URL afin d'économiser un caractère ici et là. Les caractères que je connais sont sûrs : $-_.+!*'(),;/?:@&= Vous pouvez probablement utiliser d'autres caractères comme []~ etc.

0 votes

Merci, juste ce que je cherchais :)

0 votes

Oups, je pense que je vais le changer pour retourner 0 si num <= 0 :)

61voto

Wolph Points 28062

J'ai écrit une fois un script pour faire cela aussi, je pense que c'est assez élégant :)

import string
# Remove the `_@` below for base62, now it has 64 characters
BASE_LIST = string.digits + string.letters + '_@'
BASE_DICT = dict((c, i) for i, c in enumerate(BASE_LIST))

def base_decode(string, reverse_base=BASE_DICT):
    length = len(reverse_base)
    ret = 0
    for i, c in enumerate(string[::-1]):
        ret += (length ** i) * reverse_base[c]

    return ret

def base_encode(integer, base=BASE_LIST):
    if integer == 0:
        return base[0]

    length = len(base)
    ret = ''
    while integer != 0:
        ret = base[integer % length] + ret
        integer /= length

    return ret

Exemple d'utilisation :

for i in range(100):                                    
    print i, base_decode(base_encode(i)), base_encode(i)

9 votes

Cette version est considérablement plus rapide que la solution acceptée de Baishampayan. J'ai optimisé davantage en calculant la longueur en dehors de la fonction. Résultats des tests (100 000 itérations) : version-WoLpH : .403 .399 .399 .398 .398 | version-Baishampayan : 1.783 1.785 1.782 1.788 1.784. Cette version est environ 4x plus rapide.

0 votes

Si l'on utilise reversed(string) plus rapide que le tranchage string[::-1] dans la fonction base_decode.

1 votes

Il m'a fallu beaucoup de temps pour trouver cette question. Je ne savais pas que cela s'appelait la conversion base62. Bonne réponse.

11voto

Sepero Points 734

Si vous recherchez la plus grande efficacité (comme django), vous voudrez quelque chose comme ce qui suit. Ce code est une combinaison de méthodes efficaces de Baishampayan Ghose, WoLpH et John Machin.

# Edit this list of characters as desired.
BASE_ALPH = tuple("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
BASE_DICT = dict((c, v) for v, c in enumerate(BASE_ALPH))
BASE_LEN = len(BASE_ALPH)

def base_decode(string):
    num = 0
    for char in string:
        num = num * BASE_LEN + BASE_DICT[char]
    return num

def base_encode(num):
    if not num:
        return BASE_ALPH[0]

    encoding = ""
    while num:
        num, rem = divmod(num, BASE_LEN)
        encoding = BASE_ALPH[rem] + encoding
    return encoding

Vous pouvez également calculer votre dictionnaire à l'avance. (Note : L'encodage avec une chaîne de caractères montre plus d'efficacité qu'avec une liste, même avec des nombres très longs).

>>> timeit.timeit("for i in xrange(1000000): base.base_decode(base.base_encode(i))", setup="import base", number=1)
2.3302059173583984

Encodage et décodage d'un million de chiffres en moins de 2,5 secondes. (2.2Ghz i7-2670QM)

0 votes

On n'a pas nécessairement besoin du tuple() autour de BASE_ALPH au début. En Python, chaque chaîne est itérable. Cette caractéristique est bien sûr exploitée par enumerate() . Le code devient donc encore plus léger :)

7 votes

Hey origiNell, tu as raison de dire que le tuple() n'est pas nécessaire, mais sur mon système, il accélère le code d'environ 20%. Essayez de le tester sans le tuple() et voyez ce qui fonctionne le mieux pour vous. Merci :)

1 votes

Un point intéressant. C'est tout à fait logique puisque les tuples sont plus légers que les chaînes de caractères. Merci pour cet éclairage :) !

10voto

John Machin Points 39706

Le décodeur suivant fonctionne avec n'importe quelle base raisonnable, a une boucle beaucoup plus ordonnée, et donne un message d'erreur explicite quand il rencontre un caractère invalide.

def base_n_decoder(alphabet):
    """Return a decoder for a base-n encoded string
    Argument:
    - `alphabet`: The alphabet used for encoding
    """
    base = len(alphabet)
    char_value = dict(((c, v) for v, c in enumerate(alphabet)))
    def f(string):
        num = 0
        try:
            for char in string:
                num = num * base + char_value[char]
        except KeyError:
            raise ValueError('Unexpected character %r' % char)
        return num
    return f

if __name__ == "__main__":
    func = base_n_decoder('0123456789abcdef')
    for test in ('0', 'f', '2020', 'ffff', 'abqdef'):
        print test
        print func(test)

0 votes

Même si je ne l'utiliserai probablement jamais, je dois vous donner un coup de pouce pour votre créativité. Ce code m'a fait rire :)

0 votes

@Sepero : Qu'y a-t-il de si drôle ? C'est un logiciel industriel robuste et sérieux. Pas de retournement de Micky-Mouse avec un ** dans la boucle.

0 votes

Calme-toi, mon ami. Tu as raison. J'ai raté la vraie qualité de ta boucle interne parce qu'elle était enfouie dans des choses qui n'ont rien à voir avec la question (enveloppement, vérification des erreurs, tests unitaires).

3voto

Williham Totland Points 15798

Vous voulez probablement la base64, pas la base62. Il en existe une version compatible avec les URL, ce qui fait que les deux caractères de remplissage supplémentaires ne devraient pas poser de problème.

Le processus est assez simple ; considérez que la base64 représente 6 bits et qu'un octet ordinaire en représente 8. Attribuez une valeur de 000000 à 111111 à chacun des 64 caractères choisis, et assemblez les 4 valeurs pour obtenir un ensemble de 3 octets en base256. Répétez l'opération pour chaque ensemble de 3 octets, en ajoutant à la fin le caractère de remplissage de votre choix (0 est généralement utile).

5 votes

Les méthodes d'encodage standard Python base64 ne conviennent pas vraiment aux URL courtes, car elles sont optimisées pour l'encodage des octets (c'est-à-dire les chaînes de caractères/lettres), et produiront des résultats plus longs que le simple décalage de la valeur numérique.

0 votes

@mikl Bien sûr, le module base64 de Python n'est peut-être pas adapté à la génération d'URL courtes, mais toutes les méthodes d'encodage de Python fonctionnent en réalité sur des séquences de nombres en base-256. Les octets sont en réalité des "chaînes" encodées en base-256. Python 2.x traite les chaînes de caractères comme une séquence d'octets, alors que Python 3.x (qui fait ce qu'il faut) traite les chaînes de caractères comme de l'Unicode. Ainsi, b'foobar' n'est en réalité qu'une façon fantaisiste d'écrire [102, 111, 111, 98, 97, 114] ou [0x66,0x6f,0x6f,0x62,0x61,0x72] ou b'. \x66\x6f\x6f\x62\x61\x72 qui, sans surprise, est la représentation en base 256. Les octets ne sont pas des chaînes de caractères ou des lettres. Les octets sont des octets. =)

0 votes

@yesudeep : Donc, les octets sont des octets et où voulez-vous en venir exactement ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X