具有 reduce 的 Clojure 过滤器组合

Clojure filter composition with reduce

我有一个高阶谓词

(defn not-factor-of-x? [x]
  (fn [n]
    (cond 
      (= n x) true
      (zero? (rem n x)) false
      :else true)))

which returns 一个谓词,用于检查给定参数 n 是否不是 x 的因数。 现在我想过滤一个数字列表并找出哪些不是 say '(2 3) 的因素。一种方法是:

(filter (not-factor-of-x? 3) (filter (not-factor-of-x? 2) (range 2 100)))

但是一个人只能输入这么多。为了动态地做到这一点,我尝试了函数组合:

(comp (partial filter (not-factor-of-x? 2)) (partial filter (not-factor-of-x? 3)))

并且有效。所以我尝试减少过滤器,如下所示:

(defn compose-filters [fn1 fn2]
  (comp (partial filter fn1) (partial filter fn2)))
(def composed-filter (reduce compose-filters (map not-factor-of-x? '(2 3 5 7 11))))
(composed-filter (range 2 122))   ; returns (2 3 4 5 6 7 8 9 10 .......)

那么,为什么过滤器组合没有按预期工作?

编写函数的方法有很多种and/or改进您的代码。这是一个:

(defn factor? [n x]
  (and (not= n x) (zero? (rem n x))))

(->> (range 2 100)
     (remove #(factor? % 2))
     (remove #(factor? % 3)))

;; the same as the above
(->> (range 2 100)
     (remove (fn [n] (some #(factor? n %) [2 3]))))

要查看 (reduce compose-filters ... 的问题,让我们看一下它的实际作用。首先,它在前两个谓词上使用 filter 并组合它们。其结果是一个从序列到序列的新函数。当 filter 需要一个谓词时,下一次迭代然后对该函数调用 filter。每个序列都是真值,因此新过滤器现在永远不会删除任何值,因为它使用的 "predicate" 总是 returns 真值。所以最后,只有最后一个过滤器才真正进行任何过滤——在我的 REPL 中,您的代码删除了数字 22、33、44 等等,因为 11 是其中的一个因素。我认为你想在这里做的减少更像是

(reduce comp (map (comp (partial partial filter) not-factor-of-x?) '(2 3 5 7 11)))

请注意,因为我们只想为每个数字调用一次 (partial filter),所以您可以将其移至 mapreduce 的映射步骤。至于我将如何做到这一点,考虑到您同时生成所有谓词:

(map not-factor-of-x? '(2 3 5 7 11))

在我看来,使用 every-pred

组合谓词似乎更自然
(apply every-pred (map not-factor-of-x? '(2 3 5 7 11)))

并在该谓词上使用一个 filter。它似乎更清楚地传达了意图 ("I want values satisfying every one of these preds"),并且与 (partial filter ...) 的组合不同,它避免了为每个谓词制作中间序列。
(在 Clojure 1.7+ 中,您还可以通过编写 filter 的转换器版本来避免这种情况)。