• Clojure——学习迷宫生成


    背景

    初学clojure,想着看一些算法来熟悉clojure语法及相关算法实现。
    找到一个各种语言生成迷宫的网站:http://rosettacode.org/wiki/Maze_generation
    在上述网站可以看到clojure的实现版,本文就是以初学者的视角解读改程序。

    小试牛刀

    先看一些简单的示例,以帮助我们理解迷宫生成程序。

    绑定符号x++

    (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语言中的自增运算符。

    集合过滤

    (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解释见下文

    vec交叉合并

    (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

    transduce

    transducer是clojure里面的一种编程思想,使用transducer可以简化很多语法。
    可以参考这篇文章链接,帮助理解
    文档:http://clojuredocs.org/clojure.core/transduce

    思路

    笔者阅读了迷宫生成算法,将思路整理如下

    坐标点与符号映射关系

    比如迷宫的左上角是如何生成的,不同大小的迷宫如何确定?
    经过阅读源码发现,一个坐标点的符号与其周围4个临接点相关,如果按照坐标点表示,5个点排序顺序是一致的。

    比如,上述坐标点(5,5),和其4个临界点。可以看到在该坐标系内,一个点与其临界点做成的集合排序一定是下面的顺序:

    比如迷宫左上角坐标是(0, 0),该点五元组应该是

    不在迷宫,不在迷宫,(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 
    ewline)))
    
    ;; 生成迷宫
    (println (maze->str (create-random-maze 10 10)))
    

    上述程序输出:

  • 相关阅读:
    iconv 文件编码相互转换
    MySQL 字符编码
    MySQL there can be only one TIMESTAMP column with CURRENT_TIMESTAMP in DEFAULT or ON UPDATE clause同时创建多个更新当前时间戳字段 解决方法
    PHP 输出日志到文件 DEMO
    Nginx http -> https 跳转后 POST 丢失
    SSH SCP 远程密钥登录配置和服务器间的文件传输
    Mac 安装 7zip
    git pull There is no tracking information for the current branch.
    MacOS 安装配置 Laravel
    Mac OS 安装 MySQL5.7
  • 原文地址:https://www.cnblogs.com/rainmote/p/7629958.html
Copyright © 2020-2023  润新知