具有 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
的转换器版本来避免这种情况)。
我有一个高阶谓词
(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
的转换器版本来避免这种情况)。