Почему я получаю переполнение стека? - PullRequest
4 голосов
/ 14 марта 2011

Я играл с Clojure, и мне удалось разыграть стек. В этом фрагменте кода я использую рекурсию только с recur. Я делаю много конкатенации (обратите внимание на призывы к конкатату в трассировке ниже). Я пытался занести все списки в списки, так как они ленивы, и я хотел, чтобы они оценивались по мере продвижения. Я все еще получаю переполнение стека. Вот след. Я думаю, что это может быть общей проблемой, и кто-то с большим опытом взлома clojure может указать мне правильное направление.

Вот фрагмент кода, который вызывал проблему.

(defn move-split [[xs ys]] (doall (concat (list (concat xs (list (first ys))))  (list (next ys)))))

Я положил туда doall из-за переполнения стека, но это все равно не решило проблему.

(defn move-split [[xs ys]] (doall (concat (list (doall (concat xs (list (first ys))))  ) (doall (list (next ys)))   )))

Обратите внимание на дополнительные проблемы? Здесь, везде, где я называю concat, я фильтрую результат через doall. Переполнение стека прошло.

Доал не кажется рекурсивным. То есть вложенные списки, которые также являются результатами concat, не оцениваются. Что ты думаешь?

Exception in thread "main" java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at jline.ConsoleRunner.main(Unknown Source)
Caused by: java.lang.StackOverflowError (bubble_sort2.clj:0)
    at clojure.lang.Compiler.eval(Compiler.java:5440)
    at clojure.lang.Compiler.load(Compiler.java:5857)
    at clojure.lang.Compiler.loadFile(Compiler.java:5820)
    at clojure.main$load_script.invoke(main.clj:221)
    at clojure.main$script_opt.invoke(main.clj:273)
    at clojure.main$main.doInvoke(main.clj:354)
    at clojure.lang.RestFn.invoke(RestFn.java:409)
    at clojure.lang.Var.invoke(Var.java:365)
    at clojure.lang.AFn.applyToHelper(AFn.java:163)
    at clojure.lang.Var.applyTo(Var.java:482)
    at clojure.main.main(main.java:37)
    ... 5 more
Caused by: java.lang.StackOverflowError
    at clojure.core$seq.invoke(core.clj:122)
    at clojure.core$concat$fn__3450.invoke(core.clj:599)
    at clojure.lang.LazySeq.sval(LazySeq.java:42)
    at clojure.lang.LazySeq.seq(LazySeq.java:56)
    at clojure.lang.RT.seq(RT.java:450)
    at clojure.core$seq.invoke(core.clj:122)
    at clojure.core$concat$fn__3450.invoke(core.clj:599)
    at clojure.lang.LazySeq.sval(LazySeq.java:42)
    at clojure.lang.LazySeq.seq(LazySeq.java:56)
    at clojure.lang.RT.seq(RT.java:450)
    at clojure.core$seq.invoke(core.clj:122)
    at clojure.core$concat$fn__3450.invoke(core.clj:599)
    at clojure.lang.LazySeq.sval(LazySeq.java:42)
    at clojure.lang.LazySeq.seq(LazySeq.java:56)
    at clojure.lang.RT.seq(RT.java:450)
    at clojure.core$seq.invoke(core.clj:122)
    at clojure.core$concat$fn__3450.invoke(core.clj:599)
    at clojure.lang.LazySeq.sval(LazySeq.java:42)
    at clojure.lang.LazySeq.seq(LazySeq.java:56)
    at clojure.lang.RT.seq(RT.java:450)
    at clojure.core$seq.invoke(core.clj:122)
    at clojure.core$concat$fn__3450.invoke(core.clj:599)

Ответы [ 3 ]

6 голосов
/ 14 марта 2011

Вы накапливаете кучу ленивых операций подряд, создавая выражение вроде

(concat (concat (concat ... [3]) [2]) [1])

или аналогичный. Чтобы определить даже первый элемент в результирующем списке, компилятор должен углубиться в этот стек функций, которые вы ему дали. Постарайтесь структурировать свой код так, чтобы этого не происходило, или добавляйте (doall) время от времени, чтобы вызвать энергичные / строгие вычисления. Однако я не могу вдаваться в подробности, используя лишь трассировку стека - код поможет.

1 голос
/ 14 марта 2011

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

Похоже, вы конвертируете [[1 2 3 4] [5 6 7]] в [[1 2 3 4 5] [6 7]]. Списки и ленивые конкататы на самом деле не являются правильным решением этой проблемы, и это одна из причин, по которой она причиняет вам столько боли. Мой инстинкт должен начинаться с вектора [1 2 3 4 5 6 7], который вы постоянно сохраняете, и по мере необходимости строить подвеки этого вектора. subvec дешево и быстро, как для построения, так и для использования.

Например:

(defn vector-split [v at]
  [(subvec v 0 at) (subvec v at)])
0 голосов
/ 06 ноября 2016

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

boot.user=> (defn move-split [[xs ys]]
       #_=>   (doall
       #_=>     (concat
       #_=>       (list (concat xs (list (first ys))))
       #_=>       (list (next ys)))))
#'boot.user/move-split
boot.user=> (def x (move-split [(range 5e0) (range 5e0)]))
#'boot.user/x
boot.user=> x
((0 1 2 3 4 0) (1 2 3 4))
boot.user=> (def x (move-split [(range 5e9) (range 5e9)]))
#'boot.user/x
boot.user=> (-> (nth x 0) first)
0
boot.user=> (-> (nth x 1) first)
1

Однако есть много возможностей сделать эту функцию более идиоматичной.Глядя на функцию:

(defn move-split [[xs ys]]
  (doall
    (concat
      (list (concat xs (list (first ys))))
      (list (next ys)))))

В строке 4: нам не нужно создавать list, поскольку concat уже возвращает последовательность.Точно так же нам не нужно создавать list в строке 5, next уже возвращает последовательность.С этими двумя изменениями:

(defn move-split [[xs ys]]
  (doall
    (concat
      (concat xs (list (first ys)))
      (next ys))))

Теперь, почему в мире мы испортили бы совершенно хорошую (ленивую) функцию, вставив doall вверху?Давайте избавимся от этого.Кроме того, поскольку функция должна возвращать последовательность из двух элементов, почему бы просто не сделать это вместо вызова concat в строке 3?Идиоматический способ построения двухэлементной последовательности - через векторный литеральный синтаксис.С этими двумя изменениями мы имеем:

(defn move-split [[xs ys]]
  [(concat xs (list (first ys)))(next ys)])

О, и мы можем снова применить векторный литеральный синтаксис вместо этого list конструктора, что дает нам окончательную версию:

(defn move-split [[xs ys]]
  [(concat xs [(first ys)])(next ys)])

Теперь давайте попробуем это fn out ...

boot.user=> (defn move-split [[xs ys]]
       #_=>   [(concat xs [(first ys)])(rest ys)])
#'boot.user/move-split
boot.user=> [(range 5e0) (range 5e0)]
[(0 1 2 3 4) (0 1 2 3 4)]
boot.user=> (def x (move-split [(range 5e0) (range 5e0)]))
#'boot.user/x
boot.user=> x
[(0 1 2 3 4 0) (1 2 3 4)]
boot.user=> (-> (nth x 0) first)
0
boot.user=> (-> (nth x 1) first)
1

Кажется, это работает.Что если у нас есть две огромные подпоследовательности?Давайте попробуем это с парой подпоследовательностей в 5 миллиардов элементов:

boot.user=> (def x (move-split [(range 5e9) (range 5e9)]))
#'boot.user/x
boot.user=> (-> (nth x 0) first)
0
boot.user=> (-> (nth x 1) first)
1

К вашему первоначальному вопросу, как я уже сказал, я не знаю, какие входные данные вызывали исходную функцию, которая взорвала стек.Я видел это в документации concat doc :

Конкатенация конкатенаций не выравнивается автоматически!Таким образом, clj :: clojure.core / redux с concat генерирует чрезвычайно вложенные структуры и может легко генерировать переполнения стека при попытке пройти по результирующей последовательности.(применить concat ...) должно быть предпочтительным.

Давайте использовать reduce to concat последовательность из 10 последовательностей из 10 целых чисел:

boot.user=> (take 5 (reduce concat [] (for [x (range 1e1)] (range 1e1))))
(0 1 2 3 4)

Но если мыпопробуйте использовать от reduce до concat последовательность из 10 000 последовательностей из 10 целых чисел ::

boot.user=> (take 5 (reduce concat [] (for [x (range 1e5)] (range 1e1))))

java.lang.StackOverflowError: 

Использование apply вместо этого работает нормально:

boot.user=> (take 5 (apply concat (for [x (range 1e5)] (range 1e1))))
(0 1 2 3 4)

Просто кое-что сохранитьпри рекурсивном применении concat.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...