迷路を最短経路で解く問題を Clojure で
Clojure といえば、巷ではProgramming Clojureの日本語訳である「プログラミング Clojure」の献本が行われているようで、これが発売すればいよいよ日本でも本格的に Clojure が広まっていくんじゃないでしょうか。
というわけで、スタートダッシュ(というかフライング?)を決めるために今のうちに Clojure について書いておこうと思います。お題については、もうちょっと流行には乗り遅れた感があるけど、迷路を最短経路で解く例の問題です。
解き方は至ってシンプルな幅優先探索です。せっかくなのでマルチスレッド化しようかと思ったんですが、それなりの時間で解けているようなので今回は放置。
実行例
$ cat maze.txt ************************** *S* * * * * * * ************* * * * * ************ * * * * ************** *********** * * ** *********************** * * G * * * *********** * * * * ******* * * * * * ************************** $ time clj maze.clj maze.txt ************************** *S* * $$$$ * *$* *$$* $************* * *$* $$* $$************ * *$$$$* $$$$$ * **************$*********** * $$$$$$$$$$$$$ * **$*********************** * $$$$$* $$$$$$$$$$$$$G * * * $$$$*********** * * * * ******* * * * * * ************************** clj maze.clj maze.txt 1.65s user 0.10s system 86% cpu 2.035 total $
ソースコード
(use '[clojure.contrib.duck-streams :only (read-lines)]) (defn value-at [maze y x] ((maze y) x)) (defn nrows [maze] (count maze)) (defn ncols [maze] (count (maze 0))) (defn neighbors [maze [y x]] (for [[dy dx] [[0 1] [1 0] [0 -1] [-1 0]] :let [y (+ y dy), x (+ x dx)] :when (not= \* (value-at maze y x))] [y x])) (defn solve ([maze candidate] (solve maze [[candidate]] #{})) ([maze candidates visited] (let [[[y x :as pos] :as paths] (candidates 0)] (cond (nil? pos) nil (= (value-at maze y x) \G) (reverse paths) :else (let [cands (map #(cons % paths) (remove visited (neighbors maze pos)))] (recur maze (into (subvec candidates 1) cands) (conj visited pos))))))) (defn search-start [maze] (first (for [y (range (nrows maze)), x (range (ncols maze)) :when (= \S (value-at maze y x))] [y x]))) (defn print-solution [maze paths] (let [ncols (ncols maze), path? (apply hash-set paths)] (doseq [y (range (nrows maze)), x (range ncols) :let [v (value-at maze y x)]] (if (and (path? [y x]) (not (#{\S \G} v))) (print \$) (print v)) (when (= x (dec ncols)) (newline))))) (defn main [filename] (let [maze (vec (map vec (read-lines filename)))] (print-solution maze (solve maze (search-start maze))) (flush))) (main (first *command-line-args*))