Async
Async

Reputation: 13

Clojure filter composition with reduce

I have a higher order predicate

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

which returns a predicate that checks if the given argument n is not a factor of x. Now I want to filter a list of numbers and find which are not factors of say '(2 3). One way to do this would be :

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

But one can only type so much. In order to do this dynamically I tried function composition :

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

And it works. So I tried reducing the filters, like this:

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

So, why the filter composition is not working as intended ?

Upvotes: 1

Views: 1288

Answers (2)

Magos
Magos

Reputation: 3014

To see your problem with (reduce compose-filters ... let's look a bit at what that actually does. First, it uses filter on the first two predicates and composes them.. The result of that is a new function from sequences to sequences. The next iteration then calls filter on that function, when filter expects a predicate. Every sequence is a truthy value, so that new filter will now never remove any values because it's using a "predicate" which always returns truthy values. So in the end, only the very last filter actually does any filtering - in my REPL your code removes the numbers 22, 33, 44 and so on because 11 is a factor in them. I think the reduce you want to do here is more like

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

Note how because we only want to call (partial filter) once per number, you can move that into the mapping step of the mapreduce. As to how I'd do this, considering that you produce all your predicates together:

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

it seems more natural to me to just combine the predicates at that point using every-pred

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

and use one filter on that predicate. It seems to communicate the intent a little more clearly ("I want values satisfying every one of these preds") and unlike composition of (partial filter ...) it avoids making an intermediate sequence for each predicate.
(In Clojure 1.7+ you can also avoid this by composing the transducer version of filter).

Upvotes: 0

myguidingstar
myguidingstar

Reputation: 673

There are many ways to compose functions and/or improve your code. Here's one:

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

Upvotes: 2

Related Questions