如何用函数式编程语言实现图和图算法?

发布于 2024-09-04 08:06:18 字数 166 浏览 5 评论 0 原文

基本上,我知道如何创建图形数据结构并在允许副作用的编程语言中使用 Dijkstra 算法。通常,图算法使用一种结构将某些节点标记为“已访问”,但这会产生副作用,这是我试图避免的。

我可以想到一种用函数式语言实现这一点的方法,但它基本上需要将大量状态传递给不同的函数,我想知道是否有更节省空间的解决方案。

Basically, I know how to create graph data structures and use Dijkstra's algorithm in programming languages where side effects are allowed. Typically, graph algorithms use a structure to mark certain nodes as 'visited', but this has side effects, which I'm trying to avoid.

I can think of one way to implement this in a functional language, but it basically requires passing around large amounts of state to different functions, and I'm wondering if there is a more space-efficient solution.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

呆头 2024-09-11 08:06:18

您可以查看 Martin Erwig 的 Haskell 函数图形库 是如何工作的。例如,其 最短路径函数都是纯粹的,你可以看到源代码 了解其实现方式。

另一种选择,就像提到的 fmark 一样,是使用一个抽象,它允许您根据状态实现纯函数。他提到了 State monad(在 惰性严格品种)。另一种选择,如果您使用 GHC Haskell 编译器/解释器(或者我认为任何支持 2 级类型的 Haskell 实现),另一种选择是 ST monad,它允许您编写处理可变变量的纯函数内部。

You might check out how Martin Erwig's Haskell functional graph library does things. For instance, its shortest-path functions are all pure, and you can see the source code for how it's implemented.

Another option, like fmark mentioned, is to use an abstraction which allows you to implement pure functions in terms of state. He mentions the State monad (which is available in both lazy and strict varieties). Another option, if you're working in the GHC Haskell compiler/interpreter (or, I think, any Haskell implementation which supports rank-2 types), another option is the ST monad, which allows you to write pure functions which deal with mutable variables internally.

爱她像谁 2024-09-11 08:06:18

我只是将访问的集合保留为一个集合并将其作为参数传递。任何有序类型的集合和超高效整数集合都有有效的日志时间实现。

为了表示图,我使用邻接列表,或者使用有限映射将每个节点映射到其后继列表。这取决于我想做什么。

我推荐 Chris Okasaki 的 纯函数式数据结构,而不是 Abelson 和 Sussman 。我已经链接到克里斯的论文,但如果你有钱,他将其扩展为 很棒的书


只是为了一笑,这里有一个有点可怕的反向后序深度优先搜索,在 Haskell 中以连续传递的方式完成。这直接来自 Hoopl 优化器库:

postorder_dfs_from_except :: forall block e . (NonLocal block, LabelsPtr e)
                          => LabelMap (block C C) -> e -> LabelSet -> [block C C]
postorder_dfs_from_except blocks b visited =
 vchildren (get_children b) (\acc _visited -> acc) [] visited
 where
   vnode :: block C C -> ([block C C] -> LabelSet -> a) 
                      -> ([block C C] -> LabelSet -> a)
   vnode block cont acc visited =
        if setMember id visited then
            cont acc visited
        else
            let cont' acc visited = cont (block:acc) visited in
            vchildren (get_children block) cont' acc (setInsert id     visited)
      where id = entryLabel block
   vchildren bs cont acc visited = next bs acc visited
      where next children acc visited =
                case children of []     -> cont acc visited
                                 (b:bs) -> vnode b (next bs) acc     visited
   get_children block = foldr add_id [] $ targetLabels bloc
   add_id id rst = case lookupFact id blocks of
                      Just b -> b : rst
                      Nothing -> rst

I just keep the visited set as a set and pass it as a parameter. There are efficient log-time implementations of sets of any ordered type and extra-efficient sets of integers.

To represent a graph I use adjacency lists, or I'll use a finite map that maps each node to a list of its successors. It depends what I want to do.

Rather than Abelson and Sussman, I recommend Chris Okasaki's Purely Functional Data Structures. I've linked to Chris's dissertation, but if you have the money, he expanded it into an excellent book.


Just for grins, here's a slightly scary reverse postorder depth-first search done in continuation-passing style in Haskell. This is straight out of the Hoopl optimizer library:

postorder_dfs_from_except :: forall block e . (NonLocal block, LabelsPtr e)
                          => LabelMap (block C C) -> e -> LabelSet -> [block C C]
postorder_dfs_from_except blocks b visited =
 vchildren (get_children b) (\acc _visited -> acc) [] visited
 where
   vnode :: block C C -> ([block C C] -> LabelSet -> a) 
                      -> ([block C C] -> LabelSet -> a)
   vnode block cont acc visited =
        if setMember id visited then
            cont acc visited
        else
            let cont' acc visited = cont (block:acc) visited in
            vchildren (get_children block) cont' acc (setInsert id     visited)
      where id = entryLabel block
   vchildren bs cont acc visited = next bs acc visited
      where next children acc visited =
                case children of []     -> cont acc visited
                                 (b:bs) -> vnode b (next bs) acc     visited
   get_children block = foldr add_id [] $ targetLabels bloc
   add_id id rst = case lookupFact id blocks of
                      Just b -> b : rst
                      Nothing -> rst
桃气十足 2024-09-11 08:06:18

如果您使用的是我熟悉的唯一函数式语言 Haskell,我建议使用 状态单子。 State monad 是一个函数的抽象,它接受一个状态并返回一个中间值和一些新的状态值。这是被认为是惯用的haskell对于那些需要维持大国的情况。

它是初学者函数式编程教程中强调的天真的“将状态作为函数结果返回并将其作为参数传递”习惯用法的更好替代方案。我想大多数函数式编程语言都有类似的构造。

If you were using haskell, the only functional language with which I am familiar, I would recommend using the State monad. The State monad is an abstraction for a function that takes a state and returns an intermediate value and some new state value. This is considered idiomatic haskell for those situations where maintaining a large state is necessary.

It is a much nicer alternative to the naive "return state as a function result and pass it as a parameter" idiom that is emphasized in beginner functional programming tutorials. I imagine most functional programming languages have a similar construct.

冬天旳寂寞 2024-09-11 08:06:18

这是一个 Swift 示例。您可能会发现这更具可读性。与超级神秘的 Haskell 示例不同,这些变量实际上是描述性命名的。

https://github.com/gistya/Functional-Swift-Graph

Here is a Swift example. You might find this a bit more readable. The variables are actually descriptively named, unlike the super cryptic Haskell examples.

https://github.com/gistya/Functional-Swift-Graph

苹果你个爱泡泡 2024-09-11 08:06:18

我很想听听一些非常聪明的技术,但我认为有两种基本方法:

  1. 修改一些全局状态对象。即副作用
  2. 将图作为参数传递给函数,返回值是修改后的图。我认为这是您“传递大量状态”的方法,

这就是函数式编程中所做的事情。如果编译器/解释器有任何好处,它将帮助您管理内存。特别是,如果您碰巧在任何函数中进行递归,您将需要确保使用尾递归。

I would love to hear about some really clever technique, but I think there are two fundamental approaches:

  1. Modify some global state object. i.e. side-effects
  2. Pass the graph as an argument to your functions with the return value being the modified graph. I assume this is your approach of "passing around large amounts of state"

That is what's done in functional programming. If the compiler/interpreter is any good, it will help manage memory for you. In particular, you'll want to make sure that you use tail recursion, if you happen to recurse in any of your functions.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文