Как перечислить наборы панцифровых простых чисел

Задача 118 проекта Эйлера гласит: «Используя все цифры от 1 до 9 и свободно объединяя их для формирования десятичных целых чисел, можно сформировать разные наборы. Интересно, что с набором {2,5,47,89,631} все элементы, принадлежащие ему, являются простыми. Сколько различных множеств, содержащих каждую из цифр от первой до девяти ровно один раз, содержат только простые элементы».

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

Я разработал довольно простой подход к динамическому программированию. Для этого я отвечу на вопрос: «Сколько различных наборов, содержащих каждую из цифр числа S ровно один раз, содержат только простые элементы» для разных значений S, а затем объединим их результаты, чтобы ответить на исходный вопрос.

Сначала я генерирую список всех соответствующих простых чисел. Это любое простое число, состоящее из 9 или менее цифр, не имеющее повторяющихся цифр и не имеющее 0 в качестве одной из своих цифр. Затем я использую этот список для создания словаря количества наборов. Ключами этого словаря являются замороженные наборы, которые представляют, какие цифры имеет каждое простое число, а значения представляют собой целые числа, которые подсчитывают количество соответствующих простых чисел, которые сводятся к этому набору:

from itertools import chain, combinations
from functools import cache
from collections import defaultdict
from pyprimesieve import primes


primes = [
    p for p in primes(10**9) if len(set(str(p))) == len(str(p)) and "0" not in str(p)
]
set_counts = defaultdict(int)
for p in primes:
    set_counts[frozenset(map(int, str(p)))] += 1

set_counts будет служить базовым вариантом для нашей рекурсии.

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

@cache
def set_decomps(s):
    """
    Generates all possible ways to partition a set s
        into two non-empty subsets
    """
    l = list(
        map(
            lambda x: (frozenset(x), s - frozenset(x)),
            chain.from_iterable(combinations(s, r) for r in range(1, len(s))),
        )
    )
    return l[: len(l) // 2]

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

@cache
def dp(s):
    """
    Returns the number of ways to create a set containing prime numbers
    Such that the set of all the digits in the primes is equal to s
    With no duplicates or extra digits
    """
    base = set_counts[s]
    if len(s) > 1:
        for a, b in set_decomps(s):
            base += dp(a) * dp(b)
    return base
print(dp(frozenset(range(1,10)))) # prints 114566, which is wrong

Очевидно, что в моем подходе есть изъян.

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
0
80
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Недостаток вот в чем. Рассмотрим набор {2,5,47,89,631}. К этому могут привести различные первоначальные расколы. Один {1,2,3,6}, {4,5,7,8,9}, другой {1,3,6,8,9} {2,4,5,7}. Есть еще много других. И поэтому вы пересчитываете.

Чтобы оставить вам удовольствие от проблемы, я не буду рассказывать вам, как исправить этот пересчет. Я просто скажу вам, что если вы считаете несколько способов добраться до определенного набора, то ваше число будет слишком большим.

Это показалось интересным. Чтобы избежать чрезмерного подсчета, я применил рекурсивный подход, основанный на структуре, которая упорядочивает цифры каждого простого кандидата и группирует их по младшей цифре, а также записывает количество каждого набора равных цифр. Например, 13 и 31 будут указаны в группе для младшей цифры 1 как один набор {1, 3} (представленный как битовый набор в виде целого числа) со счетчиком 2.

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

Как я обнаружил, узким местом была генерация только необходимых простых чисел, а не проверка всех простых чисел до 987 654 321.

Выход:

$ python3 prime_sets.py
Creating primes dictionary took 3 seconds.
The answer is 44680.
Recursive search took 0 seconds.

Код Python:

# Project Euler 118
 
import collections
import itertools
import random
import time

 
# https://www.codeproject.com/Articles/691200/Primality-test-algorithms-Prime-test-The-fastest-w
def MillerRabinPrimalityTest(number):
    if number == 2:
        return True
    elif number == 1 or number % 2 == 0:
        return False
 
    oddPartOfNumber = number - 1
    timesTwoDividNumber = 0
 
    while oddPartOfNumber % 2 == 0:
        oddPartOfNumber = oddPartOfNumber // 2
        timesTwoDividNumber = timesTwoDividNumber + 1 
 
    for time in range(3):
        while True:
            randomNumber = random.randint(2, number)-1
            if randomNumber != 0 and randomNumber != 1:
                break
 
        randomNumberWithPower = pow(randomNumber, oddPartOfNumber, number)
 
        if (randomNumberWithPower != 1) and (randomNumberWithPower != number - 1):
            iterationNumber = 1
 
            while (iterationNumber <= timesTwoDividNumber - 1) and (randomNumberWithPower != number - 1):
                randomNumberWithPower = pow(randomNumberWithPower, 2, number)
                iterationNumber = iterationNumber + 1
            if (randomNumberWithPower != (number - 1)):
                return False
 
    return True
 

t0 = time.time()
primes = { d:collections.defaultdict(int) for d in range(1, 10) }

 
def add_prime(p, ps):
  ds = 0
  lowest = 9
  while p > 0:
    d = p % 10
    ds = ds | (1 << d)
    lowest = min(lowest, d)
    p = p // 10
 
  primes[lowest][ds] += 1


for i in range(1, 10):
  for comb in itertools.combinations(map(str, range(1, 10)), i):
    for perm in itertools.permutations(comb):
      n = int("".join(perm))
      if MillerRabinPrimalityTest(n):
        add_prime(n, primes)


t1 = time.time()
print(f"Creating primes dictionary took {int(t1 - t0)} seconds.")


def get_lowest_missing(ds):
  for i in range(1, 10):
    if (1 << i) & ds == 0:
      return i
  return None
 

def f(primes, ds, count, lowest, dp):
  if lowest is None:
    return count
 
  key = (ds, count, lowest)
  if key in dp:
    return dp[key]
 
  answer = 0
 
  for other_ds in primes[lowest]:
    if ds & other_ds == 0:
      answer = answer + f(
          primes,
          ds | other_ds,
          count * primes[lowest][other_ds],
          get_lowest_missing(ds | other_ds),
          dp)
 
  dp[key] = answer
  return answer

 
print(f"The answer is {f(primes, 0, 1, 1, {})}")


t2 = time.time()
print(f"Recursive search took {int(t2 - t1)} seconds.")

Другие вопросы по теме