Python элегантная обратная функция от int(string,base)

Python позволяет преобразовывать строки в целые числа, используя любую базу в диапазоне [2,36], используя:

int(string,base)

Я ищу элегантную обратную функцию, которая принимает целое число и основание и возвращает строку

например

>>> str_base(224,15)
'ee'

у меня есть следующее решение:

def digit_to_char(digit):
    if digit < 10: return chr(ord('0') + digit)
    else: return chr(ord('a') + digit - 10)

def str_base(number,base):
    if number < 0:
        return '-' + str_base(-number,base)
    else:
        (d,m) = divmod(number,base)
        if d:
            return str_base(d,base) + digit_to_char(m)
        else:
            return digit_to_char(m)

примечание: digit_to_char() работает для базисов <= 169, произвольно используя символы ascii после 'z' в качестве цифр для базисов выше 36

есть ли встроенная функция Python, библиотечная функция или более элегантная обратная функция от int(string,base)?

12 ответов

Решение

Этот поток имеет несколько примеров реализации.

На самом деле, я думаю, что ваше решение выглядит довольно хорошо, оно даже рекурсивно, что здесь как-то приятно.

Я все еще упростил бы это, чтобы удалить else, но это, вероятно, личный стиль. Я думаю if foo: return очень ясно, и не нуждается в else после этого, чтобы было понятно, что это отдельная ветка.

def digit_to_char(digit):
    if digit < 10:
        return str(digit)
    return chr(ord('a') + digit - 10)

def str_base(number,base):
    if number < 0:
        return '-' + str_base(-number, base)
    (d, m) = divmod(number, base)
    if d > 0:
        return str_base(d, base) + digit_to_char(m)
    return digit_to_char(m)

Я упростил случай 0-9 в digit_to_char(), Я думаю str() яснее, чем chr(ord()) построить. Чтобы максимизировать симметрию с >= 10 дело ord() может быть учтено, но я не стал беспокоиться, так как это добавит черту, и краткость будет лучше.:)

Возможно, это не должно быть ответом, но для некоторых это может быть полезно: встроенный format Функция конвертирует числа в строку по нескольким базам:

>>> format(255, 'b') # base 2
'11111111'
>>> format(255, 'd') # base 10
'255'
>>> format(255, 'o') # base 8
'377'
>>> format(255, 'x') # base 16
'ff'

Если вы используете Numpy, есть numpy.base_repr,

Вы можете прочитать код в numpy/core/numeric.py, Коротко и элегантно

Приведенные выше ответы действительно хороши. Это помогло мне создать прототип алгоритма, который я должен был реализовать на C

Я хотел бы придумать небольшое изменение (я использовал), чтобы преобразовать десятичную в базу символов

Я также игнорировал отрицательные значения только для краткости и факта, что математически неверно -> другие правила для модульной арифметики -> другие математические вычисления, если вы используете двоичные, октановые или шестнадцатеричные -> diff в значениях без знака и со знаком

def str_base(number, base):
   (d,m) = divmod(number,len(base))
   if d > 0:
      return str_base(d,base)+base[m]
   return base[m]

что приводит к следующему выводу

>>> str_base(13,'01')
'1101'
>>> str_base(255,'01')
'11111111'
>>> str_base(255,'01234567')
'377'
>>> str_base(255,'0123456789')
'255'
>>> str_base(255,'0123456789abcdef')
'ff'
>>> str_base(1399871903,'_helowrd')
'hello_world'

если вы хотите заполнить символом нулевого пропппера, вы можете использовать

symbol_space = 'abcdest'

>>> str_base(734,symbol_space).rjust(0,symbol_space[0])
'catt'
>>> str_base(734,symbol_space).rjust(6,symbol_space[0])
'aacatt'

Просмотрите это.

def int2str(num, base=16, sbl=None):
    if not sbl:
        sbl = '0123456789abcdefghijklmnopqrstuvwxyz'
    if len(sbl) < 2:
        raise ValueError, 'size of symbols should be >= 2'
    if base < 2 or base > len(sbl):
        raise ValueError, 'base must be in range 2-%d' % (len(sbl))

    neg = False
    if num < 0:
        neg = True
        num = -num

    num, rem = divmod(num, base)
    ret = ''
    while num:
        ret = sbl[rem] + ret
        num, rem = divmod(num, base)
    ret = ('-' if neg else '') + sbl[rem] + ret

    return ret

Похоже, это моё время сиять. Хотите верьте, хотите нет, ниже приведен портированный и модифицированный код Scratch, который я написал почти три года назад, чтобы увидеть, насколько быстро я могу преобразовать из денари в шестнадцатеричное.

Проще говоря, он работает, сначала беря целое число, основание и необязательную сопровождающую строку цифр, а затем вычисляя каждую цифру преобразованного целого числа, начиная с наименее значимого.

def int2base(num, base, abc="0123456789abcdefghijklmnopqrstuvwxyz"):
  if num < 0:
    return '-' + int2base(-num, base, abc)

  else:
    output = abc[num % base] # rightmost digit

    while num >= base:
      num //= base # move to next digit to the left
      output = abc[num % base] + output # this digit

    return output

На моем собственном ПК этот код смог выполнить 10 миллионов итераций, используя диапазон ввода 0-9999 и базовый 36, последовательно менее 5 секунд. Используя тот же тест, я обнаружил, что это по крайней мере на 4 секунды быстрее, чем любой другой ответ до сих пор.

>>> timeit.timeit(lambda: [int2base(n, 36) for n in range(10000)], number=1000)
4.883068453882515

digit_to_char может быть реализовано так:

def digit_to_char(digit):
    return (string.digits + string.lowercase)[digit]

Когда-то я написал свою собственную функцию с той же целью, но сейчас это очень сложно.

from math import log, ceil, floor
from collections import deque
from itertools import repeat
from string import uppercase, digits
import re

__alphanumerals = (digits + uppercase)

class InvalidBaseError(ValueError): pass
class FloatConvertError(ValueError): pass
class IncorrectBaseError(ValueError): pass

def getbase(number, base=2, frombase = 10):
    if not frombase == 10:
        number = getvalue(number, frombase)
        #getvalue is also a personal function to replicate int(number, base)

    if 1 >= base or base >= len(__alphanumerals) or not floor(base) == base:
        raise InvalidBaseError("Invalid value: {} entered as base to convert
          to. \n{}".format(base,
        "Assert that the base to convert to is a decimal integer."))

    if isinstance(number, str):
        try:
            number = atof(number)
        except ValueError:
            #The first check of whether the base is 10 would have already corrected the number
            raise IncorrectBaseError("Incorrect base passed as base of number -> number: {} base: {}".format(number, frombase))
    #^ v was supporting float numbers incase number was the return of another operation
    if number > floor(number):
        raise FloatConvertError("The number to be converted must not be a float. {}".format(number))

    isNegative = False
    if number < 0:
        isNegative = True
        number = abs(number)

    logarithm = log(number, base) if number else 0 #get around number being zero easily

    ceiling = int(logarithm) + 1

    structure = deque(repeat(0, ceiling), maxlen = ceiling)

    while number:
        if number >= (base ** int(logarithm)):
            acceptable_digit = int(number / (base ** floor(logarithm)))
            structure.append(acceptable_digit if acceptable_digit < 10 else     __alphanumerals[acceptable_digit])
            number -= acceptable_digit * (base ** floor(logarithm))
        else:
            structure.append(0)

        logarithm -= 1

    while structure[0] == 0:
        #the result needs trailing zeros
        structure.rotate(-1)

    return ("-" if isNegative and number else "") + reduce(lambda a, b: a + b, map(lambda a: str(a), structure))

Однако я думаю, что функция strbase должна поддерживать только базы>= 2 и<= 36, чтобы предотвратить конфликт с другими инструментами в python, такими как int. Кроме того, я думаю, что только один регистр алфавитов следует использовать, предпочтительно снова в верхнем регистре, чтобы предотвратить конфликт с другими функциями, такими как int, так как он будет считать "a" и "A" равными 10.

from string import uppercase

dig_to_chr = lambda num: str(num) if num < 10 else uppercase[num - 10]

def strbase(number, base):
    if not 2 <= base <= 36:
        raise ValueError("Base to convert to must be >= 2 and <= 36")

    if number < 0:
        return "-" + strbase(-number, base)

    d, m = divmod(number, base)
    if d:
        return strbase(d, base) + dig_to_chr(m)

    return dig_to_chr(m)

Подводя итог тому, что я смог найти в Stackoverflow, самый короткий путь выглядит следующим образом:

      base = lambda num, b, numerals="0123456789abcdefghijklmnopqrstuvwxyz": numerals[0] if num == 0 else base(num // b, b, numerals).lstrip(numerals[0]) + numerals[num % b]
      >>> base(255, 7)
>>> 513

Но за краткость приходится платить — нет проверки ошибок, а также возможно переполнение рекурсии.

Вот мое решение:

def int2base(a, base, numerals="0123456789abcdefghijklmnopqrstuvwxyz"):
     baseit = lambda a=a, b=base: (not a) and numerals[0] or baseit(a-a%b,b*base)+numerals[a%b%(base-1) or (a%b) and (base-1)]
     return baseit()

объяснение

В любой базе каждое число равно a1+a2*base**2+a3*base**3..., "Миссия" - найти все.

Для каждого N=1,2,3...код изолирует aN*base**N "mouduling" по б для b=base**(N+1) которые нарезают все a больше чем N, и нарезают все a, что их серийный номер меньше чем N, уменьшая каждый раз, когда функция вызывается текущим aN*base**N,

Base%(base-1)==1 следовательно base**p%(base-1)==1 и поэтому q*base^p%(base-1)==q только с одним исключением, когда q=base-1 который возвращается 0, Чтобы это исправить, на случай, если он вернется 0, функция проверяет это 0 с начала.


преимущества

В этом примере есть только одно умножение (вместо деления) и несколько экземпляров модуля, которые занимают относительно небольшое количество времени.

Вот рекурсивная функция:

def encode(nIn, nBase):
   n = nIn // nBase
   s = '0123456789abcdefghijklmnopqrstuvwxyz'[nIn % nBase]
   return encode(n, nBase) + s if n > 0 else s

n = 1577858399
s = encode(n, 36)
print(s == 'q3ezbz')

numpy.base_repr - неплохое решение, однако в некоторых случаях нам нужна строка фиксированной длины с ведущими нулями.

def base_repr(x: int, base: int, length: int):
    from numpy import base_repr
    from math import log, ceil
    s = base_repr(x, base, length - ceil(log(x, base)))
    return s
Другие вопросы по тегам