Clojure фильтрующий состав с уменьшенным

У меня есть предикат высшего порядка

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

который возвращает предикат, который проверяет, не является ли данный аргумент n фактором x. Теперь я хочу отфильтровать список чисел и найти, которые не являются факторами, скажем '(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 .......)

Итак, почему состав фильтра не работает, как предполагалось?

2 ответа

Решение

Чтобы увидеть вашу проблему с (reduce compose-filters ... давайте посмотрим немного на то, что это на самом деле делает. Во-первых, он использует filter на первых двух предикатах и ​​составляет их. Результатом этого является новая функция от последовательностей к последовательностям. Следующая итерация затем вызывает filter на эту функцию, когда фильтр ожидает предикат. Каждая последовательность является истинным значением, так что новый фильтр теперь никогда не удалит никакие значения, потому что он использует "предикат", который всегда возвращает истинные значения. Таким образом, в конце концов, только самый последний фильтр выполняет какую-либо фильтрацию - в моем REPL ваш код удаляет числа 22, 33, 44 и т. Д., Потому что 11 является фактором в них. Я думаю, что сокращение, которое вы хотите сделать здесь, больше похоже на

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

Обратите внимание, как, потому что мы хотим только позвонить (partial filter) один раз за число, вы можете переместить это на шаг отображения карты. Что касается того, как я это сделаю, учитывая, что вы создаете все свои предикаты вместе:

(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 на этом предикате. Похоже, что цель более ясна ("Я хочу, чтобы ценности удовлетворяли каждому из этих признаков") и в отличие от состава (partial filter ...) это избегает создания промежуточной последовательности для каждого предиката.
(В Clojure 1.7+ этого также можно избежать, составив версию преобразователя filter).

Существует множество способов составления функций и / или улучшения вашего кода. Вот один из них:

(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]))))
Другие вопросы по тегам