Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/clojure/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Recursion clojure-(另一个)带有循环/重复的StackOverflow_Recursion_Clojure - Fatal编程技术网

Recursion clojure-(另一个)带有循环/重复的StackOverflow

Recursion clojure-(另一个)带有循环/重复的StackOverflow,recursion,clojure,Recursion,Clojure,我知道这是一个反复出现的问题(,等等),我知道这个问题与创建惰性顺序有关,但我不明白为什么它失败了 问题是:我编写了一个(不是很好的)快速排序算法来对使用循环/递归的字符串进行排序。但应用到10000个元素,我得到一个StackOverflower错误: (defn qsort [list] (loop [[current & todo :as all] [list] sorted []] (cond (nil? current) sorted

我知道这是一个反复出现的问题(,等等),我知道这个问题与创建惰性顺序有关,但我不明白为什么它失败了

问题是:我编写了一个(不是很好的)快速排序算法来对使用循环/递归的字符串进行排序。但应用到10000个元素,我得到一个StackOverflower错误:

(defn qsort [list]
  (loop [[current & todo :as all] [list] sorted []]
    (cond 
       (nil? current) sorted 
       (or (nil? (seq current)) (= (count current) 1)) (recur todo (concat sorted current))
       :else (let [[pivot & rest] current
                  pred #(> (compare pivot %) 0)
                  lt (filter pred rest)
                  gte (remove pred rest)
                  work (list* lt [pivot] gte todo)] 
                (recur work sorted)))))
我是这样使用的:

(defn tlfnum [] (str/join (repeatedly 10 #(rand-int 10))))
(defn tlfbook [n] (repeatedly n #(tlfnum)))
(time (count (qsort (tlfbook 10000))))
这是堆栈跟踪的一部分:

  [clojure.lang.LazySeq seq "LazySeq.java" 49]
  [clojure.lang.RT seq "RT.java" 521]
  [clojure.core$seq__4357 invokeStatic "core.clj" 137]
  [clojure.core$concat$fn__4446 invoke "core.clj" 706]
  [clojure.lang.LazySeq sval "LazySeq.java" 40]
  [clojure.lang.LazySeq seq "LazySeq.java" 49]
  [clojure.lang.RT seq "RT.java" 521]
  [clojure.core$seq__4357 invokeStatic "core.clj" 137]]}
据我所知,loop/recur执行尾部调用优化,因此不使用堆栈(实际上,这是一个使用递归语法编写的迭代过程)


阅读其他答案,由于堆栈跟踪,我发现
concat
存在问题,在
concat
之前添加
doall
可以解决堆栈溢出问题。但是为什么?

以下是concat的两个arity版本的部分代码

(defn concat [x y]
  (lazy-seq
   (let [s (seq x)]
     ,,,))
  )
请注意,它使用了另外两个函数,
lazy seq
seq
lazy seq
有点像lambda,它包装了一些代码,但还没有执行。
lazy seq
块中的代码必须产生某种序列值。当您对
惰性序列
调用任何序列操作时,它将首先计算代码(“实现”惰性序列),然后对结果执行该操作

(def lz (lazy-seq
         (println "Realizing!")
         '(1 2 3)))

(first lz)
;; prints "realizing"
;; => 1
现在试试这个:

(defn lazy-conj [xs x]
  (lazy-seq
   (println "Realizing" x)
   (conj (seq xs) x)))
请注意,它类似于
concat
,它在第一个参数上调用
seq
,并返回一个
lazy seq

(def up-to-hundred
  (reduce lazy-conj () (range 100)))

(first up-to-hundred)
;; prints "Realizing 99"
;; prints "Realizing 98"
;; prints "Realizing 97"
;; ...
;; => 99
即使您只要求第一个元素,它仍然最终实现了整个序列。这是因为实现外部“层”会导致在下一个“层”上调用
seq
,这会实现另一个懒惰的seq,它会再次调用seq,等等。因此,实现一切都是一个连锁反应,每一步都消耗一个堆栈帧

(def up-to-ten-thousand
  (reduce lazy-conj () (range 10000)))

(first up-to-ten-thousand)
;;=> java.lang.StackOverflowError
堆叠
concat
调用时也会遇到同样的问题。这就是为什么例如
(减少concat,,,)
总是一种气味,相反,您可以使用
(应用concat,,,)
(导入()cat,,,)

其他惰性操作符,如
filter
map
可能会出现完全相同的问题。如果你真的有一系列的转换步骤考虑使用换能器,

;; without transducers: many intermediate lazy seqs and deep call stacks
(->> my-seq
     (map foo)
     (filter bar)
     (map baz)
     ,,,)


;; with transducers: seq processed in a single pass
(sequence (comp
           (map foo)
           (filter bar)
           (map baz))
          my-seq)
阿恩有一个很好的答案(事实上,我以前从未注意到猫)。如果您想要更简单的解决方案,可以使用
glue
功能:


胶合在一起就像是收集 concat函数有时会产生相当令人惊讶的结果:

(concat {:a 1} {:b 2} {:c 3} )
;=>   ( [:a 1] [:b 2] [:c 3] )
在本例中,用户可能打算将3张地图合并为一张。取而代之的是,这三张地图被神秘地转换成长度为2的向量,然后嵌套在另一个序列中

conj功能还可以让用户感到惊讶:

(conj [1 2] [3 4] )
;=>   [1 2  [3 4] ]
在这里,用户可能想拿回
[1 2 3 4]
,但却错误地得到了一个嵌套向量

我们不必怀疑要合并的项是否会合并、嵌套或转换为另一种数据类型,而是提供了粘合函数,以便始终将类似的集合合并为相同类型的结果集合:

; Glue together like collections:
(is (= (glue [ 1 2] '(3 4) [ 5 6] )       [ 1 2 3 4 5 6 ]  ))   ; all sequential (vectors & lists)
(is (= (glue {:a 1} {:b 2} {:c 3} )       {:a 1 :c 3 :b 2} ))   ; all maps
(is (= (glue #{1 2} #{3 4} #{6 5} )      #{ 1 2 6 5 3 4 }  ))   ; all sets
(is (= (glue "I" " like " \a " nap!" )   "I like a nap!"   ))   ; all text (strings & chars)

; If you want to convert to a sorted set or map, just put an empty one first:
(is (= (glue (sorted-map) {:a 1} {:b 2} {:c 3})   {:a 1 :b 2 :c 3} ))
(is (= (glue (sorted-set) #{1 2} #{3 4} #{6 5})  #{ 1 2 3 4 5 6  } ))
如果要“粘合”的集合不是所有类型的集合,将引发异常。允许的输入类型包括:

  • 所有顺序:列表和向量的任意组合(向量结果)
  • 所有地图(已排序或未排序)
  • 所有集合(已排序或未排序)
  • 所有文本:字符串和字符的任意组合(字符串结果)

我将
glue
而不是
concat
添加到您的代码中,仍然得到了一个StackOverflower错误。因此,我还将懒惰的
过滤器
删除
替换为急切版本的
保留if
删除if
,以获得以下结果:

(defn qsort [list]
  (loop [[current & todo :as all] [list] sorted []]
    (cond
      (nil? current) sorted

      (or (nil? (seq current)) (= (count current) 1))
          (recur todo (glue sorted current))

      :else (let [[pivot & rest] current
                  pred #(> (compare pivot %) 0)
                  lt   (keep-if pred rest)
                  gte  (drop-if pred rest)
                  work (list* lt [pivot] gte todo)]
              (recur work sorted)))))

(defn tlfnum [] (str/join (repeatedly 10 #(rand-int 10))))
(defn tlfbook [n] (repeatedly n #(tlfnum)))
(def result
  (time (count (qsort (tlfbook 10000)))))

-------------------------------------
   Clojure 1.8.0    Java 1.8.0_111
-------------------------------------
"Elapsed time: 1377.321118 msecs"
result => 10000

相关和有趣的文章:谢谢阿恩!有了你的回答,再加上斯图尔特·塞拉的一点帮助,所有的部件终于开始装配在一起了。:-)谢谢你,艾伦!事实上,tupelo看起来像一个有趣的图书馆。但我的问题更多的是“为什么”,而不是“如何”