用于函数重置的 clojure
Posted
技术标签:
【中文标题】用于函数重置的 clojure【英文标题】:clojure for function resets let 【发布时间】:2018-06-04 00:30:28 【问题描述】:我正在尝试解决4clojure.com 上的一个问题,我应该在不使用count
的情况下计算集合中元素的数量。我尝试了两种使用for
和let
的方法,我觉得它们应该可以工作,但似乎for-loop 一直在重置let
。
(#(for [x % :let [y 0]] (inc y)) [1 2 3 4 5])
;; which returns
(1 1 1 1 1)
(#(let [y 0] (for [x %] (inc y))) [1 2 3 4 5])
;; which returns
(1 1 1 1 1)
所以我的问题是为什么会发生这种情况,以及如何让我的“变量”为集合中的每个项目保持递增。只是说变量这个词就让我想知道我是否正在尝试制作一些不能可变的东西,但我仍然觉得这应该可行。
【问题讨论】:
在学习 clojure 时要记住的一件事是for
不是一个循环结构——它是一个列表推导式,用于创建列表。所以,当你想真正循环时,通常最好使用loop
。回顾这个,我使用了reduce
,除了纯递归之外,它是解决问题的最“函数式编程”方式(我的意思是,比显式循环更重要)。干杯,玩得开心!
【参考方案1】:
在这两种情况下,您都不能更改 y
的值:
在第一种情况下,for
在每个循环步骤中重新引入 y
,这就是为什么您无法更改它的值,即使它是可变的。
第二种情况表明该值是真正不可变的,每一步都为您提供(inc y)
,但y
始终为零。简单例子:
(let [x 10]
(inc x)
x)
;;=> 10
一般来说,这类任务通常可以通过以下方法解决:
第一个是简单的递归:
(defn count-rec [data]
(if (seq data)
(inc (count-rec (rest data)))
0))
user> (count-rec [1 2 3 4 5])
;;=> 5
它在某种程度上是有缺陷的,因为它不是尾递归的,并且对于大型集合会失败
第二个是clojure的loop
:
(defn count-loop [data]
(loop [res 0 data data]
(if (seq data)
(recur (inc res) (rest data))
res)))
user> (count-loop [1 2 3 4 5])
;;=> 5
您也可以使用非常相似的显式尾递归:
(defn count-tailrec
([data] (count-tailrec 0 data))
([c data] (if (seq data)
(recur (inc c) (rest data))
c)))
user> (count-tailrec [1 2 3 4 5])
;;=> 5
第三个将使用reduce
函数:
(defn count-reduce [data]
(reduce (fn [res _] (inc res)) 0 data))
user> (count-reduce [1 2 3 4 5])
;;=> 5
只是为了好玩,您也可以使用这种方式(我不建议这样做,因为与 reduce
相比,这有点过分了):
(defn count-map [data]
(apply + (map (constantly 1) data)))
user> (count-map [1 2 3 4 5])
;;=> 5
您也可以使用 clojure 的可变原语,但它不是惯用的,应尽可能避免使用:
(defn count-atom [data]
(let [c (atom 0)]
(run! (fn [_] (swap! c inc)) data)
@c))
user> (count-atom [1 2 3 4 5])
;;=> 5
作弊来了[剧透警告!]
4clojure 在这个任务中阻塞了
count
函数的使用,但是没有阻塞java集合的.size
方法,所以可以用#(.size (seq %))
【讨论】:
惊人的答案!我发现reduce
和loop/recur
的使用最容易在心理上应用到这个特定的问题,但其他示例确实有助于更好地理解如何以不同的方式看待问题。
@Thumbnail :你是什么意思?答案中有这个确切的变体(查看count-reduce
)
@leetwinski 错过了。以上是关于用于函数重置的 clojure的主要内容,如果未能解决你的问题,请参考以下文章