Clojure:如何确定嵌套列表是否包含非数字项?

时间:2017-09-28 21:10:16

标签: list clojure nested-lists

我需要编写一个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)

问题出现在代码中,或者我如何调用函数并传递参数?在任何一种情况下,我如何使这项工作按预期进行?

3 个答案:

答案 0 :(得分:0)

这是因为(1 ..)被视为调用函数,1Long,而不是函数。首先,您应该将嵌套列表更改为'(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和nesting

for只会下降到你告诉它的嵌套深度。它不是for循环, 正如你所料,但是一个序列理解,就像python列表理解一样。

(for [x xs, y ys] y)将假设xs是列表列表并将其展平。 (for [x xs, y ys, z zs] z)是相同的但具有额外的嵌套级别。

要向下走到任何深度,您通常会使用递归。 (有很多方法可以迭代地执行此操作,但它们更难以解决问题。)

副作用

你在懒惰的序列中做副作用(打印)。这将在repl工作, 但是如果你不在任何地方使用结果,它就不会运行并引起很大的混乱。 每一个新的clojurian都会碰到某些东西。 (doseqfor类似,但副作用。)

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))