我需要编写一个Clojure函数,它将未经评估的任意列表深度嵌套作为输入,然后确定列表中的任何项目(不在函数位置)是否为非数字。这是我第一次在Clojure中写任何东西,所以我有点困惑。这是我第一次尝试制作这个功能:
(defn list-eval
[x]
(for [lst x]
(for [item lst]
(if(integer? item)
(println "")
(println "This list contains a non-numeric value")))))
我尝试使用嵌套的for循环迭代每个嵌套列表中的每个项目。试图像这样测试函数:
=> (list-eval (1(2 3("a" 5(3)))))
导致此异常:
ClassCastException java.lang.Long cannot be cast to clojure.lang.IFn listeval.core/eval7976 (form-init4504441070457356195.clj:1)
问题出现在代码中,或者我如何调用函数并传递参数?在任何一种情况下,我如何使这项工作按预期进行?
答案 0 :(得分:0)
这是因为(1 ..)
被视为调用函数,1
是Long
,而不是函数。首先,您应该将嵌套列表更改为'(1(2 3("a" 5(3))))
。接下来,您可以将函数更改为递归运行:
(defn list-eval
[x]
(if (list? x)
(for [lst x] (list-eval lst))
(if (integer? x)
(println "")
(println "This list contains a non-numeric value"))))
=> (list-eval '(1(2 3("a" 5(3)))))
答案 1 :(得分:0)
有一个名为tree-seq
的酷函数可以在遍历结构时为您完成所有艰苦的工作。使用它然后删除任何集合,删除所有数字,并检查是否还有任何东西。
(defn any-non-numbers?
[x]
(->> x
(tree-seq coll? #(if (map? %) (vals %) %))
(remove (some-fn coll? number?))
not-empty
boolean))
示例:
user=> (any-non-numbers? 1)
false
user=> (any-non-numbers? [1 2])
false
user=> (any-non-numbers? [1 2 "sd"])
true
user=> (any-non-numbers? [1 2 "sd" {:x 1}])
true
user=> (any-non-numbers? [1 2 {:x 1}])
false
user=> (any-non-numbers? [1 2 {:x 1 :y "hello"}])
true
如果您还想考虑地图密钥,只需将(vals %)
更改为(interleave (keys %) (vals %))
。
答案 2 :(得分:0)
正如其他人所提到的,您需要引用一个列表以防止其被评估为 码。这就是你所看到的例外原因。
for
只会下降到你告诉它的嵌套深度。它不是for循环,
正如你所料,但是一个序列理解,就像python列表理解一样。
(for [x xs, y ys] y)
将假设xs是列表列表并将其展平。
(for [x xs, y ys, z zs] z)
是相同的但具有额外的嵌套级别。
要向下走到任何深度,您通常会使用递归。 (有很多方法可以迭代地执行此操作,但它们更难以解决问题。)
你在懒惰的序列中做副作用(打印)。这将在repl工作,
但是如果你不在任何地方使用结果,它就不会运行并引起很大的混乱。
每一个新的clojurian都会碰到某些东西。
(doseq
与for
类似,但副作用。)
clojure方法是将函数与函数中的值分开 "做东西",比如打印到发射导弹的控制台,并保持 副作用尽可能简单。
让我们做一个明确的问题陈述:在一个地方的任何地方都有一个非数字 任意嵌套列表?如果有,请在控制台上打印一条消息说明。
在很多情况下,当你在其他语言中使用for循环时reduce
就是你想要的语法。
(defn collect-nested-non-numbers
;; If called with one argument, call itself with empty accumulator
;; and that argument.
([form] (collect-nested-non-numbers [] form))
([acc x]
(if (coll? x)
;; If x is a collection, use reduce to call itself on every element.
(reduce collect-nested-non-numbers acc x)
;; Put x into the accumulator if it's a non-number
(if (number? x)
acc
(conj acc x)))))
;; A function that ends in a question mark is (by convention) one that
;; returns a boolean.
(defn only-numbers? [form]
(empty? (collect-nested-non-numbers form)))
;; Our function that does stuff becomes very simple.
;; Which is a good thing, cause it's difficult to test.
(defn warn-on-non-numbers [form]
(when-not (only-numbers? form)
(println "This list contains a non-numeric value")))
那将会奏效。已经存在一些可以帮助你走嵌套结构的东西,所以你不需要手动完成它。
有clojure附带的clojure.walk
命名空间。它适用于你的时间
嵌套的东西,想要转换它的一些部分。我们解释了tree-seq
在另一个答案。 Specter是一个图书馆
一种非常强大的迷你语言,用于表达嵌套结构的转换。
然后我的utils库comfy包含了减少版本的 clojure.walk中的函数,因为当你有一个嵌套的东西并希望减少"它是一个单一的价值。
关于这一点的好处是你可以使用reduced
,就像命令式中断语句一样,但reduce
。如果它找到一个非数字,它不需要继续完成整个事情。
(ns foo.core
(:require
[madstap.comfy :as comfy]))
(defn only-numbers? [form]
(comfy/prewalk-reduce
(fn [ret x]
(if (or (coll? x) (number? x))
ret
(reduced false)))
true
form))
可能是"列表中的任何项目(不在功能位置)"你的意思是?
(defn only-numbers-in-arg-position? [form]
(comfy/prewalk-reduce
(fn [ret x]
(if (and (list? x) (not (every? (some-fn number? list?) (rest x))))
(reduced false)
ret))
true
form))