2014-01-20 29 views
2

我一直在努力通過"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))))))) 
+1

所謂的麪包和函數式編程的黃油'地圖,降低/ fold'約消耗整個序列的價值觀,他們沒有辦法「擺脫循環」(如果他們有這樣的選擇,他們會採取一個額外的參數,這將是一個決定什麼時候爆發的預測)。 – Ankur

+5

在Clojure中,reduce有一種方法可以打破「簡化」的功能。 – noisesmith

回答

7

reduced function有效地短路減少。

而且我換成與應用+的減少(這是雖然略少效率更清晰)

(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))) 

最後,如果是返回零,而不是累積的大小,如果從來沒有出現命中。

+1

適用+真的比減少+效率更高嗎? –

+0

無論效率如何,'apply +'都有點直接和慣用。 – JohnJ

+1

@lgrapenthin當然,我錯了,我只是基於短期和長期的輸入尺寸進行基準測試,在兩種情況下都會稍微快一點。我以前可能誤解了它的基準,或者誤解了結果。 https://www.refheap.com/25806。我將編輯答案來反映這一點。 – noisesmith

1

我在Clojure的早期教訓之一是,當循環是正確的解決方案時使用循環並沒有錯。原始代碼不錯,不一定需要任何改進。

如果你有很多的權重(大小),二進制搜索正確的時間間隔會比簡單的線性搜索更好。用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}