Действительно ли эта основная функция работает? - программирование
Подтвердить что ты не робот

Действительно ли эта основная функция работает?

С тех пор как я начинаю пользоваться Python, я начинаю тестировать свои недавно приобретенные навыки Python по некоторым проблемам на projecteuler.net.

В любом случае, в какой-то момент я закончил создание функции для получения списка всех простых чисел вплоть до числа "n".

Вот как выглядит функция atm:

def primes(n):
    """Returns list of all the primes up until the number n."""

    # Gather all potential primes in a list.
    primes = range(2, n + 1)
    # The first potential prime in the list should be two.
    assert primes[0] == 2
    # The last potential prime in the list should be n.
    assert primes[-1] == n

    # 'p' will be the index of the current confirmed prime.
    p = 0
    # As long as 'p' is within the bounds of the list:
    while p < len(primes):
        # Set the candidate index 'c' to start right after 'p'.
        c = p + 1
        # As long as 'c' is within the bounds of the list:
        while c < len(primes):
            # Check if the candidate is divisible by the prime.
            if(primes[c] % primes[p] == 0):
                # If it is, it isn't a prime, and should be removed.
                primes.pop(c)
            # Move on to the next candidate and redo the process.
            c = c + 1
        # The next integer in the list should now be a prime, 
        # since it is not divisible by any of the primes before it. 
        # Thus we can move on to the next prime and redo the process.
        p = p + 1       
    # The list should now only contain primes, and can thus be returned.
    return primes

Кажется, что все нормально, хотя одна вещь меня беспокоит. Комментируя код, эта часть внезапно показалась немой:

# Check if the candidate is divisible by the prime.
if(primes[c] % primes[p] == 0):
    # If it is, it isn't a prime, and should be removed from the list.
    primes.pop(c)
# Move on to the next candidate and redo the process.
c += 1

Если кандидат НЕ делится первичным, мы исследуем следующего кандидата, расположенного в 'c + 1'. Нет проблем с этим.

Однако, если кандидат IS делится на простое, мы сначала выкладываем его, а затем исследуем следующего кандидата, расположенного в 'c + 1'. Меня поразило, что следующий кандидат после всплытия не находится в 'c + 1', но 'c', так как после появления в 'c', следующий кандидат "падает" на этот индекс.

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

# If the candidate is divisible by the prime:
if(primes[c] % primes[p] == 0):
    # If it is, it isn't a prime, and should be removed from the list.
    primes.pop(c)
# If not:
else:
    # Move on to the next candidate.
    c += 1

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

Итак, вот мои вопросы:

После появления кандидата, который оказался не простым, можем ли мы считать, что он в моем исходном коде, что следующий кандидат НЕ делится тем же самым простым?

Если да, то почему?

Будет ли предложенный "безопасный" код просто делать ненужные проверки кандидатов, которые пропущены в "небезопасном" коде?

PS:

Я пробовал написать вышеприведенное предположение как утверждение в "небезопасную" функцию и протестировать ее с n = 100000. Проблем не было. Здесь измененный блок:

# If the candidate is divisible by the prime:
if(primes[c] % primes[p] == 0):
    # If it is, it isn't a prime, and should be removed.
    primes.pop(c)
    # If c is still within the bounds of the list:
    if c < len(primes):
        # We assume that the new candidate at 'c' is not divisible by the prime.
        assert primes[c] % primes[p] != 0
# Move on to the next candidate and redo the process.
c = c + 1
4b9b3361

Ответ 1

Он выходит из строя гораздо большим числом. Первое простое число 71, для чего кандидат может потерпеть неудачу. Самый маленький неудачный кандидат на 71 - 10986448536829734695346889, который затмевает число 10986448536829734695346889 + 142.

def primes(n, skip_range=None):
    """Modified "primes" with the original assertion from P.S. of the question.
    with skipping of an unimportant huge range.
    >>> primes(71)
    [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71]
    >>> # The smallest failing number for the first failing prime 71:
    >>> big_n = 10986448536829734695346889
    >>> primes(big_n + 2 * 71, (72, big_n))
    Traceback (most recent call last):
    AssertionError
    """
    if not skip_range:
        primes = list(range(2, n + 1))
    else:
        primes = list(range(2, skip_range[0]))
        primes.extend(range(skip_range[1], n + 1))
    p = 0
    while p < len(primes):
        c = p + 1
        while c < len(primes):
            if(primes[c] % primes[p] == 0):
                primes.pop(c)
                if c < len(primes):
                    assert primes[c] % primes[p] != 0
            c = c + 1
        p = p + 1
    return primes

# Verify that it can fail.
aprime = 71   # the first problematic prime 
FIRST_BAD_NUMBERS = (
        10986448536829734695346889, 11078434793489708690791399,
        12367063025234804812185529, 20329913969650068499781719,
        30697401499184410328653969, 35961932865481861481238649,
        40008133490686471804514089, 41414505712084173826517629,
        49440212368558553144898949, 52201441345368693378576229)

for bad_number in FIRST_BAD_NUMBERS:
    try:
        primes(bad_number + 2 * aprime, (aprime + 1, bad_number))
        raise Exception('The number {} should fail'.format(bad_number))
    except AssertionError:
        print('{} OK. It fails as is expected'.format(bad_number))

Я решил эти числа с помощью сложного алгоритма, такого как головоломка, путем поиска возможных остатков n по модулю малых простых чисел. Последний простой шаг состоял в том, чтобы получить полное n (по китайской теореме остатка в трех строках кода Python). Я знаю, что все 120 базовых решений, меньших primorial (71) = 2 * 3 * 5 * 7 * 11 * 13 * 17 * 19 * 23 * 29 * 31 * 37 * 41 * 43 * 47 * 53 * 59 * 61 * 67 * 71, периодически повторяются всеми кратными этим числом. Я переписал алгоритм много раз за каждое десятилетие проверенных простых чисел, потому что за каждое десятилетие решение было намного медленнее, чем в предыдущем. Возможно, я найду меньшее решение с тем же алгоритмом для чисел 73 или 79 в приемлемое время.


Edit:

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

Я ожидаю, что только два решения скрытый кандидат c хорош: c = p ** n или c = p1 * p ** n или c = p1 ** n1 * p ** n, где p и p1 - простые числа, а n - мощность больше 1. Функция primes не работает, если c - 2 * p делится на не простое число, меньшее p, и если все числа между c-2n и c делятся на любое простое число, меньшее p. Вариант p1 * p ** n также требует, чтобы тот же c не удался раньше для p1 (p1 < p), поскольку мы уже знаем бесконечное число таких кандидатов.

РЕДАКТИРОВАТЬ: я нашел меньший пример ошибки: номер 121093190175715194562061 для простого 79. (что примерно в девяносто раз меньше, чем для 71), я не могу продолжить по тому же алгоритму, чтобы найти меньшие примеры, потому что все базовые решения 702612 заняли более 30 часов для премьер-79 на моем ноутбуке.

Я также проверил его для всех кандидатов, меньших 400000000 (4E10) и для всех соответствующих простых чисел, что ни один кандидат не сможет выполнить утверждение в вопросе. До тех пор, пока у вас не будет терабайт памяти и тысячи лет, утверждение в алгоритме пройдет, потому что ваша временная сложность - O ((n/log (n)) ^ 2) или очень похожа.

Ответ 2

Ваше наблюдение кажется точным, что довольно неплохо.

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

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

Этот метод нахождения простых чисел, кстати, основан на сите эратотенов. При выполнении сита возможно, что если "c" кратно "p", то следующее значение никогда не будет кратным одному и тому же простому.

Возникает вопрос: существуют ли случаи, когда все значения между p * x и p * (x + 1) делятся на простые числа меньше p и p * x + 1). (Здесь алгоритм пропустит значение, и его не поймают позже.) Однако одно из этих значений четное, поэтому оно будет устранено на круглом "2". Итак, реальный вопрос заключается в том, существуют ли случаи, когда все значения между p * x и p * (x + 2) делятся на числа, меньшие p.

Сразу же, я не могу думать ни о каких цифрах меньше 100, удовлетворяющих этому условию. Для p = 5 всегда существует значение, которое не делится на 2 или 3 между двумя последовательными кратными 5.

Кажется, что много больших пробелов и последовательностей написано много, но не столько на последовательностях последовательных целых чисел, делящихся на числа, меньшие p. После некоторых (хорошо, много) проб и ошибок я определил, что каждое число между 39 474 (17 * 2,322) и 39,491 (17 * 2,233) делится на целое число меньше 17:

39,475  5
39,476  2
39,477  3
39,478  2
39,479  11
39,480  2
39,481  13
39,482  2
39,483  3
39,484  2
39,485  5
39,486  2
39,487  7
39,488  2
39,489  3
39,490  2

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

Мое заключение заключается в том, что исходный код может работать, но ваше исправление - это правильная вещь. Без доказательства того, что таких последовательностей нет, это выглядит как ошибка, хотя ошибка может быть очень, очень и очень редко.

Ответ 3

  • Для двух чисел n, m в последовательной последовательности возможных простых чисел, таких, что n и m не делятся на последний дивизор p, то m - n < р
  • Учитывая q (следующий более высокий делитель) > p, то если n делится на q, то следующее число, делящееся на q, равно n + q > n + p > m поэтому m следует пропустить в текущей итерации для теста на делимость

    Here n = primes[c] 
    
    m = primes[c + 1], i.e. primes[c] after primes.pop(c)
    
    p = primes[p]
    q = primes[p+1] 
    

Ответ 4

Вот идея:

Triptych объяснил 1 что следующее число после c не может быть c + p, но нам все равно нужно показать, что он также никогда не может быть c + 2p.

Если мы используем простые числа = [2], мы можем иметь только одно последовательное "несвязанное" число, делимое на 2.

Если мы используем простые числа = [2,3], мы можем построить 3 последовательных "простых числа", число, деленное на 2, число, деленное на три, и число, деленное на 2, и они не могут получить следующий номер, Или

2,3,4 = > 3 последовательных "простых числа"

Несмотря на то, что 2 и 3 не являются "непростыми", мне легче думать в терминах этих чисел.

Если мы используем [2,3,5], получим

2,3,4,5,6 = > 5 последовательных "простых чисел"

Если мы используем [2,3,5,7], получим

2,3,4,5,6,7,8,9,10 = > 9 последовательных "простых чисел"

Появляется шаблон. Самые последовательные простые числа, которые мы можем получить, являются следующим простым - 2.

Следовательно, если next_prime < p * 2 + 1, мы должны иметь хотя бы некоторое число между c и c + 2p, так как число последовательных не простых чисел не достаточно велико, учитывая еще простые числа.

Я не знаю о очень большом количестве, но я думаю, что этот next_prime < p * 2 + 1, вероятно, будет содержать очень большие числа.

Я надеюсь, что это имеет смысл и добавляет некоторый свет.


1Триптих был удален.

Ответ 5

Это не дает отдаленно убедительный ответ, но вот что я пробовал по этому поводу:

Я переформулировал требуемое предположение здесь: (lpf обозначает наименее главный фактор):

For any composite number, x, where:
    lpf(x) = n
There exists a value, m, where 0 < m < 2n and:
    lpf(x+m) > n

Нетрудно показать, что значения для x существуют, где не существует составного числа (x + m), чтобы удовлетворить неравенству. Любое квадратное правое демонстрирует, что:

lpf(x) = x^.5, so x = n^2
n^2 + 2n < (n + 1)^2 = n^2 + 2n + 1

Итак, в случае любого квадрата prime, для того чтобы это верно, должно быть простое число p, присутствующее в диапазоне x < p < x + 2n.

Я думаю, что это можно сделать с учетом асимптотического распределения квадратов (x ^.5) по сравнению с теоремой о числовом номере (асимптотическое распределение простых чисел примерно x/(ln x)), хотя, действительно, мое понимание Теорема о числовом номере ограничена в лучшем случае.

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

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

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

Однако при выполнении моего теста нет встречных примеров в значениях от 2 до 2 000 000. Итак, для чего это стоит, значения из алгоритма, как указано, должны быть безопасными, по крайней мере, до 2 000 000, если только моя логика не является неправильной.

Это то, что я должен добавить. Большой вопрос, Фазик, с удовольствием!

Ответ 6

Если простой p делит кандидата c, то следующий более крупный кандидат, который делится на p, равен c + p. Поэтому исходный код правильный.

Однако это гнилой способ создания списка простых чисел; попробуйте его с n = 1000000 и посмотрите, как он медленный. Проблема в том, что вы выполняете пробное деление, когда вы должны использовать сито. Здесь простое сито (псевдокод, я позволю вам сделать перевод на Python или на другой язык):

function primes(n)
    sieve := makeArray(2..n, True)
    for p from 2 to n step 1
        if sieve[p]
            output p
            for i from p+p to n step p
                sieve[i] := False

Это должно привести к тому, что простые числа меньше миллиона менее чем за секунду. И есть еще другие ситовые алгоритмы, которые еще быстрее.

Этот алгоритм называется Сито Эратосфена и был изобретен около 2200 лет назад греческим математиком. Эратосфен был интересным человеком: помимо просеивания для простых чисел он изобрел високосный день и систему широты и долготы, точно рассчитал расстояние от Солнца до Земли и окружности Земли и некоторое время был главным библиотекарем библиотеки Птолемея в Александрии.

Когда вы будете готовы больше узнать о программировании с помощью простых чисел, я скромно рекомендую этот essay в моем блоге.