我想试试clojure,做一个从开始到目标打印路径的谜题。我的尝试是什么都不打印。
我知道我是用程序的方式写的,但不确定用什么样的方式来思考功能性的写作。因此,我想了解为什么它不打印任何内容,如何让一个条件执行2个操作(即:向字符串添加方向并更新位置。我在网上找到的所有示例都只执行1个操作),以及如何实际使其工作。理想情况下,如何使我的方法发挥作用,以及什么是理想的clojure方法。
(defn -main [& args]
(def x 2)
(def y 3)
(def t_x 10)
(def t_y 15)
(while true
(let [m ""]
(cond
(> y t_y) (let [m (str m "N")])(- y 1)
(< y t_y) (let [m (str m "S")])(+ y 1)
(> x t_x) (let [m (str m "W")])(- x 1)
(< x t_x) (let [m (str m "E")])(+ x 1))
; A single line providing the move to be made: N NE E SE S SW W or NW
(println m)))))
谢谢。
此解决方案不使用循环或递归,而是使用序列,这是Clojure:中的一种流行抽象
(defn axis-steps [a b axis axis']
(concat
(cond
(< a b) (repeat (- b a) axis)
(< b a) (repeat (- a b) axis'))
(repeat nil)))
(defn path [x y tx ty]
(let [ns (axis-steps y ty "S" "N") ; = ("N" "N" nil nil nil ...)
ew (axis-steps x tx "E" "W") ; = ("E" "E" "E" nil nil nil ...)
nsew (map str ns ew) ; = ("NE" "NE" "E" "" "" "" ... )
steps (take-while seq nsew)] ; = ("NE" "NE" "E")
(clojure.string/join " " steps))) ; = "NE NE E"
(path 2 3 10 15) ; => "SE SE SE SE SE SE SE SE S S S S"
- 首先,您希望将变量视为可变变量。Clojure中的变量不能像那样修改。除非您将它们声明为
atoms
。atom
s是特殊的可变变量 (while true ...)
非常不符合法律,非常符合C-ish
。将(loop [ ... ] ...)
与recur
结合使用。但为此,您必须了解loop
和recur
的语法- 您的
(let [m (str m "N")])
thelet表单在执行任何操作之前关闭。本地分配[m (str m "N")]
仅在let
表单关闭]
和最后一个)
之间的s-之前有效。在封闭let之后执行(- y 1)
。以下所有cond
子句都是这种情况。最后执行(println m)
,因为它在(let [m ""]
表单中,所以会打印""
对于Clojure原子,文档在这里它列出了一个例子:
;; a var to be used for its side effects
(def a (atom 10))
;; #'user/a
(while (pos? @a)
(println @a)
(swap! a dec))
;; 10
;; 9
;; 8
;; 7
;; 6
;; 5
;; 4
;; 3
;; 2
;; 1
;;=> nil
然而,作为一名命令式程序员,人们会从a = new_value
的角度来思考。其中最好使用CCD_ 20。因为swap!
一开始也完全误导了我。假设你想做a = a + 1
。然后你必须想清楚,有什么功能可以:a = func(a)
?-它将是CCD_ 24。则CCD_ 25等效于CCD_=>该集合的CCD_ 27到CCD_。但假设你想增加1以上,假设增加3。那么除了a
之外,你还需要给inc
一个附加的论点。假设您希望a
设置为(inc a 3)
。然后这个交换!调用看起来像:(swap! a incf 3)
。
因此,您必须以这种方式将所有变量(x
、y
、m
)声明为原子。并且使用swap!
(或者对于初学者来说更容易使用reset!
来更新它们的值。最终,当访问可变变量应该是线程安全的时,您必须使用偶数引用。
用loop [variables] <actions> recur)
递归循环求解
啊,但我看到这不是一个游戏的情况,只是一些过程本身在运行。
对于这种情况,我将您的尝试转换为递归循环。
(loop [x 2
y 3
tx 10
ty 15
m ""]
(println m)
(cond (< ty y) (recur x (- y 1) tx ty (str m "N "))
(< y ty) (recur x (+ y 1) tx ty (str m "S "))
(< tx x) (recur (- x 1) y tx ty (str m "W "))
(< x tx) (recur (+ x 1) y tx ty (str m "E "))))
它打印:
S
S S
S S S
S S S S
S S S S S
S S S S S S
S S S S S S S
S S S S S S S S
S S S S S S S S S
S S S S S S S S S S
S S S S S S S S S S S
S S S S S S S S S S S S
S S S S S S S S S S S S E
S S S S S S S S S S S S E E
S S S S S S S S S S S S E E E
S S S S S S S S S S S S E E E E
S S S S S S S S S S S S E E E E E
S S S S S S S S S S S S E E E E E E
S S S S S S S S S S S S E E E E E E E
S S S S S S S S S S S S E E E E E E E E
;; => nil
现在我明白了,你的t_x
和t_y
是目标坐标。
对于SE
、NE
等这样的组合动作,你必须引入测试它们的子句,例如。CCD_ 44等条款。
正如我所看到的,tx
和ty
永远不会改变。所以把它们从loop
-recur
循环中排除:
(let [tx 10 ty 15]
(loop [x 2
y 3
m ""]
(when (not= m "") ; print only when m is not an empty string
(println m))
(cond (and (< ty y) (< x tx)) (recur (+ x 1) (- y 1) (str m "NE "))
(and (< y ty) (< x tx)) (recur (+ x 1) (+ y 1) (str m "SE "))
(and (< ty y) (< tx x)) (recur (- x 1) (- y 1) (str m "NW "))
(and (< y ty) (< x tx)) (recur (- x 1) (+ y 1) (str m "SW "))
(< ty y) (recur x (- y 1) (str m "N "))
(< y ty) (recur x (+ y 1) (str m "S "))
(< tx x) (recur (- x 1) y (str m "W "))
(< x tx) (recur (+ x 1) y (str m "E ")))))
它打印:
SE
SE SE
SE SE SE
SE SE SE SE
SE SE SE SE SE
SE SE SE SE SE SE
SE SE SE SE SE SE SE
SE SE SE SE SE SE SE SE
SE SE SE SE SE SE SE SE S
SE SE SE SE SE SE SE SE S S
SE SE SE SE SE SE SE SE S S S
SE SE SE SE SE SE SE SE S S S S
我的建议是,使[dx dy]
的懒惰序列走向终点。类似这样的东西:
(defn path [curr end]
(when-not (= curr end)
(lazy-seq
(let [delta (mapv compare end curr)]
(cons delta (path (mapv + delta curr) end))))))
user> (path [2 3] [10 15])
;;=> ([1 1] [1 1] [1 1] [1 1] [1 1] [1 1] [1 1] [1 1] [0 1] [0 1] [0 1] [0 1])
因此,接下来你需要的是将所有内容翻译成人类可读的方向:
(defn translate [[dx dy]]
(str ({-1 S 1 N} dx)
({-1 W 1 E} dy)))
user> (map translate (path [2 3] [10 15]))
;;=> ("NE" "NE" "NE" "NE" "NE" "NE" "NE" "NE" "E" "E" "E" "E")
user> (map translate (path [10 15] [3 21]))
;;=> ("SE" "SE" "SE" "SE" "SE" "SE" "S")
user> (map translate (path [10 15] [3 3]))
;;=> ("SW" "SW" "SW" "SW" "SW" "SW" "SW" "W" "W" "W" "W" "W")
以下是我将如何使用我最喜欢的模板项目&库:
(ns demo.core
(:use tupelo.core))
(defn next-x
[x x-tgt]
(cond
(< x x-tgt) {:x (inc x) :dir "E"}
(> x x-tgt) {:x (dec x) :dir "W"}
:else {:x x :dir ""}))
(defn next-y
[y y-tgt]
(cond
(< y y-tgt) {:y (inc y) :dir "N"}
(> y y-tgt) {:y (dec y) :dir "S"}
:else {:y y :dir ""}))
(defn update-state
[pos pos-goal]
(let [x-info (next-x (:x pos) (:x pos-goal))
y-info (next-y (:y pos) (:y pos-goal))
pos-next {:x (:x x-info) :y (:y y-info)}
dir-str (str (:dir y-info) (:dir x-info))
state-next {:pos-next pos-next :dir-str dir-str}]
state-next))
(defn walk-path [pos-init pos-goal]
(loop [pos pos-init]
(when (not= pos pos-goal)
(let [state-next (update-state pos pos-goal)]
(println (:dir-str state-next))
(recur (:pos-next state-next))))))
以及一些单元测试表明它可以工作:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(dotest
(is= (next-x 0 5) {:x 1, :dir "E"})
(is= (next-x 6 5) {:x 5, :dir "W"})
(is= (next-x 5 5) {:x 5, :dir ""})
(is= (next-y 0 5) {:y 1, :dir "N"})
(is= (next-y 6 5) {:y 5, :dir "S"})
(is= (next-y 5 5) {:y 5, :dir ""}))
(dotest
(is= (update-state {:x 0, :y 0} {:x 1, :y 1}) {:pos-next {:x 1, :y 1}, :dir-str "NE"})
(is= (update-state {:x 1, :y 0} {:x 1, :y 1}) {:pos-next {:x 1, :y 1}, :dir-str "N"})
(is= (update-state {:x 2, :y 0} {:x 1, :y 1}) {:pos-next {:x 1, :y 1}, :dir-str "NW"})
(is= (update-state {:x 0, :y 1} {:x 1, :y 1}) {:pos-next {:x 1, :y 1}, :dir-str "E"})
(is= (update-state {:x 1, :y 1} {:x 1, :y 1}) {:pos-next {:x 1, :y 1}, :dir-str ""})
(is= (update-state {:x 2, :y 1} {:x 1, :y 1}) {:pos-next {:x 1, :y 1}, :dir-str "W"})
(is= (update-state {:x 0, :y 2} {:x 1, :y 1}) {:pos-next {:x 1, :y 1}, :dir-str "SE"})
(is= (update-state {:x 1, :y 2} {:x 1, :y 1}) {:pos-next {:x 1, :y 1}, :dir-str "S"})
(is= (update-state {:x 2, :y 2} {:x 1, :y 1}) {:pos-next {:x 1, :y 1}, :dir-str "SW"}))
最终结果:
(dotest
(let [pos-init {:x 0 :y 0}
pos-goal {:x 3 :y 5}
str-result (with-out-str
(walk-path pos-init pos-goal))]
; (println str-result) ; uncomment to print result
(is-nonblank= str-result
"NE
NE
NE
N
N")))
功能CCD_ 50&可以合并的next-y
和可以稍微清理的update-state
,但我想保持简单,不使用更高级的功能或辅助功能即可启动。
如需参考,请参阅此文档来源列表,尤其是Clojure CheatSheet和书";Getting Clojure">
关于您的问题:
(< y y-tgt) {:y (inc y) :dir "N"}
-Clojure通常使用";关键字";而不是字符串来命名映射中的字段。在源代码中,它们的前面有一个冒号,而不是一对引号。pos-next {:x (:x x-info) :y (:y y-info)}
-正确。返回值是一个带有关键字:x
和:y
的新映射,它们是从变量x-info
和y-info
中的映射复制而来的将
loop
语句想象成类似于let
块。每对的第一个符号定义了一个新的"0";循环变量";,并且每对的第二个符号是该变量的初始值。由于只有1对,因此recur
语句中只有1个循环变量,因此只有1个值。loop/recur
形式可以具有零个或多个循环变量。