zenna
zenna

Reputation: 9206

Not repeating myself in recursion

I have a recursion using loop recur with some fairly complex logic. It turns out I should do identical things in two of the branches, both which should lead to a recursion. I don't believe I can abstract this away using a function because of the limitations of recur, so how can I do it and not have duplicate code. Must I use a macro?

The code is below, and repeated code is highlighted with REPEATED CODE HERE

(defn overlapping-sampler [n-samples]
  (let [...]
    (loop [samples [] cache (zipmap boxes []) n-samples n-samples]
      (cond
        (zero? n-samples)
        samples

        :else
        (let [box (categorical boxes volumes)
              cache-item (peek (cache box))]
          (if (nil? cache-item)
              ; REPEATED CODE HERE
              (let [sample (interval-sample (:internals box))
                    all-boxes (map #(apply (:formula %) sample) boxes)
                    pos-dominant (max-pred-index true? all-boxes)
                    pos-box (max-pred-index #(= box %) boxes)]
                    (if (= pos-dominant pos-box)
                        (recur (conj samples sample) cache (dec n-samples))
                        (recur samples
                               (update-in cache [(nth boxes pos-dominant)]
                                          #(conj % {:from box :sample sample}))
                               n-samples)))

              ; Otherwise with prob p=ratio of overlapping region/box, take sample
              (if (flip (/ (volume (overlap box (:from cache-item))) (volume box)))
                  (recur (conj samples (:sample cache-item)) ; I should take the sample
                         (update-in cache [box]
                                    #(pop %))
                         (dec n-samples))

                  (let [sample (gen-until #(interval-sample (:internals box))
                                    #(and (apply (:formula box) %)
                                          (not (apply (:formula (:from cache-item)) %))))
                        ;REPEATED CODE HERE
                        all-boxes (map #(apply (:formula %) sample) boxes)
                        pos-dominant (max-pred-index true? all-boxes)
                        pos-box (max-pred-index #(= box %) boxes)]
                        (if (= pos-dominant pos-box)
                            (recur (conj samples sample) cache (dec n-samples))
                            (recur samples
                                   (update-in cache [(nth boxes pos-dominant)]
                                              #(conj % {:from box :sample sample}))
                                   n-samples))))))))))

Upvotes: 0

Views: 114

Answers (2)

Michał Marczyk
Michał Marczyk

Reputation: 84379

Perhaps rewrite to

(let [cache-item ...]
  (if (and (nil? cache-item) (flip ...))
    (recur ...)
    (let [sample (if (nil? cache-item)
                   (... calculate sample ... )
                   (... use cache-item ... ))
          ; REPEATED CODE HERE
          ...]
       ...)))

This tests (nil? cache-item) twice in the "else" branch of the if. You could introduce a cache-hit? local to store the value of (nil? cache-item) to avoid repeatedly typing in the nil? call; not for performance reasons, however, as there should be virtually no difference.

Upvotes: 0

Ankur
Ankur

Reputation: 33657

Using a local function:

(defn overlapping-sampler [n-samples]
  (let [fun (fn [sample samples]
              (let  [all-boxes (map #(apply (:formula %) sample) boxes)
                     pos-dominant (max-pred-index true? all-boxes)
                     pos-box (max-pred-index #(= box %) boxes)]
                (if (= pos-dominant pos-box)
                  [(conj samples sample) cache (dec n-samples)]
                  [samples
                   (update-in cache [(nth boxes pos-dominant)]
                              #(conj % {:from box :sample sample}))
                 n-samples])))]
    (loop [[samples cache n-samples] [[] (zipmap boxes []) n-samples]]
      (cond
        (zero? n-samples)
        samples

        :else
        (let [box (categorical boxes volumes)
              cache-item (peek (cache box))]
          (if (nil? cache-item)
              ; REPEATED CODE HERE
            (let [sample (interval-sample (:internals box))]
              (recur (fun sample) samples))
              ; Otherwise with prob p=ratio of overlapping region/box, take sample
              (if (flip (/ (volume (overlap box (:from cache-item))) (volume box)))
                  (recur [(conj samples (:sample cache-item)) ; I should take the sample
                         (update-in cache [box]
                                    #(pop %))
                         (dec n-samples)])

                  (let [sample (gen-until #(interval-sample (:internals box))
                                    #(and (apply (:formula box) %)
                                          (not (apply (:formula (:from cache-item)) %))))]
                    (recur (fun sample) samples)))))))))

Upvotes: 1

Related Questions