设置位置(x,y,基数方向)并允许更新clojure

时间:2014-02-28 09:09:02

标签: clojure

所以我有一个问题,我试图在clojure中解决。这是我尝试用多种语言进行的编程练习,以及#34;学习"语言..

这是我的第一个不可变功能语言,我遇到了一些挑战。

练习从文件中读取一些"指令"以...的形式 放置X,Y,CARDINAL_DIRECTION(例如,地点0,0,北)

然后可以使用MOVE

向前移动一个空格

玩家可以使用LEFT,RIGHT左右旋转。

最后REPORT将打印出播放器的x,y和基本方向

我目前面临的挑战是我不确定在哪里存放球员的位置。在基于课程的语言中,我有一个持有坐标的玩家类。

以下是我目前的解决方案

(ns toyrobot.core
  (use clojure.java.io))

(defstruct player :x :y :face)
(def ^:dyanmic robot nil)

(defn file->vec
  "Read in a file from the resources directory"
  [input]
  (with-open [rdr (reader input)]
    (into [] (line-seq rdr))))

(defn parse-command [line]
  "Parse command"
  (clojure.string/split line #"\s|,"))

(defn on-the-board? [co-ordinates]
  "Check if the x & y co-ordinates are on the board"
  (let [x (Integer/parseInt (first co-ordinates))
        y (Integer/parseInt (second co-ordinates))]
    (if (and (>= x 0) (>= y 0) (<= x 5) (<= y 5))
      true
      false)))

(defn place [co-ordinates]
  "Place the robot at the co-ordinates"
   (if (on-the-board? co-ordinates)
     co-ordinates
     nil))

(defn -main []
    (doseq [command (file->vec "resources/input.txt")]
      (case (clojure.string/upper-case (first (parse-command command)))
        "PLACE" (place (rest (parse-command command)))
        "MOVE" (println "move")
        "LEFT" (println "left")
        "RIGHT" (println "right")
        "REPORT" (println "report")
        "")))

2 个答案:

答案 0 :(得分:4)

Thumbnail's answer很好,我并不想真正分散注意力,但是你的小练习也让我有机会玩Clojure的lisp-nature,这是我无法抗拒的。

;Agree that unit vectors are more convenient to work with than cardinal directions
(def north [0 1])
(def east [1 0])
(def south [0 -1])
(def west [-1 0])

;Just for a taste of macros
(defmacro name-value-map [& xs] 
  `(zipmap ~(mapv name xs) (vector ~@xs)))

(def direction->heading (name-value-map north east south west))
(def heading->direction (clojure.set/map-invert direction->heading))

;Robot commands just return an updated structure
(defn left [robot]
  (update-in robot [:heading] (fn [[dx dy]] [(- 0 dy) dx])))

(defn right [robot]
  (update-in robot [:heading] (fn [[dx dy]] [dy (- 0 dx)])))

(defn move [robot]
  (update-in robot [:position] (partial mapv + (:heading robot))))

;Report and return unchanged
(defn report [robot] 
  (println "Robot at" (:position robot) 
           "facing" (heading->direction (:heading robot)))
  robot)

;Create
(defn place [x y heading] 
  {:position [x y] :heading heading})

现在有了这些,你已经通过线程宏

在mini-DSL中拥有了你的语言
(-> (place 3, 3, north) report move report left report move report)
;Printed Output:
;Robot at [3 3] facing north
;Robot at [3 4] facing north
;Robot at [3 4] facing west
;Robot at [2 4] facing west
;Return Value:
;{:position [2 4], :heading [-1 0]}

的确,如果你有一个你信任的文件 含有内容

(def sample-file-contents "(place 3, 3, north) move left move")

您可以将数据作为表单

读入
user=> (read-string (str "(" sample-file-contents ")"))
((place 3 3 north) move left move)

交错一些报告(在REPL *1是先前的值)

user=> (interleave *1 (repeat 'report))
((place 3 3 north) report move report left report move report)

搞定线程宏

user=> (cons '-> *1)
(-> (place 3 3 north) report move report left report move report)

eval uate获得与上面相同的输出

user=> (eval *1)
;Printed Output
;Robot at [3 3] facing north
;Robot at [3 4] facing north
;Robot at [3 4] facing west
;Robot at [2 4] facing west
;Return Value:
;{:position [2 4], :heading [-1 0]}

为了更好地设置,使用一个好的解析库不需要太多额外的工作

(require '[instaparse.core :as insta])
(require '[clojure.tools.reader.edn :as edn])

(def parse (insta/parser "
  <commands> = place (ws command)*
  <command> = place | unary-command 
  place = <'place '> location <', '> direction
  unary-command = 'left' | 'right' | 'move' | 'report'
  number = #'[0-9]+'
  location = number <', '> number 
  direction = 'north' | 'east' | 'west' | 'south'
  <ws> = <#'\\s+'> "))

(defn transform [parse-tree]
  (insta/transform 
    {:number edn/read-string 
     :location vector 
     :direction direction->heading 
     :place (fn [[x y] heading] #(place x y heading))
     :unary-command (comp resolve symbol)}
    parse-tree))

(defn run-commands [[init-command & more-commands]] 
  (reduce (fn [robot command] (command robot)) (init-command) more-commands))

现在我们已经放弃了括号的要求(并且包含了一些示例换行符),重新定义了示例文件

(def sample-file-contents "place 3, 3, north report
                           move report 
                           left report
                           move report")

再次在REPL显示中间结果

user=> (parse sample-file-contents)
([:place [:location [:number "3"] [:number "3"]] [:direction "north"]] [:unary-command "report"] 
 [:unary-command "move"] [:unary-command "report"] 
 [:unary-command "left"] [:unary-command "report"] 
 [:unary-command "move"] [:unary-command "report"])

user=> (transform *1)
(#< clojure.lang.AFunction$1@289f6ae> #'user/report 
 #'user/move #'user/report
 #'user/left #'user/report 
 #'user/move #'user/report)

user=> (run-commands *1)
;Printed Output:
;Robot at [3 3] facing north
;Robot at [3 4] facing north
;Robot at [3 4] facing west
;Robot at [2 4] facing west
;Return Value:
;{:position [2 4], :heading [-1 0]}

答案 1 :(得分:3)

您可以很好地控制解析指令文件,因此让我们专注于内部计算。

Clojure结构本质上是不可变的,因此机器人的历史是一系列不同的机器人状态对象,而不是单个机器人对象的状态序列。因此,将机器人命令表示为在给定机器人状态时返回另一个机器人状态的函数是很自然的。

我们需要

  • 指向turn-leftturn-right以及move-forward
  • 的命令
  • 某种方式在给定的地方创建一个机器人状态并指向一个 给定方向。

我们应该如何表示机器人状态?让我们保持简单。

  • 地方只是数字对,x之前是x。所以起源是 [0 0][5 0]沿x轴为5。
  • 方向是移动向量,因此[1 0]代表 东和[0 -1]代表南。这使得移动变得容易。
  • 机器人状态是一个记录(比结构更容易处理) :place:direction个字段。

让我们首先处理左右改变方向的功能。

我们首先定义一个辅助函数,从序列生成一个循环:

(defn cycle-map [fs]
  "maps each element in the finite sequence fs to the next, and the last to the first"
  (assoc (zipmap fs (next fs)) (last fs) (first fs)))

...我们用它来生成一个地图,将每个方向都指向左边的那个:

(def left-of (cycle-map (list [0 1] [-1 0] [0 -1] [1 0])))

我们可以将其反转以产生一个地图,该地图将每个方向都指向右边的那个:

(def right-of (clojure.set/map-invert left-of))

我们将使用这些地图作为修改机器人状态的函数(严格来说,返回修改后的机器人状态)。

现在我们定义我们的机器人状态:

(defrecord Robot [place direction])

...以及我们需要操作它的一些功能:

(defn turn-left [robot] (assoc robot :direction (left-of (:direction robot))))

defn move-forward [robot] (assoc robot :place (mapv + (:place robot) (:direction robot))))

我们试试吧:

toyrobot.core=>  (Robot. [0 0] [1 0])
{:place [0 0], :direction [1 0]}

toyrobot.core=>  (turn-left (Robot. [0 0] [1 0]))
{:place [0 0], :direction [0 1]}

toyrobot.core=> (move-forward (Robot. [0 0] [1 0]))
{:place [1 0], :direction [1 0]}

toyrobot.core=> (take 10 (iterate move-forward (Robot. [0 0] [1 0])))
({:place [0 0], :direction [1 0]}
 {:place [1 0], :direction [1 0]}
 {:place [2 0], :direction [1 0]}
 {:place [3 0], :direction [1 0]}
 {:place [4 0], :direction [1 0]}
 {:place [5 0], :direction [1 0]}
 {:place [6 0], :direction [1 0]}
 {:place [7 0], :direction [1 0]}
 {:place [8 0], :direction [1 0]}
 {:place [9 0], :direction [1 0]})

有效!