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