Сито сундарама-список понимания
Я пытаюсь написать функцию, которая вычисляет всех нечетных чисел от 1..n используя "решето Сундарама".
вот моя попытка:
sSund :: Integer -> [Integer]
sSund n = [ i * 2 + 1 | i <- [1..n], j <- [f i], (i + j + 2 * i * j) > n ]
where f 1 = 1
f y = y + 1 --use function f because i don't know how insert 1 into j's list
но он дает некоторые неправильные числа, такие как 9,15,21,25 и т. д.
*Main> sSund 30
[7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41,43,45,47,49,51,53,55,57,59,61]
что я делаю не так?
1 ответов
как это работает
seive Сундарама работает, фокусируясь на нечетных числах 2n+1 и исключая те, которые являются произведением чисел.
если два числа умножаются, чтобы сделать нечетное число, они должны быть нечетными, поэтому наше число 2n+1 = (2i+1) (2j+1). Если мы умножим, что мы выходим из 2n+1 = 4ij + 2И +2Д + 1, который можно упростить до 2n=4ij+2И+2дж, что опять же упрощает до n=2ij+я+Дж. Поэтому мы не хотим, если мы можем написать его как 2ij+я+Дж. Это верно для любых чисел I и j, но это нормально, просто избавиться от тех, где i
исправление кода
в вашем коде, вы создаете некоторые цифры i + j + 2 * i * j
быть исключенным, но вы на самом деле просто исключаете i
вместо i + j + 2 * i * j
. The j<-[f i]
просто дает вам один j
значение в списке вместо всех чисел из i
до n
, который вы должны написать как [i..n]
.
это много проще всего сначала создать список исключений:
sSundDelete :: Integer -> [Integer]
sSundDelete n = [i+j+2*i*j|i<-[1..n], j<-[i..n]]
здесь я решил просто разрешить i
и j
между 1
и n
, потому что в противном случае 2ij+i+j определенно больше, чем n.
теперь мы можем составить список чисел x
которые не включают эти числа, а затем делают их нечетными с помощью формулы 2*n+1
:
sSund :: Integer -> [Integer]
sSund n = let del = sSundDelete n in
2:[2*x+1 | x <- [1..n], not (x `elem` del)]
, который правильно дает вам
> sSund 30
[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61]
ускорение вещи вверх!--37-->
это не так быстро, как могло бы быть, потому что, если вы посмотрите на
> sSundDelete 10
[4,7,10,13,16,19,22,25,28,31,12,17,22,27,32,37,42,47,52,24,31,38,45,52,59,66,73,40,49,58,67,76,85,94,60,71,82,93,104,115,84,97,110,123,136,112,127,142,157,144,161,178,180,199,220]
он имеет номера гораздо больше, чем нам нужно - sSund 10
доходит только до 2*10+1=21. Это означает, что мы проверяем наши номера снова и снова против номеров, которые мы не рассматривали в любом случае!
самое простое, что нужно сделать, это переписать sSundDelete
сказать
sSundDelete n = [i+j+2*i*j|i<-[1..n], j<-[i..n],i+j+2*i*j<=n]
так же, как и ты, или
sSundDelete n = filter (<= n) [i+j+2*i*j|i<-[1..n], j<-[i..n]]
используя немного математики, чтобы ускорить
проблема в том, что они генерируют слишком много чисел, а затем выбрасывают их. Было бы быстрее генерировать только те числа, которые нам нужны.
на самом деле, я думаю, что лучше рассчитать, как далеко идти. самый маленький j
мы всегда будем использовать is i
, так что наименьшее, что 2ij+i+j может быть 2i2+2i. Если мы не хотим, чтобы это было над n, мы хотим 2i2+2i2i <= floor (sqrt (fromIntegral n / 2)) (floor
усекает десятичные дроби, так что floor 35.7
и в 35, и fromIntegral
используется здесь для преобразования n
к числу с плавающей запятой (разрешая нецелые числа), поэтому мы можем делать деление и квадратные корни.
это было много работы, но теперь мы можем просто вычислить один раз, насколько большой i
должны go:
sSundDelete n = filter (<= n) [i+j+2*i*j|i<-[1..floor (sqrt (fromIntegral n / 2))], j<-[i..n]]
мы можем сделать аналогичную работу на j. Мы хотим 2ij+i+jj<=floor( (n'-i')/(2*i'+1)) здесь i'=fromIntegral i
и n'=fromIntegral n
. Это дает нам
sSundDelete n = [i+j+2*i*j|let n'=fromIntegral n,
i<-[1..floor (sqrt (n' / 2))],
let i' = fromIntegral i,
j<-[i..floor( (n'-i')/(2*i'+1))]]
это делает его достаточно быстрым для меня, чтобы не отказаться от ожидания sSund 5000
для вычисления второго простого числа!