Algorithm 如何在Clojure算法实现中处理多个变量?
我是Clojure的新手,试图通过在其中实现一些算法来学习。我正在编写的算法用于计算图形数据结构的节点Algorithm 如何在Clojure算法实现中处理多个变量?,algorithm,recursion,clojure,functional-programming,immutability,Algorithm,Recursion,Clojure,Functional Programming,Immutability,我是Clojure的新手,试图通过在其中实现一些算法来学习。我正在编写的算法用于计算图形数据结构的节点介数中心性度量 我试图实现的算法(Brandes算法)中的函数如下所示: (defn ss-shortest-path [g start] (loop [state (initialize-state g start)] (if (empty? (:queue state)) state (recur (next-state state))))) 这里,V
介数中心性度量
我试图实现的算法(Brandes算法)中的函数如下所示:
(defn ss-shortest-path [g start]
(loop [state (initialize-state g start)]
(if (empty? (:queue state))
state
(recur (next-state state)))))
这里,V
是图的顶点,s
是我们试图计算和返回最短路径度量的起始节点s、Pred和sigma
这就是我通过使用为每个起始节点创建初始图形g
,来设法得出的结果start
:
(defn ss-shortest-path
[g start]
(let [nodeset (disj (nodes g) start)
pred (apply assoc {} (interleave (nodes g) (repeat nil)))
dist (apply assoc {start 0} (interleave nodeset (repeat -1)))
sigma (apply assoc {start 1} (interleave nodeset (repeat 0)))
stack []]
(loop [queue (conj clojure.lang.PersistentQueue/EMPTY start)]
(if (empty? queue)
{:sigma sigma
:pred pred
:stack stack}
(let [v (peek queue)
stack (conj stack v)]
(doseq [w (successors g v)]
(when (= (dist w) -1)
(do
(conj queue w)
(assoc dist w (+ 1 (dist v)))))
(when (= (dist w) (+ 1 (dist v)))
(do
(assoc sigma w (+ (sigma w) (sigma v)))
(assoc pred w v))))
(recur (pop queue)))))))
我知道Clojure数据结构是不可变的,因此每次我在变量pred、sigma、stack、dist
中调用conj
或assoc
,都会创建一个新的副本,原始变量保持原样
但是,我不想使用可变状态,比如atoms
,refs
,因为我有一种感觉,那就是简单地复制我已经知道的命令式样式
因此,我正在寻求一些有经验的克隆法学家的帮助,帮助我以惯用的方式创建这个函数
提前感谢。背景:这里是alg的java版本:
首先,你需要定义s,Pred,sigma。您还应该定义g、v、start等的格式
其次,我不确定这是否是最好的学习练习。您可以用Clojureloop/recur
、doseq
、以及其他方法替换Javawhile
、for
等,但它仍然感觉像是一种“强制配合”。通过阅读好书,如《勇敢与真实的Clojure》、《获得Clojure》等,你可能会更快(更深)学到更多
这个想法是,小的、独立的练习问题比单个庞大的问题学习效率更高
别忘了添加书签:
- (是的,尽管名称相似,但不同)
- 我的图书馆;)李>
我会做两件主要的事情:首先,算法有一个由多个“变量”组成的状态(队列
,堆栈
,等等)。首先,我将使用一个不可变映射构造一个表示算法状态的函数,如
(defn initialize-state [g start]
(let [nodeset (disj (nodes g) start)]
{:g g
:nodeset nodeset
:pred (apply assoc {} (interleave (nodes g) (repeat nil)))
:dist (apply assoc {start 0} (interleave nodeset (repeat -1)))
:sigma (apply assoc {start 1} (interleave nodeset (repeat 0)))
:stack []
:queue (conj clojure.lang.PersistentQueue/EMPTY start)
:current-vertex nil}))
然后,在REPL中,我将测试该映射是否针对g
和start
的各种选择正确初始化
其次,我会将算法分解为多个小函数,这些小函数以状态作为输入,并返回状态作为输出,如下所示(此代码不起作用,您必须填写缺少的部分):
带有:pre
和:post
键的映射称为pre-and-post条件,状态?
-功能可以实现为(defn state?[x](和(map?x)(包含?x:queue))
请注意,对于您编写的每个函数,在编写下一个函数之前,可以在REPL中使用一些数据对其进行测试,以确保其正常工作。现在,可以使用comp
将所有这些函数包装到一个完整的状态转换中:
(def next-state (comp pop-queue process-successors next-vertex))
最后一个算法是这样的:
(defn ss-shortest-path [g start]
(loop [state (initialize-state g start)]
(if (empty? (:queue state))
state
(recur (next-state state)))))
总而言之,如果你把算法分解成更小的部分,可以单独开发和验证,那么实现算法就容易多了。其他答案都没有明确说明这一点,所以我想我应该澄清一下“处理不变性”部分
我认为循环
是这里使用的正确构造。设置的问题是循环中唯一的累加器是队列
。从一次迭代到下一次迭代的每一位数据都应该是循环累加器的一部分
在这里的例子中,dist
、sigma
、pred
和stack
都是可能从一个循环迭代更改到下一个循环迭代的数据,因此它们都应该在循环的方括号中声明。然后,当您需要更新其中一条数据时,您将更新提供给recur
:
(loop [queue (conj clojure.lang.PersistentQueue/EMPTY start)
pred (apply assoc {} (interleave (nodes g) (repeat nil)))
dist (apply assoc {start 0} (interleave nodeset (repeat -1)))
sigma (apply assoc {start 1} (interleave nodeset (repeat 0)))
stack []]
(if (empty? queue)
(Return everything)
(recur (conj queue some-data)
(assoc pred :some-key some-data)
(assoc dist :another-key other-data)
(assoc sigma :key data)
(conj stack stack-data))))
赋予recur
(本例中更新的不可变结构)的所有内容都将在下一次迭代中提供给loop
我同意@Rulle的观点,虽然你们有这么多累加器,但把它们都打包到自己的结构中比手工处理每次迭代要整洁得多。这真是太棒了。正是我想要的。我能够完成代码,它工作了!关于“:pre”和“:post”地图,还有一个问题。这些函数用于什么?如何定义函数“state”。如果你能解释一下,我将不胜感激。再次感谢你的回答。很高兴我能帮上忙!“{:pre…:post…}”是所谓的前置和后置条件,这里解释:。而“state”只是一个检查数据是否与我们选择的“state”表示形式相对应的函数,例如,您可以将其实现为(defn state?[x](和(map?x)(包含?x:queue))。你不必使用前置和后置条件,许多人不必。但我发现使用断言和前置和后置条件有助于检测和定位bug。