двойной поток для предотвращения ненужных мемоизация?
Я новичок в Haskell и пытаюсь реализовать решето Эйлера в стиле потоковой обработки.
Когда я проверилHaskell wiki о простых числах , я нашел какой-то таинственный метод оптимизации потоков. В 3.8 линейное слияние этой Вики:
primesLME = 2 : ([3,5..] `minus` joinL [[p*p, p*p+2*p..] | p <- primes'])
where
primes' = 3 : ([5,7..] `minus` joinL [[p*p, p*p+2*p..] | p <- primes'])
joinL ((x:xs):t) = x : union xs (joinL t)
И он говорит
"подача двойных простых чисел введена здесь, чтобы предотвратить ненужное запоминание и, таким образом, предотвращение утечки памяти, согласно Мелиссе О'Нил. код. "
Как это может быть? Я не могу понять, как это работает.
1 ответ:
Обычно определение потока простых чисел в формулировке Ричарда Берда о решете Эратосфена является самореферентным:
import Data.List.Ordered (minus, union, unionAll) ps = ((2:) . minus [3..] . foldr (\p r -> p*p : union [p*p+p, p*p+2*p..] r) []) psПростые числа
Произведенный поток используется в качестве входных данных в его собственном определении. Это приводит к сохранению всего потока простых чисел в памяти (или большей его части). Точка фиксации здесь - совместное использование, corecursive :ps, полученные этим определением, используются в качестве входных данных для него. Чтобы предотвратить порочный круг , определение заполняется начальным значением, 2. Это соответствует математическому определению сита Эратосфена как нахождения простых чисел в промежутках между композитами, пронумерованных для каждого простого p путем подсчета в шаги p, р = {2} U ({3,4,...} \ U {{Р2, Р2+Р, Р2+2p , ...} / p in P }).fix f = xs where xs = f xs -- a sharing fixpoint combinator ps = fix ((2:) . minus [3..] . foldr (...) []) -- = xs where xs = 2 : minus [3..] (foldr (...) [] xs)
тот самый идея (из-за мелиссы О'Нил) состоит в том, чтобы разделить его на два потока, причем внутренняя петля питает второй поток простых чисел "выше" его:
Таким образом, когдаfix2 f = f xs where xs = f xs -- double-staged fixpoint combinator ps2 = fix2 ((2:) . minus [3..] . foldr (...) []) -- = 2 : minus [3..] (foldr (...) [] xs) where -- xs = 2 : minus [3..] (foldr (...) [] xs)ps2производит некоторое простоеp, его внутренний потокxsиз "ядра" простых чисел должен быть создан только доsqrt p, и любые простые числа, которые производятсяps2, могут быть отброшены и собраны системой сразу же после этого:\ \ <- ps2 <-. \ \ <- xs <-. / \ \_________/Простые числа, производимые внутренние петли
xsнельзя сразу отбросить, потому что они нужны для самого потокаxs. Когдаxsпроизвело простоеq, только его часть нижеsqrt qможет быть отброшена сразу после того, как она была поглощенаfoldrчастью вычисления. Другими словами, эта последовательность сохраняет обратный указатель в себе вплоть доsqrtсвоей самой большой произведенной ценности (поскольку она потребляется своим потребителем, какТак что с одной петлей подачи (с
Для этого код должен быть скомпилирован с оптимизацией, то есть с помощью переключателяfix) почти вся последовательность должна была бы быть сохранена в памяти, в то время как с двойной подачей (сfix2) только внутренний цикл должен быть в основном сохранен, который достигает только до квадратного корня текущего значения, произведенного главным потоком. Таким образом, общая сложность пространства уменьшается примерно с O(N) до O(sqrt(N)) - резкое сокращение.-O2, и выполняться автономно. Возможно, Вам также придется использовать переключатель-fno-cse. И в тестовом коде должна быть только одна ссылка наps2:main = getLine >>= (read >>> (+(-1)) >>> (`drop` ps2) >>> print . take 5)На самом деле, при тестировании в Ideon, он показывает практически постоянное потребление памяти.
И это сито Эратосфена, а не Эйлера.
Исходными определениями являются:
Оба они очень неэффективны из-за преждевременного обращения с кратными числами. Легко исправить первый определение путем слиянияeratos (x:xs) = x : eratos (minus xs $ map (*x) [x..] ) -- ps = eratos [2..] eulers (x:xs) = x : eulers (minus xs $ map (*x) (x:xs)) -- ps = eulers [2..]mapи перечисления в одно перечисление переместилось дальше (отxкx*x, т. е.[x*x, x*x+x..]), так что его обработка может быть отложено -- потому что здесь генерируются кратные каждого простого числасамостоятельно (перечисляются через фиксированные интервалы):eratos (p:ps) xs | (h,t) <- span (< p*p) xs = -- ps = 2 : eratos ps [2..] h ++ eratos ps (minus t [p*p, p*p+p..]) -- "postponed sieve"Что то же самое, что птичье сито в верхней части этого столба, сегментно :
ps = 2 : [n | (r:q:_, px) <- (zip . tails . (2:) . map (^2) <*> inits) ps, n <- [r+1..q-1] `minus` foldr union [] [[s+p, s+2*p..q-1] | p <- px, let s = r`div`p*p]](
(f <*> g) x = f x (g x)используется здесь как без точек стенография.)Нет простого решения для второго определения, т. е.
eulers.
дополнение: вы можете увидеть ту же идею, реализованную с генераторами Python, для сравнения, здесь.
Фактически, этот код Python использует телескопическое, многоступенчатое рекурсивное производство эфемерных простых потоков; в Haskell мы можем организовать для него с помощью неразделяемого, многоступенчатого комбинатора точек фиксации
_Y:primes = 2 : _Y ((3:) . sieve 5 . unionAll . map (\p -> [p*p, p*p+2*p..])) where _Y g = g (_Y g) -- == g . g . g . g . .... sieve k s@(x:xs) | k < x = k : sieve (k+2) s -- == [k,k+2..] \\ s, | True = sieve (k+2) xs -- when s ⊂ [k,k+2..]