在Clojure中保存条件表达式中的重复计算?

时间:2017-07-07 15:24:22

标签: clojure

在下面的代码中,我需要计算键盘中一个元素和驱动器中一个元素之和的最大值,但总和应小于或等于s

  (def s 10)
  (def keyboards '(3 1))
  (def drives '(5 2 8))

  (let [k (sort (fn [x y] (> x y)) keyboards) ; sort into decreasing
        d (sort (fn [x y] (> x y)) drives)    ; sort into decreasing
        ]
    (loop [k1 (first k) ks (rest k) d1 (first d) ds (rest d)]
      (cond
        (or (nil? k1) (nil? d1)) -1     ; when one of the list is empty
        (< (+ k1 d1) s) (+ k1 d1)       ; whether (+ k1 d1) can be saved to compute once?
        (and (empty? ks) (empty? ds)) -1
        (empty? ks) (if (< (+ k1 (first ds)) s) (+ k1 (first ds)) -1) ; whether (+ k1 (first ds)) can be saved once?
        (empty? ds) (if (< (+ d1 (first ks)) s) (+ d1 (first ks)) -1) ; whether (+ d1 (first ks)) can be saved once?
        :else (let [bs (take-while #(< % s) [ (+ k1 (first ds)) (+ (first ks) d1) ])]
                (if (empty? bs) (recur (first ks) (rest ks) (first ds) (rest ds))
                    (apply max bs))))))

如评论中所示,我想知道是否有任何方法可以进一步优化条件表达式中的重复添加操作。 在条件检查之前使用let绑定来计算它们可能不是最佳的,因为只有一个条件是真的,因此其他条件的计算将被浪费。

我想知道Clojure编译器是否足够聪明以便为我优化重复计算,或者有一个聪明的表达式使操作在检查和返回值中只执行一次?

任何使代码更具惯用性的建议都将受到赞赏。

2 个答案:

答案 0 :(得分:3)

这听起来有点像knapsack problem。有更多计算效率的方法来计算它,但是如果你正在处理两个或三个小于几百的小列表,并且如果它不是在热循环中运行的关键代码段,那么考虑一下更简单的:

(let [upper-limit 10
      keyboards [3 1]
      drives [5 2 8]]

  (apply max
         (for [k keyboards
               d drives
               :let [sum (+ k d)]
               :when (<= sum upper-limit)]
           sum)))

您只执行一次(可能很昂贵的)计算(在:let绑定中),这是您真正要求的。这是O(n^2),但如果它符合上述标准,那么这是一个易于读者理解的解决方案;因此,它是可维护的。如果它尽可能高效至关重要,请考虑更具算法效率的解决方案。

Yu Shen编辑:

没有合格的金额时会出现轻微问题。它可以改进如下:

(let [upper-limit 10
      keyboards [3 1]
      drives [5 2 8]
      eligbles (for [k keyboards
                     d drives
                     :let [sum (+ k d)]
                     :when (<= sum upper-limit)]
                 sum)]
  (if (empty? eligbles)
    nil
    (apply max eligbles)))

答案 1 :(得分:2)

如果您想保留当前代码的结构,可以使用Mark Engelberg的better-cond库:

(require '[better-cond.core :as b])

(def s 10)
(def keyboards '(3 1))
(def drives '(5 2 8))

(let [k (sort (fn [x y] (> x y)) keyboards) ; sort into decreasing
      d (sort (fn [x y] (> x y)) drives)]   ; sort into decreasing
  (loop [k1 (first k) ks (rest k) d1 (first d) ds (rest d)]
    (b/cond
      (or (nil? k1) (nil? d1)) -1           ; when one of the list is empty
      :let [x (+ k1 d1)]
      (< x s) x
      (and (empty? ks) (empty? ds)) -1
      :let [y (+ k1 (first ds))]
      (empty? ks) (if (< y s) (dec y))
      :let [z (+ d1 (first ks))]
      (empty? ds) (if (< z s) (dec z))
      :else (let [bs (take-while #(< % s) [(+ k1 (first ds)) (+ (first ks) d1)])]
              (if (empty? bs) (recur (first ks) (rest ks) (first ds) (rest ds))
                (apply max bs))))))