2013-11-15 2 views
7

Я понимаю, что одним из способов работы с цифрами числа в Python является преобразование числа в строку, а затем использование строковых методов для срезания результирующего «число» в группы «цифр». Например, предположим, у меня есть функция prime, которая проверяет простоты, я могу подтвердить, что целое число п является одновременно левым и правым truncatable prime сИспользует `str` правильную идиому для работы с цифрами в Python

all(prime(int(str(n)[:-i])) and prime(int(str(n)[i:])) for i in range(1, len(str(n)))) 

Этот метод включает первый преобразование н в строку так, чтобы его можно нарезать и преобразовать этот срез обратно в целое число, чтобы можно было проверить его примитивность. Возможно, это моя история со статически типизированными языками или смутная идея о том, что строки являются «дорогими» или опытными языками, которые включают встроенные функции для подобных манипуляций (например, Mathematica'sIntegerDigits и FromDigits); но мне осталось интересно, правильно ли это решать такие задачи.

Является конверсией между укусами и цифрами правильный или даже единственный подход для доступа к цифрам в Python. Существуют ли более эффективные подходы?

+0

Из любопытства, это для проблем на [Project Euler] (http://projecteuler.net)? Мне нравится этот сайт! – Jud

+3

Для очень больших чисел и/или многочисленных повторов одним из способов сокращения расходов является создание переменной, которая содержит строчную версию номера. Таким образом, вы не называете 'str' дважды. – iCodez

+0

iCodez: Хороший совет. – orome

ответ

1

Это всегда был мой подход, и он отлично работал, хотя я никогда не проводил много испытаний на скорость. Он работает особенно хорошо, когда требуется перебирать перестановки/комбинации цифр, поскольку вы можете создавать такие строки с функциями в пакете itertools.

Существуют, конечно, другие методы, которые включают в себя менее простые математические операции, но если скорость абсолютно невелика, я чувствую, что строковый метод является самым Pythonic.


Вот, к примеру, является более математический подход, где а и Ь индексируются с правой стороны (то есть те, место 0, десятки место 1, и т.д.):

def getSubdigits(n, a, b): 
    n %= 10 ** a 
    n //= 10 ** b 
    return n 

Для это работать с той же индексацией, как строки нарезка, вам нужно найти общее количество цифр первого и функция становится:

def getSubdigits2(n, a, b): 
    l = int(math.ceil(math.log10(n))) 
    n %= 10 ** (l - a) 
    n //= 10 ** (l - b) 
    return n 

и строка нарезка эквивалент:

def subDigits3(n, a, b): 
    return int(str(n)[a:n]) 

Вот временные результаты:

  • subDigits: 0,293327726114
  • subDigits2: 0,850861833337
  • subDigits3: 0,990543234267

Мой вынос из этого результата состоит в том, что метод нарезки прекрасно, если вы действительно заботитесь о скорости, и в этом случае вам нужно использовать первый m этод и подумать об индексах в другом направлении.

+0

Да, я предполагаю, что в некоторых случаях может быть какая-то математика, которая изменит проблему, но если предположить, что это было позабочено, и я сократил математику, вопрос в том, является ли это питоновским (и, возможно, только подход). – orome

+0

Теперь у вас есть искренне любопытные (так как я все время занимаюсь этим). Позвольте мне поднять общее решение, используя простую арифметику и посмотреть, что я нахожу. – Jud

+1

Почему не просто 'n = n% 1000' вместо' n - = (n // 1000) * 1000'? – Shashank

2

Как насчет этого?

def digits(n): 
    if n == 0: 
     yield 0 
    else: 
     while n: 
      yield n % 10 
      n //= 10 

for digit in digits(123): 
    # do something with digit 

Это должно быть как более удобным, так и более эффективным, чем пример, который вы показали.

EDIT: Я хочу добавить еще две вещи.

0) Вы можете расширить технику по мере необходимости. Предположим, вы хотите проверить правильные штрихи. Если у вас есть функция is_prime():

def right_trunc_int_values(n): 
    if n == 0: 
     yield 0 
    else: 
     while n: 
      yield n 
      n //= 10 

assert(all(is_prime(n) for n in right_trunc_int_values(317)) 

1) Для того, чтобы решить общую проблему удобно работать с цифрами, вы можете быть в состоянии использовать decimal модуль. Я рассмотрю это больше. Но между тем, вы можете использовать мою digits() функции, чтобы индексируемый список цифр:

d = list(digits(123)) 
print(d[2]) # prints 2 

EDIT: Это довольно легко конвертировать ряд цифр до целого значения.

def int_from_digits(digits): 
    result = 0 
    found_a_digit = False 
    for digit in reversed(digits): 
     result = result * 10 + digit 
    return result 

def is_right_trunc_prime(n): 
    d = list(digits(n)) 
    return all(is_prime(int_from_digits(d[:i]) for i in range(len(d), -1, -1))) 

# example from question of left and right truncatable check 
d = list(digits(n)) 
all(prime(int_from_digits(d[:-i])) and prime(int_from_digits(d[i:])) for i in range(1, len(d))) 
+0

Это явно полезно в любых случаях, когда цифры нужны сами по себе, но мне непонятно, как включить это в случай, например, в вопрос, где последовательность цифр должна быть преобразована обратно в один номер. – orome

+0

@raxacoricofallapatorius, правильно ли я ответил на ваш вопрос? Постскриптум Мне пришлось опубликовать все вышеперечисленное, не протестировав его. Если я допустил какую-либо ошибку, сообщите мне, и я исправлю это для вас. – steveha

+0

Все очень хорошо говорят, что «цифры» должны «быть более эффективными, чем' str », но в соответствии с' timeit', 'str (1234567890)' почти в 4 раза быстрее, чем 'list (цифры (1234567890))' on моя машина (0.923 us против 3.55 usec). Предположительно, потому что любой цикл в Python может быть медленным по сравнению со встроенной функцией, реализованной в C. Конечно, вы бы хотели протестировать реальный код, прежде чем выбирать, что использовать, преобразование - это не вся стоимость исполнения. –

6

В вашем примере кода, вы могли бы уйти с использованием divmod, а не строка нарезка цифр. divmod(x, y) возвращает кортеж x//y, x%y, который для y значений 10**i - это именно то, что вы хотите для левой и правой частей вашего номера. Это не обязательно больше Pythonic, хотя это может быть немного быстрее.

sn = str(n) 
all(prime(int(sn[:i])) and prime(int(sn[i:])) for i in range(1, len(sn))) # original 
all(all(map(prime, divmod(n, 10**i))) for i in range(1, len(sn))) # variant using divmod 

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

Напишите код для чтения, если только он не чувствителен к производительности.

+0

Еще один отличный ответ. Я впечатлен тем, насколько я узнал из этого вопроса всего за несколько минут! – orome

+0

Мне это нравится. Это быстрый и идиоматический способ разбиения числа на правую часть и левую часть по количеству доступных цифр. – steveha

+0

«Напишите код для чтения, если он не чувствителен к производительности». - Мудрые слова! – Jud

1

Тестирование осталось truncatable простых чисел без str() и нарезки:

def is_prime(n): 
    if n < 2: 
     return False 
    elif n == 2: 
     return True 
    elif n % 2 == 0: 
     return False 
    return all(n % x for x in xrange(3,int(pow(n,0.5))+1,2)) 

def is_left_truncatable_prime(n): 
    largest_power_of_ten = 1 
    while largest_power_of_ten < n: 
     largest_power_of_ten *= 10 
    while True: 
     largest_power_of_ten /= 10 # Use // in Python 3 
     if is_prime(n): 
      n %= largest_power_of_ten 
      if n == 0: 
       return True 
     else: 
      return False 

print is_left_truncatable_prime(167) # True 
print is_left_truncatable_prime(173) # True 
print is_left_truncatable_prime(171) # False 

Я не экстенсивно проверил это очень жаль, если есть какая-либо ошибка. Дайте мне знать, если есть, и я их исправлю.

EDIT: исправлен код.

+0

Хм. На моей машине, используя «173» в качестве теста и изменяя код опроса, чтобы использовать вашу функцию 'is_prime', и проверять только левый, а не левый и правый, этот код примерно на 5% медленнее, чем спрашивающие года. Если это подтверждается другими машинами, другими версиями Python и другими значениями (что, конечно же, далеки от моего единственного теста), тогда 'str' будет предпочтительнее всего: производительность, краткость, удобочитаемость. –

+0

@Steve Ну его алгоритм O ((log_10 (n))^2), так как нарезка является линейной операцией времени, и он срезает каждое число в последовательности (1 + 2 + ... + log (n)). [страница сложности времени для Python] (https://wiki.python.org/moin/TimeComplexity) вы можете видеть, что «Get Slice» - это O (k), где k - длина среза. Так что да, возможно, моя функция на 5% медленнее для 173, но я думаю, что она будет быстрее для больших чисел с момента ее O (log_10 (n)) вместо O ((log_10 (n))^2). Может быть, Python бросает вызов мне, хотя :) – Shashank

+0

Теперь должно быть немного быстрее. Я исправил часть своей логики, которая была эквивалентна просто «n% = наибольшая_power_of_ten». – Shashank

4

Нативные целые числа на Python хранятся в базе с мощностью-2, поэтому для преобразования их в десятичную нотацию или из десятичной записи требуется реальная работа. Во многих типах «головоломок» ;-) проблем, требующих частого доступа к десятичным разрядам очень больших целых чисел, может сделать мир различий, чтобы вместо этого использовать модуль decimal. Это сохраняет значения в базе с мощностью -10, так что «преобразование» в/из десятичных цифр является тривиальным расходом.

>>> import decimal 
>>> x = decimal.Decimal('1e20') - 1 
>>> x 
Decimal('99999999999999999999') 
>>> x.as_tuple().digits 
(9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9) 

Это занимает время линейное в количестве цифр. Преобразование собственного целого числа в/из десятичного числа занимает квадратичное время в количестве цифр.

Лучшее Я могу догадаться о вашем конкретном приложении здесь, хотя, используя divmod(), действительно лучший подход.

Смежные вопросы