Является ли это оптимальным старшим генератором?

Является ли это каким-либо образом оптимальным решением для нахождения простых чисел? Я не пытаюсь добавить каждую оптимизацию под солнцем, но является основным преимуществом?

def primesUpto(self, x):
    primes = [2]
    sieve = [2]
    i = 3
    while i <= x:
        composite = False
        j = 0
        while j < len(sieve):
            sieve[j] = sieve[j] - 1
            if sieve[j] == 0:
                composite = True
                sieve[j] = primes[j]
            j += 1
        if not composite:
            primes.append(i)
            sieve.append(i*i-i)
        i += 1
    return primes

Ответ 1

Хм, очень интересно. Ваш код действительно честный и добросердечный подлинное сито Эратосфена ИМХО, подсчитывая свой путь по восходящим натуральным числам, уменьшая каждый счетчик, который он устанавливает для каждого встречного числа, на 1 на каждом шаге.

И это очень неэффективно. Протестировано на Ideone, он работает при том же эмпирическом порядке роста ~ n^2.2 (при испытанных диапазонах в несколько тысяч простых чисел) в качестве лихорадочного ситверного анализатора Turner (в Haskell).

Почему? Некоторые причины. Сначала, ранняя помощь в вашем тесте: когда вы обнаруживаете его составной, вы продолжаете обработку массива счетчиков sieve. Вы должны из-за второй причины: считать разницу , уменьшая каждый счетчик на 1 на каждом шаге, с 0, представляющим ваш текущий должность. Это самое верное выражение оригинального сита IMHO, и оно очень неэффективно: сегодня наши процессоры знают, как добавлять числа в O (1) раз (если эти числа относятся к определенному диапазону, 0.. 2 ^ 32, или 0.. 2 ^ 64, конечно).

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

И третья, возможно самая непосредственная причина неэффективности, является преждевременной обработкой кратных: когда вы сталкиваетесь с 5 как простое, вы добавляете свой первый множественный ( еще не встречается), т.е. 25, сразу в массив счетчиков, sieve (т.е. расстояние между текущей точкой и кратным, i*i-i). Это слишком рано. Добавление 25 должно быть отложено, пока... ну, пока мы не встретим 25 среди восходящих натуральных чисел. Начав обрабатывать кратные числа первичных преждевременных (в p вместо p*p), приводит к тому, что у них слишком много счетчиков для поддержания - O(n) из них (где n - количество произведенных простых чисел), вместо просто O(π(sqrt(n log n))) = O(sqrt(n / log n)).

Оптимизация отсрочки при применении к подобному "счетному" ситу в Haskell привела к его эмпирическим порядкам роста от ~ n^2.3 .. 2.6 для n = 1000 .. 6000 простых чисел до чуть выше ~ n^1.5 (с явно огромными выигрышами в скорости). Когда подсчет был дополнительно заменен прямым добавлением, результирующие измеренные эмпирические порядки роста были ~ n^1.2 .. 1.3 в производстве до hlaf миллиона простых чисел (хотя, по всей вероятности, это увеличилось бы на ~ n^1.5 для больших диапазонов).