OCaml定向图顶点模块

时间:2017-12-05 10:14:28

标签: graph module ocaml vertex directed-graph

我已经看到了一些图形顶点签名,甚至提出了我自己的:

module type VERTEX = sig
    type t
    type label

    val equal : t -> t -> bool
    val create : label -> t
    val label : t -> label  
end

但我完全不知道如何将其作为模块实现。应该标注哪些类型和标签?如何根据标签创建t?我如何从t?

获得标签

2 个答案:

答案 0 :(得分:1)

基于签名实现模块就像一个迷你拼图。以下是我将如何分析它:

我在阅读该签名时的第一句话是,该签名中无法构建label类型的值。因此,我们的实现需要更大一些,可能是指定type label = string

现在,我们有:

val create : label -> t
val label : t -> label

这是一种双射(类型是“等同的”)。实现它的最简单方法是定义type t = label,这样它实际上只有一种类型,但是从模块外部你不知道它。

其余的是

type t
val equal: t -> t -> bool

我们说label = stringt = label。所以t = stringequal是字符串相等。

轰!我们在这里:

module String_vertex : VERTEX with type label = string = struct
  type label = string
  type t = string

  let equal = String.equal

  let create x = x
  let label x = x
end

VERTEX with type label = string部分就是您要在同一文件中定义它。否则,您可以执行以下操作:

(* string_vertex.ml *)
type label = string
type t = string

let equal = String.equal

let create x = x
let label x = x

可以使用VERTEX调用任何带F(String_vertex)的仿函数F. 不过,最好使用内容string_vertex.mli创建include VERTEX with type label = string

答案 1 :(得分:0)

我是Graphlib的作者,所以我不能过去,因为这个问题直接打击了我的心。老实说,我被问到这个问题数百万次离线,从来没有能够提供一个好的答案。

真正的问题是来自OCamlGraph库的图形界面都搞砸了。我们启动Graphlib作为尝试修复它们。但是,OCamlGraph是一个有价值的Graph算法存储库,因此我们限制自己与OCamlGraph接口兼容。我们的主要问题是并且仍然是这个Vertex接口基本上在标签集和节点集之间建立双射。人们通常偶然发现这一点,因为这没有意义 - 为什么我们需要两种不同的类型,一种用于标签,另一种用于顶点,如果它们是相同的?

实际上,VERTEX接口的最简单实现是以下模块

module Int : VERTEX with type label = int = struct
  type t = int
  type label = int
  let create x = x 
  let label x = x
end

在这种情况下,我们确实在标签集和顶点集之间有一个简单的双射(通过标识endofunctor)。

然而,更深刻的外观,向我们展示了一个签名

val create : label -> t
val label : t -> label

不是真正的双射,因为双射是一对一的映射。类型系统并不真正需要或强制执行。例如,create函数可能是labelt的投射,其中label是顶点族的一些独特元素。相应地,label函数可能是遗忘函子,它返回独特的标签并忘记其他所有内容。

鉴于这种方法,我们可以有另一种实现方式:

  module Labeled = struct
    type label = int
    type t = {
      label : label;
      data : "";  
    }

    let create label = {label; data = ""}
    let label n = n.label
    let data n = n.data
    let with_data n data = {n with data}
    let compare x y = compare x.label y.label
  end

在该实现中,我们使用标签作为节点的标识,并且可以将任意属性附加到节点。在此解释中,create函数将所有节点集划分为一组equivalence classes,其中一个类的所有成员共享相同的标识,即它们代表不同的真实世界实体时间点或空间。例如,

type color = Red | Yellow | Green
module TrafficLight = struct 
   type label = int
   type t = {
     id : label;
     color : color
   }

   let create id = {id; color=Red}
   let label t = t.id
   let compare x y = compare x.id y.id
   let switch t color = {t with color}
   let color t = t.color
end

在此模型中,我们使用其ID号表示交通信号灯。颜色属性不会影响交通信号灯的标识(如果红绿灯切换到另一种颜色,它仍然是相同的交通信号灯,尽管在功能编程语言中它用两个不同的对象表示)。

上述表示的主要问题在于,在所有图形教科书中,标签的使用方式相反 - 作为不透明属性。在教科书中,他们会将交通信号灯的颜色称为标签。节点本身将表示为int。这就是为什么我说OCamlGraph接口搞砸了(因此也就是Graphlib接口)。因此,如果您不希望与教科书产生矛盾,那么您应该使用未标记的图形(int可能是节点的最佳表示)。如果需要将属性附加到节点,则可以使用外部有限映射,即数组,映射,关联列表或任何其他字典。否则,您需要记住,您的标签不是标签,而是反之亦然 - 节点。

说完这些,让我们为图顶点指定一个更好的界面:

module type VERTEX = sig 
  type id
  type label
  type t

  val create : id -> t
  val id : t -> id
  val label : t -> label
  val with_label : t -> label -> label
end

建议的界面与您的界面兼容(因此与OCamlGraph兼容),因为它是同构模式重命名(即,我们将label重命名为id)。它还允许我们创建有效的未标记节点,其中id = t,以及将任意信息附加到节点而不依赖于外部映射。