how would a loop with a nested return be implemented in clojure?

一曲冷凌霜 提交于 2019-12-12 14:06:47

问题


I'm playing around with a crafty tutorial here:

http://buildnewgames.com/introduction-to-crafty/

and am wondering how this particular function be implemented in clojurescript/clojure

 var max_villages = 5;
 for (var x = 0; x < Game.map_grid.width; x++) {
   for (var y = 0; y < Game.map_grid.height; y++) {
     if (Math.random() < 0.02) {
       Crafty.e('Village').at(x, y);

       if (Crafty('Village').length >= max_villages) {
        return;
       }
    }
  }
}

I know that we can have the (for []) construct but how would you get it to stop when max_villages hits 5?


回答1:


Here's one approach:

(def max-villages 5)

(->> (for [x (range map-width)
           y (range map-height)]
       [x y])
     (filter (fn [_] (< (rand) 0.02)))
     (take max-villages))

Then perhaps add (map make-village-at) or something similar as the next stage of the pipeline; if it's meant to perform side effects, add a dorun or doall as the final stage to force them to happen at once (choosing one or the other depending on whether the return values are interesting).

NB. some extra vectors and random numbers may be generated due to seq chunking, it'll be less than 32 though.

A more imperative approach with a counter for comparison:

(let [counter (atom 0)]
  (doseq [x (range map-width)
          :while (< @counter max-villages)
          y (range map-height)
          :while (< @counter max-villages)
          :when (< (rand) 0.02)]
    (swap! counter inc)
    (prn [x y]))) ; call make-village-at here

:while terminates the loop at the current nesting level when its test expression fails; :when moves on to the next iteration immediately. doseq supports chunking too, but :while will prevent it from performing unnecessary work.




回答2:


Using recursion it would be something like:

(letfn [(op [x y]
          (if (= (rand) 0.02)
            (do
              (village-at x y)
              (if (>= (village-length) max-villages) true))))]
  (loop [x 0 y 0]
    (when (and (< x width) (not (op x y)))
      (if (= (inc y) height)
        (recur (inc x) 0)
        (recur x (inc y))))))



回答3:


That's a great tutorial!

A variation on Michael's approach (I would have just commented to his answer but I don't have enough stack power yet) would be to use Cartesian products rather than nested for loops:

;; some stub stuff to get the example to run
(ns example.core
  (:use clojure.math.combinatorics))

(def max-villages 5)
(def map-width 10)
(def map-height 10)
(defn crafty-e [x y z] (print z))

;; the example, note I use doseq rather than map to empasize the fact that the loop 
;; is being performed for its side effects not its return value.
(doseq [coord (take max-villages 
                (filter 
                 (fn [_] (< (rand)  0.02))
                 (cartesian-product (range map-width) (range map-height))))]
(crafty-e :village :at coord))


来源:https://stackoverflow.com/questions/16599191/how-would-a-loop-with-a-nested-return-be-implemented-in-clojure

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!