我如何在clojure中编写以下python的等效代码,但严格使用递归进行堆栈管理(例如,不使用带有向量作为前沿的循环/重复)?我意识到它很简单,有一个矢量保持你的路径,只是偷看/弹出,但我这样做是为了一个思考练习。
Python版
def dfs(start,successors,goal,visited=set()):
if start not in visited:
visited.add(start)
for s in successors.get(start):
if goal(s):
return s
else:
res = dfs(s,successors)
if res: return res #bail early when found
return False
Clojure版本
(defn dfs [start goal? successors visited]
(if (goal? start)
start
(when (not (contains? visited start))
(mapcat #(dfs % goal? successors (conj visited start))
(successors start)))))
由于迭代是通过Clojure版本中的map调用来控制的,所以你不能像Python那样尽早保释,例如if goal(s): return s
。
由于您正在使用map收集列表内的递归调用,因此即使找到目标,您也必须评估每个可能的节点。只有在探索完所有节点后才能得到结果。
(defn dfs-non-rec [frontier goal? successors visited]
(loop [f frontier g? goal? s successors v visited]
(let [node (peek f)]
(cond ; case 1
(goal? node)
node
;case 2
(not (contains? v node))
(recur (vec (concat (pop f) (successors node))) g? s (conj v node))
;case 3
:else
(recur (pop f) g? s (conj v node))))))
我该如何处理?
修改
<小时/> 关于一些提供的答案是否实际上是深度优先的,我有些困惑。混淆源于我对输入的假设,我本来应该在这篇文章中提供。我将输入视为邻接列表,它代表图形,而不是树
(def graph {"a" ["b","c","d"],
"b" ["a","e","f"],
"c" ["x","y"],
"d" [],
"e" [],
"f" [],
"x" ["c"],
"y" ["e"]})
然后当转换为seq时,所遵循的顺序实际上是深度优先,用于通过调用图上的seq创建的结果树,但是不遵循邻接列表所隐含的顺序,因为图形结构在转换中丢失了。
因此,如果您从x
开始寻找节点a
,我希望遍历顺序为adcyex
,而不是abcdbaefcxy
答案 0 :(得分:2)
首先,你并不需要检查树的循环,因为clojure的数据结构没有循环引用(除非你不使用{{{ 1}}引用另一个原子,这是一个明显的代码气味)。简单的遍历方式可能看起来像这样(这种方式被大量的lisp(和整体编程)书籍引用):
atom
另外,在clojure中有更简洁(因而也就是惯用)的方法。最简单的方法是使用tree-seq:
user> (defn dfs [goal? data]
(if (goal? data)
data
(loop [data data]
(when-let [[x & xs] (seq data)]
(cond (goal? x) x
(coll? x) (recur (concat x xs))
:else (recur xs))))))
user> (dfs #{10} [1 [3 5 [7 9] [10] 11 12]])
10
user> (dfs #{100} [1 [3 5 [7 9] [10] 11 12]])
nil
user> (defn dfs [goal? tree]
(first (filter goal? (tree-seq coll? seq tree))))
#'user/dfs
user> (dfs #{10} [1 [3 5 [7 9] [10] 11 12]])
10
user> (dfs #{100} [1 [3 5 [7 9] [10] 11 12]])
nil
user> (dfs (every-pred number? even?) [1 [3 5 [7 9] [10] 11 12]])
10
是懒惰的,所以它只会遍历树,直到找到所需的值。
另一种方法是使用clojure&#39; zippers:
tree-seq
答案 1 :(得分:0)
我会像下面那样使用the Tupelo library进行测试:
(ns tst.demo.core
(:use tupelo.test)
(:require [tupelo.core :as t] ))
(def data [[1 2 [3]]
[[4 5] 6]
[7]])
(def search-result (atom nil))
(defn search-impl
[goal? data]
(when (= ::not-found @search-result)
(if (goal? data)
(reset! search-result data)
(when (coll? data)
(doseq [it data]
(search-impl goal? it))))))
(defn search [goal? data]
(reset! search-result ::not-found)
(search-impl goal? data)
@search-result)
(dotest
(println "1 => " (search #(= 5 %) data))
(println "2 => " (search #(and (integer? %) (even? %)) data))
(println "3 => " (search #(= [4 5] %) data))
(println "4 => " (search #(= 99 %) data)) )
结果:
1 => 5
2 => 2
3 => [4 5]
4 => :tst.demo.core/not-found
当它让你的程序更清晰和/或更简单时,不要害怕使用一些可变状态(在这种情况下是一个原子)。
如果你真的想隐藏原子全局可见,请执行以下操作:
(defn search2-impl
[search2-result goal? data]
(when (= ::not-found @search2-result)
(if (goal? data)
(reset! search2-result data)
(when (coll? data)
(doseq [it data]
(search2-impl search2-result goal? it))))))
(defn search2 [goal? data]
(let [search2-result (atom ::not-found)]
(search2-impl search2-result goal? data)
@search2-result))
(dotest
(println "21 => " (search2 #(= 5 %) data))
(println "22 => " (search2 #(and (integer? %) (even? %)) data))
(println "23 => " (search2 #(= [4 5] %) data))
(println "24 => " (search2 #(= 99 %) data)))
21 => 5
22 => 2
23 => [4 5]
24 => :tst.demo.core/not-found