如何用更惯用的东西替换这个循环?

时间:2014-01-20 03:41:32

标签: clojure functional-programming idiomatic

我一直在"Clojure for the Brave and True"工作,我只花了一个小时盯着这个循环试图把它变成一个减少或其他“更漂亮”的循环。我正试图在循环中尝试击中目标数字并提前返回时被绊倒。这里的想法是随机生成一个数字(基于大小)并返回该数字的正文部分。从概念上讲,我正在考虑将列表分成两部分并返回“断点”。我还可以设想在它上面进行映射并添加“大小索引”然后进行过滤。我觉得我错过了一些简单的东西(我一直在努力减少)。

(defn hit
  "Expects a sequence of maps which have a :name and :size"
  [body-parts]
  (let [body-part-size-sum (reduce + 0 (map :size body-parts))
       target (inc (rand body-part-size-sum))]
    (loop [[part & rest] body-parts
           accumulated-size (:size part)]
      (if (> accumulated-size target)
         part
        (recur rest (+ accumulated-size (:size part)))))))

3 个答案:

答案 0 :(得分:7)

reduced function有效地缩短了减少量。

此外,我用apply替换了reduce on +(虽然效率稍低,但更清晰)

(defn hit
  "Expects a sequence of maps which have a :name and :size"
  [body-parts]
  (let [body-part-size-sum (apply + (map :size body-parts))
       target (inc (rand body-part-size-sum))
       found (reduce (fn [accumulated-size part]
                        (if (> accumulated-size target)
                          (reduced part)
                          (+ accumulated-size (:size part))))
                     0
                     body-parts)]
    (if (map? found) found)))

如果命中永远不会发生,则最终if将返回nil而不是累积大小。

答案 1 :(得分:1)

我认为要减少累积金额 How to make a cumulative sequence?

然后找到匹配该编码的第一个 http://clojuredocs.org/clojure_contrib/clojure.contrib.seq-utils/find-first

可以让你摆脱显式循环。

答案 2 :(得分:1)

我在Clojure的早期课程之一是,当循环是正确的解决方案时使用循环并没有错。原始代码并不坏,不一定需要任何改进。

如果您有很多权重(大小),那么正确间隔的二进制搜索将比简单线性搜索更好。 Clojure-ish进行二进制搜索的方法可能就是让Java做到这一点。

(defn find-interval [intervals x]
  (Math/abs (inc (java.util.Collections/binarySearch intervals x))))

要学习的一个重要的功能习语是闭包,或“放过lambda”。我们可以通过将变量放在返回函数的词法环境中来保存变量。在这种情况下,我们会保存预先计算的权重累计总和w,它们的总计n以及我们想要在向量中选择的值。

(defn weighted-choice-generator [choices]
  (let [weights (java.util.ArrayList. (reductions + (map second choices)))
        values (mapv first choices)
        n (last weights)]
    (fn [] (nth values (find-interval weights (long (rand n)))))))

我们将样本数据强制为上面预期的一系列值 - 权重对,并从加权选择​​生成器中获取我们的命中函数。

(def hit-hobbit-asym (weighted-choice-generator 
                       (map (juxt identity :size) asym-hobbit-body-parts)))

现在测试数千次以确认命中数与大小成正比:

(pprint (frequencies (repeatedly 59000 hit-hobbit-asym)))
{{:name "left-shoulder", :size 3} 2951,
 {:name "chest", :size 10} 9922,
 {:name "left-forearm", :size 3} 3046,
 {:name "left-lower-leg", :size 3} 3038,
 {:name "neck", :size 2} 1966,
 {:name "back", :size 10} 9900,
 {:name "left-ear", :size 1} 997,
 {:name "nose", :size 1} 1023,
 {:name "left-thigh", :size 4} 4020,
 {:name "left-achilles", :size 1} 972,
 {:name "left-hand", :size 2} 2075,
 {:name "left-foot", :size 2} 2062,
 {:name "left-eye", :size 1} 1047,
 {:name "left-knee", :size 2} 2068,
 {:name "left-upper-arm", :size 3} 2996,
 {:name "abdomen", :size 6} 6020,
 {:name "head", :size 3} 2933,
 {:name "left-kidney", :size 1} 986,
 {:name "mouth", :size 1} 978}