clojure中的条件来构建字符串和更新变量



我想试试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"
  1. 首先,您希望将变量视为可变变量。Clojure中的变量不能像那样修改。除非您将它们声明为atomsatoms是特殊的可变变量
  2. (while true ...)非常不符合法律,非常符合C-ish。将(loop [ ... ] ...)recur结合使用。但为此,您必须了解looprecur的语法
  3. 您的(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)

因此,您必须以这种方式将所有变量(xym)声明为原子。并且使用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_xt_y是目标坐标。

对于SENE等这样的组合动作,你必须引入测试它们的子句,例如。CCD_ 44等条款。

正如我所看到的,txty永远不会改变。所以把它们从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">

关于您的问题:

  1. (< y y-tgt) {:y (inc y) :dir "N"}-Clojure通常使用";关键字";而不是字符串来命名映射中的字段。在源代码中,它们的前面有一个冒号,而不是一对引号。

  2. pos-next {:x (:x x-info) :y (:y y-info)}-正确。返回值是一个带有关键字:x:y的新映射,它们是从变量x-infoy-info中的映射复制而来的

  3. loop语句想象成类似于let块。每对的第一个符号定义了一个新的"0";循环变量";,并且每对的第二个符号是该变量的初始值。由于只有1对,因此recur语句中只有1个循环变量,因此只有1个值。loop/recur形式可以具有零个或多个循环变量。

最新更新