Clojure:如何确定嵌套列表是否包含非数字项?
Clojure: How to determine if a nested list contains non-numeric items?
我需要编写一个 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)
这里的问题是出在代码上,还是出在我如何调用函数和传递参数上?无论哪种情况,我怎样才能按预期进行这项工作?
发生这种情况是因为 (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)))))
有一个名为 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 %))
。
引用
正如其他人提到的,您需要引用一个列表以防止它被评估为
代码。这就是您所看到的异常的原因。
用于嵌套
for
只会下降到您指定的嵌套深度。它不是for循环,
如您所料,但序列理解,如 python 列表理解。
(for [x xs, y ys] y)
将假定 xs 是列表的列表并将其展平。
(for [x xs, y ys, z zs] z)
相同但多了一层嵌套。
要深入到任何深度,您通常会使用递归。
(有很多方法可以迭代地执行此操作,但它们更难让您全神贯注。)
副作用
您正在惰性序列中产生副作用(打印)。这将在 repl 工作,
但如果您不在任何地方使用结果,它就不会 运行 并造成很大的混乱。
这是每个新的 clojurian 都会在某个时候遇到的事情。
(doseq
类似于 for
,但有副作用。)
clojure 方法是将处理值的函数与处理值的函数分开
"do stuff",喜欢打印到发射导弹的控制台,并保持
尽可能简单的副作用函数。
综合起来
让我们把问题陈述清楚:
任意嵌套列表?如果有,则向控制台打印一条消息。
在很多情况下,当您在其他语言中使用 for 循环时,reduce
就是您在 clojure 中想要的。
(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 是一个库,它是
一种非常强大的迷你语言,用于表达嵌套结构的转换。
然后是我的实用程序库 comfy,其中包含
clojure.walk 中的函数,因为当你有一个嵌套的东西并想 "reduce" 它成为一个单一的值时。
这样做的好处是您可以使用 reduced
,它类似于命令式 break 语句,但用于 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))
也许 "any item in the list (not in function position)" 你是这个意思?
(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))
我需要编写一个 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)
这里的问题是出在代码上,还是出在我如何调用函数和传递参数上?无论哪种情况,我怎样才能按预期进行这项工作?
发生这种情况是因为 (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)))))
有一个名为 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 %))
。
引用
正如其他人提到的,您需要引用一个列表以防止它被评估为 代码。这就是您所看到的异常的原因。
用于嵌套
for
只会下降到您指定的嵌套深度。它不是for循环,
如您所料,但序列理解,如 python 列表理解。
(for [x xs, y ys] y)
将假定 xs 是列表的列表并将其展平。
(for [x xs, y ys, z zs] z)
相同但多了一层嵌套。
要深入到任何深度,您通常会使用递归。 (有很多方法可以迭代地执行此操作,但它们更难让您全神贯注。)
副作用
您正在惰性序列中产生副作用(打印)。这将在 repl 工作,
但如果您不在任何地方使用结果,它就不会 运行 并造成很大的混乱。
这是每个新的 clojurian 都会在某个时候遇到的事情。
(doseq
类似于 for
,但有副作用。)
clojure 方法是将处理值的函数与处理值的函数分开 "do stuff",喜欢打印到发射导弹的控制台,并保持 尽可能简单的副作用函数。
综合起来
让我们把问题陈述清楚: 任意嵌套列表?如果有,则向控制台打印一条消息。
在很多情况下,当您在其他语言中使用 for 循环时,reduce
就是您在 clojure 中想要的。
(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 是一个库,它是
一种非常强大的迷你语言,用于表达嵌套结构的转换。
然后是我的实用程序库 comfy,其中包含 clojure.walk 中的函数,因为当你有一个嵌套的东西并想 "reduce" 它成为一个单一的值时。
这样做的好处是您可以使用 reduced
,它类似于命令式 break 语句,但用于 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))
也许 "any item in the list (not in function position)" 你是这个意思?
(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))