我一直在"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)))))))
答案 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}