我需要在Clojure中表示有向图。我想将图中的每个节点表示为一个对象(可能是一个记录),其中包含一个名为:edges
的字段,该字段是可以从当前节点直接访问的节点的集合。希望不言而喻,但我希望这些图表是不可变的。
只要我进行拓扑排序并“从叶子上”构建每个图形,我就可以用这种方法构造有向非循环图形。
然而,这种方法不适用于循环图。我能想到的一个解决方法是为整个图形设置一个单独的集合(可能是地图或矢量)。然后,每个节点中的:edges
字段将具有键(或索引)到图的边集合中。添加这种额外的间接级别是有效的,因为我可以在他们(将)引用的东西之前创建密钥(或索引),但它感觉就像一个kludge。每当我想要访问一个相邻节点时,我不仅需要进行额外的查找,而且还必须传递全局边缘集合,这感觉非常笨拙。
我听说有些Lisps有办法创建循环列表而不需要使用变异函数。有没有办法在Clojure中创建不可变的循环数据结构?
答案 0 :(得分:6)
您可以将每个节点包装在一个引用中,为它指定一个稳定的句柄(并允许您修改可以以nil开头的引用)。然后可以以这种方式构建循环图。这确实有"额外"当然是间接的。
我认为这不是一个好主意。你的第二个想法是更常见的实现。我们构建了这样的东西来保存RDF图,并且可以在核心数据结构和层顶部索引的基础上构建它而不需要太多努力。
答案 1 :(得分:6)
过去几天我一直在玩这个。
我首先尝试让每个节点都有一组refs到边缘,每个边缘都有一组refs到节点。我在(dosync... (ref-set...))
类型的操作中将它们设置为彼此相等。我不喜欢这样,因为更改一个节点需要大量更新,打印图表有点棘手。我不得不重写print-method
多方法,因此repl不会堆栈溢出。此外,每当我想为现有节点添加边缘时,我必须首先从图中提取实际节点,然后进行各种边缘更新以及确保每个人都持有最新版本的事情另一件事。此外,因为事物在参考中,确定某些东西是否与其他东西相关联是线性时间操作,这似乎是不优雅的。在确定使用这种方法实际执行任何有用的算法之前,我并没有走得太远。
然后我尝试了另一种方法,它是其他地方提到的矩阵的变体。该图是一个clojure映射,其中键是节点(不是节点的refs),值是另一个映射,其中键是相邻节点,每个键的单个值是该节点的边缘,表示为表示边缘强度的数值,或我在别处定义的边缘结构。
对于1->2, 1->3, 2->5, 5->2
(def graph {node-1 {node-2 edge12, node-3 edge13},
node-2 {node-5 edge25},
node-3 nil ;;no edge leaves from node 3
node-5 {node-2 edge52}) ;; nodes 2 and 5 have an undirected edge
要访问node-1的邻居,请转到(keys (graph node-1))
或调用其他位置(neighbors graph node-1)
定义的函数,或者您可以说((graph node-1) node-2)
从1->2
获取边缘。
几个优点:
我看到的唯一不利的缺点是,对于任何给定的操作(添加,删除,任何算法),您不能只是将它传递给起始节点。你必须传递整个图形图和一个起始节点,这可能是一个公平的代价,以支付整个事物的简单性。另一个小缺点(或者可能不是)对于无向边缘,您必须在每个方向上定义边缘。这实际上没问题,因为有时边缘对每个方向都有不同的值,这个方案允许你这样做。
我在这里看到的另一件事是,因为边缘隐含在地图中存在键值对,所以无法定义超边界(即连接超过2个节点的边界)。我不认为这是一个大问题,因为我遇到的大多数图算法(全部?)只处理连接2个节点的边缘。
答案 2 :(得分:3)
我之前遇到过这个挑战,并得出结论认为目前在Clojure中使用真正不可变的数据结构是不可能的。
但是,您可能会发现以下一个或多个选项可以接受: