День добрый, всем!
> Прошу прощения за некропостинг, но на письмо отвечу.
> Я остановился на 40 000, поскольку решето Эратосфена на Рефале
> оказалось сильно медленным, и его время растёт нелинейно от длины
> исходного списка. Причём, мне показалось, что даже квадратично.
> Поэтому результата в 176 100 я просто не дождусь.
> Время я замерял для переславской реализации Рефала-5. Рефал-5λ
> сильно медленнее (в разы), поэтому мне стыдно было показывать эти числа .
> В последующей переписке Антон Орлов продолжил оффтопик, мне уже лень
> пытаться переписывать его примеры на Рефал.
Вся переписка очень интересная и содержательная.
То, что нарыл Антон я ввел в свой курс по FP (Хаскелль) под заголовком
"Эратосфен это про вычеркивание, а не про делимость".
Действительно, Эратосфен говорил: оставь P как очередное простое и иди
с шагом P дальше и вычеркивай. То есть, он не про делимость, а про
удаление из изходного множества арифметической прогрессии:
[2P, 3P ..]
С этой точки зрения, чистый Эротосфен выглядит так (это все можно
описать на Рефале, но у меня нет Рефала):
primesE1 :: [Integer]
primesE1 = sieve [2..]
where sieve (p:xs) = let c1 = 2*p
c2 = c1 + p
in p : sieve (minusOS xs [c1, c2 ..])
> primesE1!!2000
> 17389
> (14831010 reductions, 27298448 cells, 2 garbage collections)
здесь minusOS -- вычитание "упорядоченных множеств", представленных
отсортированными ленивыми бесконечными списками:
minusOS :: [Integer] -> [Integer] -> [Integer]
minusOS (x:xs) (y:ys)
| x < y = x : minusOS xs (y:ys)
| x == y= minusOS xs ys
| x > y = x : minusOS xs ys
То, что нашел Антон, это следующих шаг: давайте вычеркивать будем не
как сказал Эратосфен, а вычеркнем ОБъЕДИНЕНИЕ всего, что советовал
вычеркивать Эратосфен. Появляется функция объединения бесконечного
множетсва "упорядоченных множеств", причем они перечислены в порядке
роста первого элемента:
primesE :: [Integer]
primesE = 2 : minusOS [3 ..] cs
where
cs = unionOS
[ [c1, c2 ..] | p <- primesE,
let c1 = 2*p,
let c2 = c1+p
]
unionOS :: [[Integer]] -> [Integer]
unionOS ((x:xs) : xss) = x : unionOS (ins xs xss)
where ins (x:xs) ((y:ys) : yss)
| x < y = (x:xs) : (y:ys) : yss
| x > y = (y:ys) : ins (x:xs) yss
| x == y= (y:ys) : ins xs yss
и этот вариант не лучше, а хуже (в 3+ раз) обычного Эратосфена:
> primesE!!2000
> 17393
> (47292483 reductions, 83601402 cells, 8 garbage collections)
А вот дальше можно сообразить, что первое число, которое удастся
простому P действительно вычеркнуть своей арифметической прогрессией
[2P, 3P..] будет P*P -- все меньшие члены прогрессии уже вычеркнуты до
него. Поэтому прогрессию можно заменить на [P*P, P*P+P ..]
В коде выше это замена всех c1 = 2*p на c1 = p*P
И это ускоряет обычный Эратосфен совсем немного, а Эратосфен с
объединением unionOS -- радикально и он на порядок (раз в 6) обгоняет
обычного!
> primesE1!!2000
> 17387
> (14692824 reductions, 27114235 cells, 2 garbage collections)
> primesE!!2000
> 17393
> (2731729 reductions, 4790869 cells)
Тупое прозрачное опредленеие "N -- простое, если не делется ни на одно
простое P, которое P*P<=K" дает лишь втрое хуже результат:
primes1 = 2 : filter (isPrime1) [3..]
where isPrime1 n = null(filter (\ p -> n`mod`p == 0)
(takeWhile (\ k -> (k*k <= n)) primes1))
> primes1!!2000
> 17393
> (7394774 reductions, 10355414 cells, 1 garbage collection)
И, наконец, мное описанное решето Абрамова-Дейкстры(?), которое не про
вычеркивание, не про делимость, а про взаимную простоту с
произведением простых (gcd pp n)==1 дает такой результат:
primesA = sieve 1 1 2 [2..]
where sieve pp n' lim ns@(n:ns1)
| n <= lim = n : sieve (pp*n) n lim ns1
| otherwise = sieve 1 1 (n'*n') (filter ((== 1).(gcd pp)) ns)
> primesA!!2000
> 17393
> (4254611 reductions, 7465819 cells)
что хуже ("всего" в 1.55 раз) Эратосфена вычеркивания с объединением
unionOS.
Это все.
Есть еще одна идея ускорения, но она применимы ко всем. И если
исследовать ее эффект, то применяя ее ко всем. В коде, который Антон
Орлов нашек на Haskell.org эта идея применена для No=1.
Идея состоит в том, что можно "в уме" вычеркнуть из [2..] все числа,
которые делятся на 2, 3, 5, ... p (взято No первых простых чисел).
Пусть ns результат вычеркивания. Тогда он определяется вот так:
Пусть pp произведение 2, 3, 5, ... p. Тогда:
ns = [ n+r | n <- [pp, 2*pp ...], r <- rs ]
where rs = filter ((==1).(gcd pp)) [p+1-pp .. p]
Если взять No простых, то после фильтрации в rs останется не pp
элементов, a меньше -- length rs. И от исходного множества у нас
останется вот такая доля: (length rs) / pp. Как-то так:
No length rs pp Доля
1 1 2 50.0%
2