【问题标题】:Implementing Karger's minimum cut algorithm in functional paradigm在函数范式中实现 Karger 的最小割算法
【发布时间】:2014-05-29 11:23:13
【问题描述】:

我用任何命令式语言实现这个算法都没有问题,但我在用 Clojure 或任何其他函数式语言实现它时遇到了困难。许多算法都是根据使用可变数据结构和命令式循环来描述的,我很难将所有这些都转换为功能域。

这是我在 Clojure 中使用邻接列表作为图形表示来实现它的不完整尝试(草稿,而不是工作实现):

(ns karger.core
  (:require [clojure.string :as string]))

(defn load-data []
    (zipmap 
     (range 1 1000)
     (map rest (map read-string
       (string/split (slurp "data.txt") #"\n")))))

(defn min-cut [graph] 
  (let [start (rand-int (count graph))
        end (rand-int (graph start))
        start-list (nth graph start)]
    (for [x (graph end)
          :when (not= x start)]
      (assoc graph start (conj start-list x)))
      ))

(count (load-data))

谁能给我这个算法的参考实现(最好用 Clojure 编写)?另外,如果有人给我一个一般性建议,将命令式术语描述的算法转换为功能域,我想知道。

提前致谢!

更新 #1

这里是用 Python 编写的算法实现的链接:http://pastebin.com/WwWCtxpu

【问题讨论】:

  • 你能用命令式语言提供算法吗?
  • 你能展示一下 data.txt 中的一行吗?如果一个人不知道该文件中的数据将如何形成,就很难分辨出什么是预期的和什么是错误。我假设使用 read-stringrest 每一行都是一个 Clojure 序列。
  • 抽象的描述看起来并不十分必要。 en.wikipedia.org/wiki/…
  • 一般来说,函数式编程的想法是提供所有将函数参数化为附加参数的“状态”,并在函数的返回值中返回所有“状态变化”(然后使用这些调用函数中的新值)。我开始研究算法的纯函数 clojure 实现,基于解决方案 here 并将图形作为输入和输出传递给每个函数,但这对于现在的答案来说有点多 - 我可能会跟进如果没有其他人做,另一次翻译。
  • 此外,这里的代码远不是算法的实现,并且充斥着对数据类型和函数的滥用。

标签: algorithm graph clojure functional-programming imperative-programming


【解决方案1】:

您的代码存在根本问题:

  • 你的start-list绑定是一个数字,不能conjed to'
  • 您正在调用 assoc 并忽略返回值,从而使其成为无操作。
  • 您正在使用for,就好像它是一个循环构造(它是一个列表理解)
  • 您在哈希映射上调用 nth,这将始终失败(zipmap 返回哈希映射)

一般来说,函数式编程的想法是将可变变量提升为不可变的局部绑定,方法是使“世界状态”完全由函数参数封装,并使用该状态的改进版本进行函数调用。

这是一个基于您发布的python解决方案的工作实现,以及java示例here使用的图形文件

(ns min-cut.core
  (:require [clojure.java.io :as io]
            [clojure.string :as string]
            [clojure.pprint :refer [pprint]]))

(defn make-maps
  [filename]
  (reduce (fn [graph line]
            (let [[node & edges] (->> line
                                      (#(string/split % #"\W+"))
                                      (remove #{""})
                                      (map read-string))]
              (assoc graph node (set edges))))
          {}
          (line-seq (io/reader filename))))

(defn karger
  [graph]
  (if (<= (count (keys graph))
          2)
    (count (graph (apply min (keys graph))))
    (let [start (rand-nth (keys graph))
          finish (rand-nth (vec (graph start)))
          graph (loop [g graph
                       [edge & edges] (seq (graph finish))]
                  (if-not edge
                    g
                    (recur
                     (if (= edge start)
                       g
                       (update-in g [start] conj edge))
                     edges)))
          graph (loop [g graph
                       [edge & edges] (seq (graph finish))]
                  (if-not edge
                    g
                    (let [gr (update-in g [edge] disj finish)
                          gr (if (= edge start)
                               gr
                               (update-in gr [edge] conj start))]
                      (recur gr edges))))
          graph (dissoc graph finish)]
      (recur graph))))

(defn -main
  [& [file]]
  (let [file (or file "kargerAdj.txt")
        graph (make-maps file)]
    (println "min cut is: "
             (reduce min (repeatedly 1801 #(karger graph))))))

这是对 python 代码的非常直译,所以这个代码有很多地方可以改进。对于初学者来说,karger 函数中的两个循环可能会被一个 reduce 替换,这样会更加简洁明了。

请注意,在此代码中创建的任何值都不会发生变化 - 值会被反弹,但不会更改传入的数据结构,并且唯一使用的全局定义是函数 make-mapskarger 和 @987654333 @ - 所有数据都在本地绑定并传递给下一个用户。

【讨论】:

  • 这可以很容易地与 core.reducers 并行,这样每个 CPU 都在计算尝试并保留最小的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-04-02
  • 2019-12-23
  • 2014-07-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多