Чем этот алгоритм хуже?

В Википедии приведен один из приведенных алгоритмов для генерации простых чисел:

def eratosthenes_sieve(n):
    # Create a candidate list within which non-primes will be
    # marked as None; only candidates below sqrt(n) need be checked. 
    candidates = [i for i in range(n + 1)]
    fin = int(n ** 0.5)

    # Loop over the candidates, marking out each multiple.
    for i in range(2, fin + 1):
        if not candidates[i]:
            continue

        candidates[i + i::i] = [None] * (n // i - 1)

    # Filter out non-primes and return the list.
    return [i for i in candidates[2:] if i]

Я немного изменил алгоритм.

def eratosthenes_sieve(n):
    # Create a candidate list within which non-primes will be
    # marked as None; only candidates below sqrt(n) need be checked. 
    candidates = [i for i in range(n + 1)]
    fin = int(n ** 0.5)

    # Loop over the candidates, marking out each multiple.

    candidates[4::2] = [None] * (n // 2 - 1)

    for i in range(3, fin + 1, 2):
        if not candidates[i]:
            continue

        candidates[i + i::i] = [None] * (n // i - 1)

    # Filter out non-primes and return the list.
    return [i for i in candidates[2:] if i]

Сначала я отметил все числа, кратные 2, а затем рассмотрел только нечетные числа. Когда я измерял время обоих алгоритмов (пробовал 40 000 000), первый всегда был лучше (хотя и очень незначительно). Я не понимаю, почему. Может кто-нибудь объяснить?

P.S.: Когда я пробую 100.000.000, мой компьютер зависает. Это почему? У меня Core Duo E8500, 4 ГБ ОЗУ, Windows 7 Pro 64 Bit.

Обновление 1: это Python 3.

Обновление 2: Вот как я рассчитал время:

start = time.time()
a = eratosthenes_sieve(40000000)
end = time.time()
print(end - start)

ОБНОВЛЕНИЕ: благодаря ценным комментариям (особенно от nightcracker и Winston Ewert) мне удалось написать то, что я планировал в первую очередь:

def eratosthenes_sieve(n):
    # Create a candidate list within which non-primes will be
    # marked as None; only c below sqrt(n) need be checked. 
    c = [i for i in range(3, n + 1, 2)]
    fin = int(n ** 0.5) // 2

    # Loop over the c, marking out each multiple.

    for i in range(fin):
        if not c[i]:
            continue

        c[c[i] + i::c[i]] = [None] * ((n // c[i]) - (n // (2 * c[i])) - 1)

    # Filter out non-primes and return the list.
    return [2] + [i for i in c if i]

Этот алгоритм улучшает исходный алгоритм (упомянутый вверху) (обычно) на 50%. (Все же, хуже, чем алгоритм, упомянутый nightcracker, естественно).

Вопрос к мастерам Python: есть ли более Pythonic способ выразить этот последний код более функциональным способом?

ОБНОВЛЕНИЕ 2: я так и не смог расшифровать алгоритм, упомянутый nightcracker. Наверное, я слишком глуп.


person blackened    schedule 24.03.2011    source источник
comment
где это "функциональное программирование"?   -  person Javier    schedule 25.03.2011
comment
Вероятно, вы можете немного ускорить процесс, используя xrange вместо range с циклом for.   -  person GWW    schedule 25.03.2011
comment
Я нахожу обратное, как вы делаете время?   -  person Winston Ewert    schedule 25.03.2011
comment
Теперь интересно: результаты синхронизации меняются с python2 на python3.   -  person Winston Ewert    schedule 25.03.2011
comment
@Winston Ewert: Может быть, из-за диапазона и xrange?   -  person blackened    schedule 25.03.2011


Ответы (4)


Вопрос в том, почему это должно быть даже быстрее? В обоих примерах вы фильтруете числа, кратные двум, сложным способом. Неважно, жестко ли вы закодировали candidates[4::2] = [None] * (n // 2 - 1) или он выполняется в первом цикле for i in range(2, fin + 1):.

Если вас интересует оптимизированное сито Эратосфена, то вам сюда:

def primesbelow(N):
    # https://stackoverflow.com/questions/2068372/fastest-way-to-list-all-primes-below-n-in-python/3035188#3035188
    #""" Input N>=6, Returns a list of primes, 2 <= p < N """
    correction = N % 6 > 1
    N = (N, N-1, N+4, N+3, N+2, N+1)[N%6]
    sieve = [True] * (N // 3)
    sieve[0] = False
    for i in range(int(N ** .5) // 3 + 1):
        if sieve[i]:
            k = (3 * i + 1) | 1
            sieve[k*k // 3::2*k] = [False] * ((N//6 - (k*k)//6 - 1)//k + 1)
            sieve[(k*k + 4*k - 2*k*(i%2)) // 3::2*k] = [False] * ((N // 6 - (k*k + 4*k - 2*k*(i%2))//6 - 1) // k + 1)
    return [2, 3] + [(3 * i + 1) | 1 for i in range(1, N//3 - correction) if sieve[i]]

Объяснение здесь: Перенос оптимизированного решета Эратосфена с Python на C++

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

person orlp    schedule 24.03.2011
comment
Спасибо за ваш вклад и экономию вашего времени. Я обхожу все четные числа. Разве это не существенно? - person blackened; 25.03.2011
comment
@blackened: проблема в том, что вы не обходите четные числа. Вы выделяете для них память и даже отфильтровываете их, тратя драгоценное процессорное время! Зачем давать вонючим четным номерам такое особое отношение, которого они не заслуживают? Если бы вы обошли четные числа, вы бы создали массив с элементами ^[N/2] (^[] округляется). Элемент в ключе i будет обозначать число 2*i + 1 как простое или не простое. - person orlp; 25.03.2011
comment
Я думаю, вы правы. Я подумаю над вашим комментарием. Спасибо, что сэкономили ваше время. - person blackened; 25.03.2011
comment
В основном это так: c">stackoverflow.com/questions/5293238/, но затем с помощью хака, который использует назначения фрагментов, поэтому интерпретатор python использует быстрые назначения на месте (в C++ это не требуется, поскольку вы можете выполнять эти назначения самостоятельно) . - person orlp; 25.03.2011

Вы не экономите много времени, избегая эвенов. Большая часть времени вычислений в алгоритме тратится на это:

candidates[i + i::i] = [None] * (n // i - 1)

Эта строка вызывает много действий со стороны компьютера. Всякий раз, когда рассматриваемое число четное, это не запускается, поскольку цикл останавливается на операторе if. Таким образом, время, затрачиваемое на выполнение цикла для четных чисел, действительно очень мало. Таким образом, устранение этих четных раундов не приводит к существенному изменению времени цикла. Вот почему ваш метод не намного быстрее.

Когда python производит числа для диапазона, он использует формулу: start + index * step. Умножение на два, как в вашем случае, будет немного дороже, чем на один, как в исходном случае.

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

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

person Winston Ewert    schedule 24.03.2011
comment
Спасибо, что не пожалели времени, Уинстон Эверт. - person blackened; 25.03.2011

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

person kniteli    schedule 24.03.2011
comment
Он пытается получить преимущество, передавая 2 в финал xrange, тем самым ускоряя цикл. т. е. он может пропустить проверку простоты для всех четных чисел. - person Winston Ewert; 25.03.2011
comment
Ну, против этого, первый код перебирает все целые числа от 2 до sqrt (n), мой перебирает половину. - person blackened; 25.03.2011

В вашем дополнительном шаге нет необходимости, и он фактически пройдет всю коллекцию n один раз, выполнив операцию «избавиться от четных», а не просто работая с n ^ 1/2.

person Ichorus    schedule 24.03.2011