我知道这不是惯用的Clojure。在现实世界中,我会使用向量。但是,只是好奇,有没有一种方法可以使add-node
在不引起堆栈溢出的情况下工作?例如,也许通过某种方式使列表中的最后一个节点发生变异?
(defrecord ListNode [value next])
(defn add-node [^ListNode curr v]
(if curr
(assoc curr :next (add-node (:next curr) v))
(ListNode. v nil)))
//stackoverflow!
(def result
(loop [l nil i 0]
(if (< i 100000)
(recur (add-node l i) (inc i))
l)))
答案 0 :(得分:1)
Clojure的实现方式是反向构造一个常规列表,将新元素添加到列表的开头。这样,您的所有操作都只会影响(新)头节点,并且您不需要消耗堆栈。然后,最后,您只需反转(单链列表):
(dotest
(let [result-1 (loop [cum nil
val 0]
(if (< val 100000)
(recur
(cons val cum)
(inc val))
cum))
result-2 (reverse result-1)]
(is= (take 10 result-1)
[99999 99998 99997 99996 99995 99994 99993 99992 99991 99990])
(is= (take 10 result-2)
[0 1 2 3 4 5 6 7 8 9])))
如果您确实是真的 想要,则可以根据需要使用ListNode
结构重新进行广播(但是,为什么?)。
话虽如此,如果要按顺序构建列表,而又不反转任何内容,只需使用Java LinkedList:
(ns xxx
(:import [java.util LinkedList]))
(dotest
(let [linked-list (LinkedList.) ]
(dotimes [i 100000]
(.add linked-list i) )
(is= (take 10 linked-list)
[0 1 2 3 4 5 6 7 8 9])
(is= (take-last 10 linked-list)
[ 99990 99991 99992 99993 99994 99995 99996 99997 99998 99999 ] ) ))
您也可以创建一个懒惰列表。我最喜欢的方式是by using lazy-cons
:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(defn lazy-nodes
[value limit]
(when (< value limit )
(lazy-cons value (lazy-nodes (inc value) limit)) ))
(dotest
(let [result (lazy-nodes 0 100000)]
(is= (take 10 result)
[ 0 1 2 3 4 5 6 7 8 9] )
(is= (take-last 10 result)
[ 99990 99991 99992 99993 99994 99995 99996 99997 99998 99999 ] ) ))
答案 1 :(得分:1)
您似乎缺少将列表定义为代数数据类型list x = (x, list x) | nil
这个定义的重点是它的递归性,对吗?因此使用此定义,如果要将项目x添加到现有的xs列表中,只需执行new-list = (x, list x)
。
在代码中,您的add-node
函数应定义为:
(defn add-node [^ListNode xs x] (ListNode x xs))
现在,如果要向此列表中添加大量的项目,则不会产生堆栈溢出,但是由于递归定义,您的列表将是LIFO样式列表。您插入的最后一项将是您看到的第一项。得到头是O(1),得到任何其他东西都是O(n)。该定义暗示了这一点。在您当前对add-node
的定义中,每次插入都是O(n),这可能意味着您使用的数据结构错误。
这是函数编程中非常常见的习惯用法,例如lisps(缺点列表)和haskell。如果您想翻转列表,执行反向操作非常简单。但是,在这种情况下,您可能希望一起使用其他数据结构,例如clojure的vector
或差异列表(请参见Bill Burdick的this wonderful answer)
答案 2 :(得分:1)
策略是按照相反的顺序收集列表节点,然后从下至上重建列表。可能是这样的:
(defn add-node [^ListNode curr v]
(loop [nodes () curr curr]
(if curr
(recur (cons curr nodes) (:next curr))
(reduce (fn [acc n] (assoc n :next acc))
(ListNode. v nil)
nodes))))
user> (add-node (ListNode. 1 (ListNode. 2 nil)) 100)
;;=> #user.ListNode{:value 1, :next #user.ListNode{:value 2, :next #user.ListNode{:value 100, :next nil}}}
recur
部分将列表“反向”,而将累积值(从新节点开始)减少“追加”到下一个,导致结构相同。
现在您的函数不会导致堆栈溢出(很好地尝试打印结果仍会导致堆栈溢出,但是由于另一个原因:地图只是要打印的很深)
但是对,这仅对教育有好处)