프로그래밍/Clojure

[Brave Clojure #4] reduce로 map, filter, some 구현하기

김재택 2023. 12. 19. 19:00

map의 설명을 마치 고 저자는 이렇게 말합니다. "If you want an exercise that will really blow your hair back, try implementing map using reduce, and then do the same for filter and some after you read about them later in this chapter."

 

안해 볼 수 없겠죠

(defn map-by-reduce
  [f xs]
  (seq (reduce #(conj %1 (f %2))
               []
               xs)))

(map-by-reduce inc [1 2 3 4])
;; => (2 3 4 5)

(def food-journal
  [{:month 1 :day 1 :human 5.3 :critter 2.3}
   {:month 1 :day 2 :human 5.1 :critter 2.0}
   {:month 2 :day 1 :human 4.9 :critter 2.1}
   {:month 2 :day 2 :human 5.0 :critter 2.5}
   {:month 3 :day 1 :human 4.2 :critter 3.3}
   {:month 3 :day 2 :human 4.0 :critter 3.8}
   {:month 4 :day 1 :human 3.7 :critter 3.9}
   {:month 4 :day 2 :human 3.7 :critter 3.6}])

(defn filter-by-reduce
  [f xs]
  (seq (reduce #(if (f %2)
                  (conj %1 %2)
                  %1)
               []
               xs)))

(filter-by-reduce #(< (:human %) 5) food-journal)
;; => ({:month 2, :day 1, :human 4.9, :critter 2.1}
;;     {:month 3, :day 1, :human 4.2, :critter 3.3}
;;     {:month 3, :day 2, :human 4.0, :critter 3.8}
;;     {:month 4, :day 1, :human 3.7, :critter 3.9}
;;     {:month 4, :day 2, :human 3.7, :critter 3.6})

(defn some-by-reduce
  [f xs]
  (reduce #(if (and (not %1)
                    (f %2))
             (f %2)
             %1)
          nil
          xs))

(some-by-reduce #(> (:critter %) 5) food-journal)
;; => nil

(some-by-reduce #(> (:critter %) 3) food-journal)
;; => true

(some-by-reduce #(and (> (:critter %) 3) %) food-journal)
;; => {:month 3, :day 1, :human 4.2, :critter 3.3}

 

그런데 some의 경우 이렇게 구현하면 take-while대신 filter를 sorted sequence에 사용하면 손해보는 것과 같은 손해가 발생합니다. 그나마 연산의 효율성을 챙기고자 조건이 만족되고나면 predicate 함수를 실행시키지 않도록 and 연산의 피연산자 순서를 신경써서 배치하였으나, 여전히 (not %1)이 남은 seq의 길이만큼은 계산되어야 하는 것이죠. loop/recur를 통해 조건이 만족되면 즉시 연산을 멈추도록 구현하는 편이 훨씬 효율적일 것 같네요.