Clojure 如何用更惯用的东西替换这个循环?
我一直在努力,我只是花了一个小时盯着这个循环,试图把它变成一个reduce或其他“更漂亮”的循环。我被绊倒了,因为我在循环中试图达到那个目标号码,并且很早就回来了。这里的想法是随机生成一个数字(基于大小)并返回该数字处的身体部位。从概念上讲,我正在考虑将列表一分为二并返回“断点”。我还可以想象在它上面映射,然后添加一个“大小索引”进行过滤。我觉得我错过了一些简单的东西(我一直在尝试减少) 该电路有效地短路了一个减速器 此外,我还将reduce on+替换为apply(虽然效率稍低,但更清晰)Clojure 如何用更惯用的东西替换这个循环?,clojure,functional-programming,idioms,Clojure,Functional Programming,Idioms,我一直在努力,我只是花了一个小时盯着这个循环,试图把它变成一个reduce或其他“更漂亮”的循环。我被绊倒了,因为我在循环中试图达到那个目标号码,并且很早就回来了。这里的想法是随机生成一个数字(基于大小)并返回该数字处的身体部位。从概念上讲,我正在考虑将列表一分为二并返回“断点”。我还可以想象在它上面映射,然后添加一个“大小索引”进行过滤。我觉得我错过了一些简单的东西(我一直在尝试减少) 该电路有效地短路了一个减速器 此外,我还将reduce on+替换为apply(虽然效率稍低,但更清晰) 最
最后一个if是返回nil,而不是如果命中从未发生,则返回累计大小。我认为做一个约化来获得累计总和 然后找到第一个匹配密码的
将使您能够摆脱显式循环。我在Clojure的早期课程之一是,当循环是正确的解决方案时,使用循环是正确的。原始代码并不坏,不一定需要任何改进 如果您有很多权重(大小),那么二进制搜索正确的间隔将比简单的线性搜索更好。进行二进制搜索最简单的方法可能是让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}
所谓的函数式编程的面包和黄油
map,reduce/fold
是关于消耗整个值序列,它们没有任何方法“打破循环”(如果它们有这样的选择,它们将使用一个额外的参数,该参数将是决定何时打破循环的谓词)。在Clojure中,reduce确实有一种方法来打破循环,reduced
function.clojure-contrib已经过时。apply+真的比reduce+更有效吗?不管效率如何,apply+
都有点简单易懂。@lgrapenthin好吧,我错了,我只是用短输入大小和长输入大小进行了基准测试,在这两种情况下reduce都稍微快一点。我可能记错了以前的基准测试,或者记错了结果。我将编辑答案以反映这一点。谢谢你的基准!顺便说一句,我们最近遇到了一个类似的问题,事实证明应用速度更快,所以也许你对早期基准没有错。让我找到它…不幸的是,在评论中讨论的答案被作者删除了。
(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}