Привет! Сегодня мы решим 41-ю задачу из Проекта Эйлера. Сделаем это сначала в развёрнутом виде, а потом максимально сократим решение.
Условие
Будем считать n-значное число пан-цифровым, если каждая из цифр от 1 до n используется в нем ровно один раз. К примеру, 2143 является 4-значным пан-цифровым числом, а также простым числом. Какое существует наибольшее n-значное пан-цифровое простое число?
Решение
Для начала импортируем модуль math. Из него нам понадобится функция ceil(), округляющая число вверх.
import math
Как не трудно догадаться по условию, нам понадобится функция, определяющая, является ли число простым. Напишем её.
def is_prime(x: int):
if x % 2 == 0:
return x == 2
for i in range(3, math.ceil(x ** 0.5), 2):
if x % i == 0:
return False
return True
2 является единственным чётным простым числом, поэтому, если число оказалось чётным, нужно проверить, не 2 ли это. Если 2 – вернуть True, иначе – False. Т. к. return производит выход из функции, после него мы уверены, что число не является чётным. Будем проверять делители x начиная с 3 до округлённого верх квадратного корня из x с шагом 2 (только нечётные). Если x поделится нацело на один из вариантов – немедленно возвращаем False, т. к. число не будет являться простым. Если никакая проверка не привела к выходу из цикла, то число является простым.
Теперь перейдём непосредственно к нашей проблеме. Определим верхнюю границу искомого числа: максимальное пан-цифровое число – это 987_654_321 (да-да, в Python в качестве разделителя в числах можно использовать символ подчёркивания). Поскольку нам нужно найти максимальное пан-цифровое простое число, следует двигаться от максимального значения вниз и выдать первое подходящее. Можно написать функцию для определения, является ли число пан-цифровым, и проверять на простоту только такие числа, но это крайне неэффективно. Среди девятизначных пан-цифровыми будут являться только 9! = 362_880, т. е. каждое 2_756-е. Как мы видим, пан-цифровые числа попадаются крайне нечасто, поэтому стоит придумать алгоритм, который будет генерировать только пан-цифровые числа.
Для генерации всех перестановок (англ. permutations) в стандартной библиотеке itertools есть специальный генератор - permutations(). На вход он принимает любой итерируемый объект (строку/список/...), а возвращает все перестановки (каждая в виде списка) данного нами объекта.
Т. к. нам нужно получить перестановки от большей к меньшей, следует указать из на входе генератора в обратном порядке. Например для девятизначного числа это будет [9, 8, 7, 6, 5, 4, 3, 2, 1]. Обобщим: для i-значного числа нам нужно составить список от i до 1:
list(range(i, 0, -1))
Нам требуется рассмотреть не только 9-значные числа, но и 8/7/6/5/4/3/2/1-значные. Оформим это. Также подадим в качестве аргумента для генератора преобразованный список, где тип каждого элемента изменим на str. Т. к. j – значение конкретной перестановки, – будет являться списком из отдельных строк, каждая из которых будет эквивалентна определённой цифре, преобразуем это значение в число. Далее остаётся проверить его на простоту, вывести, если оно таковым является, и выйти из 2 циклов. Оформим это в функцию:
def main():
for i in list(range(9, 0, -1)):
for j in itertools.permutations(list(map(str, list(range(i, 0, -1))))):
x = int(''.join(j))
if is_prime(x):
print(x)
return
А сейчас ещё немного подумаем. Ведь в 9-значном пан-цифровом числе сумма цифр s=1+2+3+4+5+6+7+8+9=45, а 45 делится на 3. А, как мы помним, если сумма цифр числа делится на 3, то и само число делится на 3, соответственно, оно не является простым. Из этого следует, что ни одно из 9-значных пан-цифровых чисел не является простым. Аналогичные манипуляции для 2/3/5/6/8-значных пан-цифровых чисел приведут нас к выводу, что среди них также нет простых, т. к. суммы их цифр соответственно равны 3, 6, 15, 21, 36. Единственное же 1-значное пан-цифровое число – 1, – не является не простым, не сложным; но нам достаточно только 1-й части формулировки.
Итого, нам остаётся искать простые пан-цифровые числа только среди 7-значных и 4-значных, так что изменим первый цикл for:
for i in (7, 4):
На этом этапе вся программа выглядит следующим образом:
import math
import itertools
def is_prime(x: int):
if x % 2 == 0:
return x == 2
for i in range(3, math.ceil(x ** 0.5), 2):
if x % i == 0:
return False
return True
def main():
for i in (7, 4):
for j in itertools.permutations(list(map(str, list(range(i, 0, -1))))):
x = int(''.join(j))
if is_prime(x):
print(x)
return
if __name__ == '__main__':
main()
Сокращение решения
А теперь примемся за сокращение нашей программы
Для начала избавимся от функции is_prime() и от библиотеки math, которая использовалась только в этой функции. В модуле sympy, который, к сожалению, не является стандартным, но который можно установить через pip install в командной строке, аналогичная нашей функция isprime(). Удалим и заменим соответствующие участки кода:
import itertools
import sympy
def main():
for i in (7, 4):
for j in itertools.permutations(list(map(str, list(range(i, 0, -1))))):
x = int(''.join(j))
if sympy.isprime(x):
print(x)
return
if __name__ == '__main__':
main()
Уберём проверку на запуск в качестве основного приложения, удалим ненужные скобки в for, уберём создание списка из map'а, так как map возвращает итерируемый тип, который можно подать на вход itertools.permutations:
import itertools
import sympy
def main():
for i in 7, 4:
for j in itertools.permutations(map(str, list(range(i, 0, -1)))):
x = int(''.join(j))
if sympy.isprime(x):
print(x)
return
main()
А теперь попрошу любителей и страстных поклонников pep-8 отвернуться от экрана. Грядёт импорт нескольких библиотек в одну строку, использование других имён модулей и удаление пробелов после запятых.
import itertools as t,sympy
def m():
for i in 7,4:
for j in t.permutations(map(str,list(range(i,0,-1)))):
x = int(''.join(j))
if sympy.isprime(x):
print(x)
return
m()
Заметим, что обращаться к sympy под другим именем не выгодно, т. к. двойное написание "sympy" (при импорте и при обращении) займёт 10 символов, а переименование (например, "sympy as s") и обращение ("s") – уже 11.
Уже вышло вполне компактно, не так ли?
А сейчас время спорных решений – нужно избавиться от функции. Но как, ведь нам нужно выйти сразу из 2 циклов, и обычный break использовать не выйдет? После вывода ответа просто выйдем из программы. Несмотря на то, что глазом вы ответ увидеть вряд ли успеем, формально он будет выведен. Если не выходить, то программа выдаст 2 ответа, а верным является только первый. Можно было бы сделать какую-нибудь запрещённую операцию, в духе деления на ноль, но тогда в консоль будет выведено сообщение об ошибке, т. е. что-то помимо ответа.
Для выхода из программы можно использовать exit() или quit(), но они одинаковы по своей длине, так что разницы нет никакой. Я выберу exit()
Итак, программа выглядит следующим образом:
import itertools as t,sympy
for i in 7,4:
for j in t.permutations(map(str,list(range(i,0,-1)))):
x=int(''.join(j))
if sympy.isprime(x):
print(x)
exit()
Остался последний штрих: убрать единственный лишний переход на следующую строку и знак табуляции:
import itertools as t,sympy
for i in 7,4:
for j in t.permutations(map(str,list(range(i,0,-1)))):
x=int(''.join(j))
if sympy.isprime(x):
print(x);exit()
Ура, товарищи! Программа занимает всего 159 символов. Если есть идеи, как можно уменьшить код ещё меньше, пишите в комментариях.
Комментарии (13)
longclaps
27.11.2021 01:48+4Вот 5 строк, без сторонних библиотек:
for x in range(7654321, 0, -2): for p in range(3, int(x ** .5), 2): if not x % p: break else: if "1234567".startswith(''.join(sorted(str(x)))): exit(print(x))
Крохоборствуя на пробелах и выкинув корень, можно выжать 140 символов, влезть в рамки изначального ограничения твиттера. Но зачем?
datacompboy
27.11.2021 03:58+10Кратчайшее решение все же
print(7652413)
Если мы используем знания о делимости и проч...
nochkin
27.11.2021 05:02+2Предлагаю опустить "print", так как в задаче не сказано про то, что бы вывести число на экран. Достаточно его просто найти.
GospodinKolhoznik
27.11.2021 09:03+4Там и не сказано, что его надо найти. Спрашивается какое оно, а не чему равно.
celen
27.11.2021 04:38+8import itertools as t,sympy print([j for i in(7,4)for j in map(''.join,t.permutations('987654321'[-i:]))if sympy.isprime(int(j))][0])
Ваше решение, сокращенное и в одну строку. 134 символа.
forthuser
27.11.2021 09:15Pandigital prime
ещё в решении на разных языках на ресурсе rosettacode.org :)
(решение представлено всего на 13-ти языках)
GospodinKolhoznik
27.11.2021 10:19+4Предлагаю наибольшее пан-цифровое простое число называть пан-атаман-цифровое число.
slonopotamus
27.11.2021 18:15+5каждая из цифр от 1 до n используется в нем ровно один раз
максимальное пан-цифровое число – это 987_654_321
Я не понял, откуда возникло ограничение 10-тичной системой счисления.
P_R_V
27.11.2021 22:42+1Спасибо, что напомнили об этом сборнике задач
Кто не в курсе - projecteuler.net
stabuev
28.11.2021 13:16+4Странное укорачивание. Так можно запихнуть указанную функцию в библиотеку, которую обозвать одной буквой, потом вызвать ее импортом и выполнить :)
Будет что-то в духе:
import m
m.m()
brnovk
пан-цифровым
Есть же нормальное название.https://ru.wikipedia.org/wiki/Панциферное_число