初學clojure,想着看一些算法來熟悉clojure語法及相關算法實現。
找到一個各類語言生成迷宮的網站:http://rosettacode.org/wiki/Maze_generation
在上述網站能夠看到clojure的實現版,本文就是以初學者的視角解讀改程序。javascript
先看一些簡單的示例,以幫助咱們理解迷宮生成程序。html
(defn f [x] (let [x++ (+ x 5)] #{[x x++]})) (println (f 1)) => #'sinlov.clojure.base-learn/f #{[1 6]} => nil
Tips: 上述程序將x++
綁定爲x+5
,不一樣於c語言中的自增運算符。java
(select odd? (set [1 2 3 4 5])) => #{1 3 5} (select (partial odd?) (set [1 2 3 4 5])) => #{1 3 5}
select
語法參考文檔:http://clojuredocs.org/clojure.set/select
partial
解釋見下文算法
(interleave [0 1 2] ['a 'b 'c]) => (0 a 1 b 2 c) (interleave [0 1 2] ['a 'b 'c] ['b]) => (0 a b) (interleave [0 1 2] ['a 'b 'c] (repeat 'z)) => (0 a z 1 b z 2 c z)
文檔:http://clojuredocs.org/clojure.core/interleave編程
transducer是clojure裏面的一種編程思想,使用transducer能夠簡化不少語法。
能夠參考這篇文章連接,幫助理解
文檔:http://clojuredocs.org/clojure.core/transducedom
筆者閱讀了迷宮生成算法,將思路整理以下oop
好比迷宮的左上角┌
是如何生成的,不一樣大小的迷宮如何肯定?
通過閱讀源碼發現,一個座標點的符號與其周圍4個臨接點相關,若是按照座標點表示,5個點排序順序是一致的。
網站
好比,上述座標點(5,5),和其4個臨界點。能夠看到在該座標系內,一個點與其臨界點作成的集合排序必定是下面的順序:
ui
好比迷宮左上角座標是(0, 0),該點五元組應該是3d
不在迷宮,不在迷宮,(0, 0), (0, 1), (1, 0)
假設不在迷宮或者該位置爲空,記爲0
;若是是牆記爲1
那麼上述五元組能夠換算爲11100
。
再好比迷宮右上角,五元組爲
(n-1, 0), 不在迷宮, (n, 0), (n, 1), 不在迷宮
可換算爲10110
。
按照如上規則能夠生成以下表:
[" " " " " " " " "· " "╵ " "╴ " "┘ " " " " " " " " " "╶─" "└─" "──" "┴─" " " " " " " " " "╷ " "│ " "┐ " "┤ " " " " " " " " " "┌─" "├─" "┬─" "┼─"]
(ns maze.core (:require [clojure.set :refer [intersection select]] [clojure.string :as str])) ;; 獲得周圍臨界點 (defn neighborhood ([] (neighborhood [0 0])) ([coord] (neighborhood coord 1)) ([[y x] r] (let [y-- (- y r) y++ (+ y r) x-- (- x r) x++ (+ x r)] #{[y++ x] [y-- x] [y x--] [y x++]}))) ;; 判斷位置是否爲空 (defn cell-empty? [maze coords] (= :empty (get-in maze coords))) ;; 判斷位置是否爲牆 (defn wall? [maze coords] (= :wall (get-in maze coords))) ;; 過濾迷宮中指定類型的點的集合 (defn filter-maze ([pred maze coords] (select (partial pred maze) (set coords))) ([pred maze] (filter-maze pred maze (for [y (range (count maze)) x (range (count (nth maze y)))] [y x])))) ;; 建立新迷宮 (defn create-empty-maze [width height] (let [width (inc (* 2 width)) height (inc (* 2 height))] (vec (take height (interleave (repeat (vec (take width (repeat :wall)))) (repeat (vec (take width (cycle [:wall :empty]))))))))) (defn next-step [possible-steps] (rand-nth (vec possible-steps))) ;; 核心算法,深度優先遞歸 (defn create-random-maze [width height] (loop [maze (create-empty-maze width height) stack [] nonvisited (filter-maze cell-empty? maze) visited #{} coords (next-step nonvisited)] (if (empty? nonvisited) maze (let [nonvisited-neighbors (intersection (neighborhood coords 2) nonvisited)] (cond (seq nonvisited-neighbors) (let [next-coords (next-step nonvisited-neighbors) wall-coords (map #(+ %1 (/ (- %2 %1) 2)) coords next-coords)] (recur (assoc-in maze wall-coords :empty) (conj stack coords) (disj nonvisited next-coords) (conj visited next-coords) next-coords)) (seq stack) (recur maze (pop stack) nonvisited visited (last stack))))))) ;; 迷宮座標與字符映射 (def cell-code->str [" " " " " " " " "· " "╵ " "╴ " "┘ " " " " " " " " " "╶─" "└─" "──" "┴─" " " " " " " " " "╷ " "│ " "┐ " "┤ " " " " " " " " " "┌─" "├─" "┬─" "┼─"]) ;; 獲取迷宮座標的類型 ;; 使用5 bit表示一個點對應的字符映射 ;; 例如:00111對應┘ (defn cell-code [maze coord] (transduce (comp (map (partial wall? maze)) (keep-indexed (fn [idx el] (when el idx))) (map (partial bit-shift-left 1))) (completing bit-or) 0 (sort (cons coord (neighborhood coord))))) (defn cell->str [maze coord] (get cell-code->str (cell-code maze coord))) ;; 將迷宮座標轉換爲字符 (defn maze->str [maze] (->> (for [y (range (count maze))] (for [x (range (count (nth maze y)))] (cell->str maze [y x]))) (map str/join) (str/join \newline))) ;; 生成迷宮 (println (maze->str (create-random-maze 10 10)))
上述程序輸出: