你的根本问题是,你是试图采取一种必要的方法,但Clojure集合是不可改变的。此外,我认为dotimes
将始终返回nil
,print
返回nil
后打印其参数。
有一个更好的办法,但让我们先来看看我们如何能够利用atoms获得必要的解决方案:
(defn div [x seq]
(let [r (range 1 (+ (/ x 2) 1)) ]
(dotimes [i (count (range 1 (+ (/ x 2) 1)))]
(cond
;; "Append" the ith element of r to seq
;; (Technically, we are replacing the value stored in seq
;; with a new list -- the result of conj-ing (nth r i)
;; to the current value stored in seq)
(= (mod x (nth r i)) 0) (swap! seq conj (nth r i))))) ;; <= don't print
seq) ;; <== seq is what we're interested in, so we return it here.
;; Otherwise, we would return the result of dotimes,
;; which is nil
请注意,我们已经消除了print
并期望seq
是一个原子(我们更新使用swap!
)。现在,我们可以称之为div
如下:
user> (deref (div 6 (atom [])))
[1 2 3]
我们可以通过从参数列表移动seq
到let
里面的函数,然后取消引用它,当我们回到改善这个。但首先避免突变会更好。作为tangrammer表示他的回答,这是使用for
很容易实现:
(defn div [x]
(let [r (range 1 (+ (/ x 2) 1))]
;; Loop over r, taking only elements that are divisors of x
(for [i r
:when (= (mod x i) 0)]
i))) ;; <= for accumulates the result of this expression (here simply i) into a sequence
user> (div 6)
(1 2 3)
在生产代码中,你可能会内嵌在这种情况下r
,但我想保留原始结构,尽可能地。我们也可以在:when
条款中使用zero?
。但是我们在for循环中所做的只是一个简单的过滤器,所以我们可以使用Guillermo's approach并使用filter
来代替。
这并不是说每个步骤都会覆盖“seq”。这是你永远不会保存'(conj seq(nth r i))'的返回值。你认为'conj'应该改变'seq'“到位”,但这不是Clojure数据结构的工作原理。当你使用“修改”数据结构的函数时,该函数将返回“新”数据结构,而“旧”数据结构将保持不变。 –