2015-09-23 2 views
1

Я хочу вычислить ленивую последовательность простых чисел.Clojure Lazy Seq Результаты в переполнении стека

Вот интерфейс:

user=> (take 10 primes) 
(2 3 5 7 11 13 17 19 23 29) 

До сих пор, так хорошо.

Однако, когда я беру 500 простых чисел, это приводит к переполнению стека.

    core.clj: 133 clojure.core/seq 
        core.clj: 2595 clojure.core/filter/fn 
       LazySeq.java: 40 clojure.lang.LazySeq/sval 
       LazySeq.java: 49 clojure.lang.LazySeq/seq 
        RT.java: 484 clojure.lang.RT/seq 
        core.clj: 133 clojure.core/seq 
        core.clj: 2626 clojure.core/take/fn 
       LazySeq.java: 40 clojure.lang.LazySeq/sval 
       LazySeq.java: 49 clojure.lang.LazySeq/seq 
       Cons.java: 39 clojure.lang.Cons/next 
       LazySeq.java: 81 clojure.lang.LazySeq/next 
        RT.java: 598 clojure.lang.RT/next 
        core.clj: 64 clojure.core/next 
        core.clj: 2856 clojure.core/dorun 
        core.clj: 2871 clojure.core/doall 
        core.clj: 2910 clojure.core/partition/fn 
       LazySeq.java: 40 clojure.lang.LazySeq/sval 
       LazySeq.java: 49 clojure.lang.LazySeq/seq 
        RT.java: 484 clojure.lang.RT/seq 
        core.clj: 133 clojure.core/seq 
        core.clj: 2551 clojure.core/map/fn 
       LazySeq.java: 40 clojure.lang.LazySeq/sval 
       LazySeq.java: 49 clojure.lang.LazySeq/seq 
        RT.java: 484 clojure.lang.RT/seq 
        core.clj: 133 clojure.core/seq 
        core.clj: 3973 clojure.core/interleave/fn 
       LazySeq.java: 40 clojure.lang.LazySeq/sval 

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

Вот код.

(defn assoc-nth 
    "Returns a lazy seq of coll, replacing every nth element by val 

    Ex: 
    user=> (assoc-nth [3 4 5 6 7 8 9 10] 2 nil) 
    (3 nil 5 nil 7 nil 9 nil) 
    " 
    [coll n val] 
    (apply concat 
     (interleave 
      (map #(take (dec n) %) (partition n coll)) (repeat [val])))) 

(defn sieve 
    "Returns a lazy seq of primes by Eratosthenes' method 

    Ex: 
    user=> (take 4 (sieve (iterate inc 2))) 
    (2 3 5 7) 

    user=> (take 10 (sieve (iterate inc 2))) 
    (2 3 5 7 11 13 17 19 23 29) 
    " 
    [s] 
    (lazy-seq 
    (if (seq s) 
    (cons (first s) (sieve 
         (drop-while nil? (assoc-nth (rest s) (first s) nil)))) 
    []))) 

(def primes 
    "Returns a lazy seq of primes 

    Ex: 
    user=> (take 10 primes) 
    (2 3 5 7 11 13 17 19 23 29) 
    " 
    (concat [2] (sieve (filter odd? (iterate inc 3))))) 
+2

Ваша реализация Сита Эратосфена не является хвостовой рекурсивной (функция 'recur' никогда не используется), поэтому это приведет к удалению стека. Clojure! = Lisp, поэтому, если вы скопировали реализацию из другой версии Lisp, вам придется переработать ее, чтобы сделать ее хвостовой рекурсивной в Clojure (и, честно говоря, я не вижу, как это было бы хвостом рекурсивно в другом Lisp) .. На первый взгляд кажется, что 'sieve' нужно будет переделать, чтобы сделать его хвостовым рекурсивным. Удачи. –

+1

lazy-seq может устранить эту проблему с помощью непрямого обращения, так что здесь есть и другая проблема. – noisesmith

+0

Мне не удалось воспроизвести переполнение стека, и ваш код кажется прекрасным. Когда я увеличиваю лимит от 500 до 1000, программа становится слишком медленной, чтобы я был достаточно терпелив, а использование кучи огромно. – Davyzhu

ответ

4

Вы генерируете множество ленивых последовательностей, которые занимают пространство стека.

разбив его:

user=> (iterate inc 3) 
;; produces first lazy seq (don't run in repl!) 
(3 4 5 6 7 8 9 10 11 12 13 ...) 

user=> (filter odd? (iterate inc 3)) 
;; again, don't run in repl, but lazy seq #2 
(3 5 7 9 11 13 ...) 

лично я сделал бы (iterate #(+ 2 %) 3) вырезать последовательность, но это капля в море по сравнению с общей проблемой.

Теперь мы начинаем в решето, который начинает создавать новый ленивый SEQ для нашего выхода

(lazy-seq 
(cons 3 (sieve (drop-while nil? (assoc-nth '(5 7 9 ...) 3 nil))))) 

Прыжков в ассоциативный энном, мы начинаем создавать более ленивые последовательности

user=> (partition 3 [5 7 9 11 13 15 17 19 21 23 25 ...]) 
;; lazy seq #4 
((5 7 9) (11 13 15) (17 19 21) (23 25 27) ...) 

user=> (map #(take 2 %) '((5 7 9) (11 13 15) (17 19 21) (23 25 27) ...)) 
;; lazy seq #5 
((5 7) (11 13) (17 19) (23 25) ...) 

user=> (interleave '((3 5) (9 11) (15 17) (21 23) ...) (repeat [nil])) 
;; lazy seq #6 + #7 (repeat [nil]) is a lazy seq too 
((5 7) [nil] (11 13) [nil] (17 19) [nil] (23 25) [nil] ...) 

user=> (apply concat ... 
;; lazy seq #8 
(5 7 nil 11 13 nil 17 19 nil 23 25 nil ...) 

Так уже у вас есть 8 ленивых последовательностей, и мы применили только первое сито.

Вернуться к решету, и это делает каплю в то время, которое производит ленивый порядковый номер 9

user=> (drop-while nil? '(5 7 nil 11 13 nil 17 19 nil 23 25 nil ...)) 
;; lazy seq #9 
(5 7 11 13 17 19 23 25 ...) 

Таким образом, для одной итерации решета, мы генерируемые 9 последовательностей.

В следующей итерации сита вы создаете новый ленивый seq (а не оригинал!), И процесс начинается снова, генерируя еще 7 последовательностей в петлю.

Следующая проблема заключается в эффективности вашей функции assoc-nth. Последовательность чисел и nils не является эффективным способом для обозначения кратных значений конкретного фактора. Вы очень быстро закончите очень много длинных последовательностей, которые не могут быть выпущены, в основном заполнены нулями, и вам нужно прочитать более длинные и длинные последовательности, чтобы решить, является ли кандидат фактором или нет - это неэффективно для ленивые последовательности, которые считывают фрагменты из 32 записей, чтобы быть эффективными, поэтому, когда ваш факторинг из 33 и выше, вы тянете несколько фрагментов последовательности для работы.

Также каждый раз, когда вы делаете это в совершенно новой последовательности.

Добавление некоторой отладки к вашему ассоциированному-n-му и ее запуск на небольшом образце быстро иллюстрируют это.

user=> (sieve (take 50 (iterate #(+ 2 %) 3))) 
assoc-nth n: 3 , coll: (5 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 63 65 67 69 71 73 75 77 79 81 83 85 87 89 91 93 95 97 99 101) 
returning r: (5 7 nil 11 13 nil 17 19 nil 23 25 nil 29 31 nil 35 37 nil 41 43 nil 47 49 nil 53 55 nil 59 61 nil 65 67 nil 71 73 nil 77 79 nil 83 85 nil 89 91 nil 95 97 nil) 
assoc-nth n: 5 , coll: (7 nil 11 13 nil 17 19 nil 23 25 nil 29 31 nil 35 37 nil 41 43 nil 47 49 nil 53 55 nil 59 61 nil 65 67 nil 71 73 nil 77 79 nil 83 85 nil 89 91 nil 95 97 nil) 
returning r: (7 nil 11 13 nil 17 19 nil 23 nil nil 29 31 nil nil 37 nil 41 43 nil 47 49 nil 53 nil nil 59 61 nil nil 67 nil 71 73 nil 77 79 nil 83 nil nil 89 91 nil nil) 
assoc-nth n: 7 , coll: (nil 11 13 nil 17 19 nil 23 nil nil 29 31 nil nil 37 nil 41 43 nil 47 49 nil 53 nil nil 59 61 nil nil 67 nil 71 73 nil 77 79 nil 83 nil nil 89 91 nil nil) 
returning r: (nil 11 13 nil 17 19 nil 23 nil nil 29 31 nil nil 37 nil 41 43 nil 47 nil nil 53 nil nil 59 61 nil nil 67 nil 71 73 nil nil 79 nil 83 nil nil 89 nil) 
;; ... 
assoc-nth n: 17 , coll: (19 nil 23 nil nil 29 31 nil nil 37 nil 41 43 nil 47 nil nil 53 nil nil 59 61 nil nil) 
returning r: (19 nil 23 nil nil 29 31 nil nil 37 nil 41 43 nil 47 nil nil) 
assoc-nth n: 19 , coll: (nil 23 nil nil 29 31 nil nil 37 nil 41 43 nil 47 nil nil) 
returning r:() 
(3 5 7 11 13 17 19) 

Это показывает, как вам нужно все больше и больше элементов последовательности для получения списка простых чисел, так как я начал с нечетными номерами 3 до 99, но только в конечном итоге с простыми числами 3 до 19 лет, но на последнем итерации n = 19, в моей конечной последовательности было недостаточно элементов, чтобы вывести дальнейшие кратные.

Есть ли решение?

Я думаю, что вы ищете ответы на вопрос «как сделать ленивые последовательности лучше делать то, что я хочу?». В этом случае ленивые последовательности будут компромиссом. Ваш алгоритм работает, но генерирует слишком много стека. Вы не используете какой-либо recur, поэтому вы генерируете последовательности последовательностей. Первое, на что нужно обратить внимание, - это сделать ваши методы более рекурсивными и потерять некоторые последовательности. Я не могу предлагать решения для этого здесь, но я могу связать другие решения с одной и той же проблемой и посмотреть, есть ли области, которые они делают лучше, чем ваши.

Есть 2 достойных решения по номерам this implementation of Eratosthenes primes. Один использует диапазон и просеивает его, а другой использует массивы булевых (что на 40 раз быстрее). associated article с ним (в Japenese, но google хорошо переводится в Chrome) хорошо читается и показывает время наивных подходов к очень сфокусированной версии, используя массивы напрямую и набирайте подсказки, чтобы избежать дальнейших проблем с распаковкой в ​​jvm.

Существует another SO question, который имеет хорошую реализацию с использованием переходных значений для повышения эффективности. У этого метода есть похожие методы фильтрации.

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

Для несвязанного случая, посмотрите на this gist для бесконечной последовательности простых чисел. В моих тестах это было на 6 раз медленнее, чем предыдущий вопрос SO, но все еще довольно хорош. Однако реализация очень отличается.

Смежные вопросы