我已经看到了一些图形顶点签名,甚至提出了我自己的:
module type VERTEX = sig
type t
type label
val equal : t -> t -> bool
val create : label -> t
val label : t -> label
end
但我完全不知道如何将其作为模块实现。应该标注哪些类型和标签?如何根据标签创建t?我如何从t?
获得标签答案 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 = string
和t = label
。所以t = string
和equal
是字符串相等。
轰!我们在这里:
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
函数可能是label
对t
的投射,其中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
,以及将任意信息附加到节点而不依赖于外部映射。