Простые числа в Хаскеле - PullRequest
5 голосов
/ 13 мая 2019

Я изучаю Haskell, и я попытался создать бесконечный список простых чисел, но я не могу понять, что моя функция делает неправильно.

Функция:

prime = 2:3:filter (\x -> all (\y -> (mod x y) > 0) (init prime)) [5..]

Я думаю, что это init prime, но странно то, что даже если я установлю верхнюю границу диапазона (например, 5..10), функция зациклится навсегда и никогда не получит никакого результата для prime !! 2

Подскажите, пожалуйста, что я делаю не так?

Ответы [ 2 ]

10 голосов
/ 13 мая 2019

Итак, давайте посмотрим, что init делает для конечного списка:

init [1] == []
init [1,2] == [1]
init [1,2,3] == [1,2]

ОК, так что это дает нам все, кроме последнего элемента списка.

Так что же такое init primes?Ну, prime без последнего элемента.Надеемся, что если мы правильно реализовали prime, у него не должно было бы иметь последний элемент (потому что существует бесконечно много простых чисел!), Но, что более важно, нам пока не нужно заботиться, потому что у нас нетв любом случае, полный список на данный момент - мы заботимся только о первой паре элементов, так что для нас это почти то же самое, что и сам prime.

Теперь, посмотрим наall: Что это делает?Ну, он берет список и предикат и говорит нам, если все элементы списка удовлетворяют предикату:

all (<5) [1..4] == True
all even [1..4] == False

он работает даже с бесконечными списками!

all (<5) [1..] == False

так чтоздесь происходит?Ну, вот в чем дело: он работает с бесконечными списками ... но только если мы можем реально оценить список до первого элемента списка, который нарушает предикат!Давайте посмотрим, верно ли это здесь:

all (\y -> (mod 5 y) > 0) (init prime)

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

Теперь давайте посмотрим на определение простого числа, мы получим

all (\y -> (mod 5 y) > 0) (2:3:filter (\x -> all (\y -> (mod x y) > 0) (init prime)) [5..])

Итак, чтобы определить, является ли 5 ​​простым числом, нам нужно только проверить,это:

  1. делится на 2 - это не так, давайте продолжим
  2. делится на 3 - все еще нет
  3. делится на ...?Что ж, мы находимся в процессе проверки, что такое 3-е простое число, поэтому мы еще не знаем ...

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

Так как же нам поступить так?Ну, к сожалению, нам придется изменить нашу логику.Одна вещь, которую мы можем сделать, это попытаться вспомнить, сколько простых чисел у нас уже есть, и взять для сравнения только столько, сколько нам нужно:

prime = 2 : 3 : morePrimes 2 [5..]
  morePrimes n (x:xs)
    | all (\y -> mod x y > 0) (take n prime) = x : morePrimes (n+1) xs
    | otherwise                              = morePrimes n xs

Так как же это работает?Что ж, это в основном то, о чем мы только что говорили: мы помним, сколько простых чисел у нас уже есть (начиная с 2, потому что мы знаем, что у нас есть по крайней мере [2,3] в n. Затем мы проверяем, делится ли наше следующее простое числос помощью любого из n простых чисел, которые мы уже знаем с помощью take n, и, если это так, мы знаем, что это наше следующее простое число, и нам нужно увеличить n - в противном случае мы просто продолжаем.

Естьтакже более известная форма, вдохновленная (хотя и не совсем такой, как) Ситом Эратосфена:

prime = sieve [2..] where
  sieve (p:xs) = p : sieve (filter (\x -> mod x p > 0) xs)

, так как это работает? Ну, опять же, с похожей идеей: мы знаем, что следующее простое числочисло должно быть не делимым на любое предыдущее простое число. Итак, что нам делать? Итак, начиная с 2, мы знаем, что первый элемент в списке является простым числом. Затем мы отбрасываем каждое число, делимое на это простое число.число с использованием filter. И после этого следующий элемент в списке снова будет простым числом (потому что мы его не выбросили), поэтому мы можем повторить процесс.

Ни один из этих лайнеров не похож на тот, на который вы надеялись.

1 голос
/ 29 мая 2019

Если код в другой ответ реструктурируется под идентификатором

[take n primes | n <- [0..]]  ==  inits primes

, в конце концов мы получаем

import Data.List
              -- [ ([], 2), ([2], 3), ([2,3], 5), ... ]
primes = 2 : [ c | (ps, p) <- zip (inits primes) primes,  
                   c <- take 1 [c | c <- [p+1..], 
                                    and [mod c p > 0 | p <- ps]]]

Дальнейшее его улучшение алгоритмически, оно становится

primes = 2 : [ c | (ps, r:q:_) <- zip (inits primes)                  -- [] [3,4,...]
                                      (tails $ 3 : map (^2) primes),  -- [2] [4,9,...]
                   c <- [r..q-1], and [mod c p > 0 | p <- ps]]        -- [2,3] [9,25,...]
...