如何在Clojure算法实现中处理多个变量?

时间:2018-10-31 04:01:27

标签: algorithm recursion clojure functional-programming immutability

我是Clojure的新手,正在尝试通过在其中实现一些算法来学习。我正在编写的算法用于计算图形数据结构的节点betweenness centrality度量。

我要实现的算法(品牌算法)中的功能如下:

enter image description here

在这里,V是图形的顶点,s是我们试图从中计算并返回最短路径度量S, Pred and sigma

的起始节点

这是我设法通过使用loom为每个起始节点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数据结构是不可变的,因此每次我在变量conj中调用assocpred, sigma, stack, dist时,都会创建一个新副本,并且原始变量保持原样。

但是,我不想使用atomsrefs之类的可变状态,因为我有一种感觉,那就是简单地复制我已经知道的命令式样式。

因此,我正在寻求经验丰富的Clojurist的帮助,以帮助我以惯用的方式创建此功能。

谢谢。

3 个答案:

答案 0 :(得分:2)

我要做两件事:首先,该算法的状态由多个“变量”(onInit: function () { var oModel = new sap.ui.model.json.JSONModel(); var sHeaders = { "Content-Type": "application/json", "Accept": "application/json", "APIKey": "<<yourAPIKey>>" }; var oData = { "sourceLanguage": "en", "targetLanguages": [ "de" ], "units": [{ "value": "I would like to analyze my sales data.", "key": "ANALYZE_SALES_DATA" }] }; var ODataJSON = JSON.stringify(oData); oModel.loadData("/ml-dest/translation/translation", ODataJSON, true, "POST", null, false, sHeaders); oModel.attachRequestCompleted(function (oEvent) { var oData = oEvent.getSource().oData; console.log(oData.units[0].translations[0]); }); } queue等组成)。首先,我将使用不可变映射构建一个表示算法状态的函数,例如

stack

然后,我将在REPL中测试该映射是否针对(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})) g的各种选择正确初始化。

第二,我将算法分解为多个小函数,这些小函数以 state 作为输入,并返回 state 作为输出,例如这(此代码无效,您必须填写缺少的部分):

start

具有(defn next-vertex [state] {:pre [(state? state)] :post [(state? %)]} (let [v (peek (:queue state))] (-> state (update :stack conj v) (assoc :current-vertex v)))) (defn process-successor [state w] (let [dist-w (dist w)] (cond ;; fill in... ))) (defn process-successors [state] {:pre [(state? state)] :post [(state? %)]} (reduce process-successor state (successors (:g state) (:current-vertex state)))) (defn pop-queue [state] {:pre [(state? state)] :post [(state? %)]} (update state :queue pop)) :pre键的映射称为前后条件,并且:post函数可以实现为例如state?,就像进行健全性检查一样。

请注意,对于编写的每个函数,可以在编写下一个函数之前在REPL中使用一些数据对其进行测试,以确保其可以正常工作。现在,可以使用(defn state? [x] (and (map? x) (contains? x :queue)))将所有这些功能包装在一起,形成完整的状态转换:

comp

现在,最终算法的内容如下:

(def next-state (comp pop-queue process-successors next-vertex))

因此,总而言之,如果将算法分解为较小的部分,可以单独开发和验证,则实现算法要容易得多。

答案 1 :(得分:0)

其他答案都没有明确地说出来,所以我认为我将清除“处理不变性”部分。

我会说loop是在这里使用的正确构造。设置的问题是循环中唯一的累加器是queue。从一个迭代到下一个迭代变化的每一位数据都应该是循环累加器的一部分。

在您的情况下,distsigmapredstack都是有可能从一个循环迭代更改为下一个循环迭代的数据,因此它们应该全部在循环的方括号中声明。然后,当您需要更新其中一条数据时,可以更新对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

我在这里同意@Ru​​lle的观点,尽管您有这么多的累加器,将它们全部打包成自己的结构而不是手动处理每次迭代都比较整洁。

答案 2 :(得分:-2)

背景:这是alg的Java版本:https://github.com/jgrapht/jgrapht/blob/master/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/BetweennessCentrality.java


首先,您需要定义s,Pred,sigma。您还应该定义g,v,开始等的格式。

第二,我不确定这是最好的学习方法。您可以将Java whilefor等替换为Clojure loop/recurdoseq等,但仍然感觉像是“强制适合”。通过阅读“勇敢与真实的Clojure”,“获取Clojure”等优秀书籍,您可能可以更快(更深入!)学到更多东西。

想法是,比起单一的庞大问题,小型,独立的练习题在学习上的效率要高得多。


别忘了添加书签: