这是一个悬而未决的问题,但我从来没有找到一个让我满意的解决方案。
假设我有这种代数数据类型:
type t = A of int | B of float | C of string
现在,假设我想写一个compare
函数 - 因为我想将我的值放在Map
中,例如 - 我会这样写:
let compare t1 t2 =
match t1, t2 with
| A x, A y -> compare x y
| A _, _ -> -1
| _, A _ -> 1
| B x, B y -> compare x y
| B _, _ -> -1
| _, B _ -> 1
| C x, C y (* or _ *) -> compare x y
或者我可以这样写:
let compare t1 t2 =
match t1, t2 with
| A x, A y -> compare x y
| B y, B x -> compare x y
| C x, C y -> compare x y
| A _, _
| B _, C _ -> -1
| _ -> 1
如果我没错,说n
是构造函数的数量,第一个compare
将有3 * (n - 1) + 1
个案例,第二个将有n + ((n - 2) * (n - 1)) / 2 + 2
个案例
这是非常不满意的,因为:
n = 3
(我们的案例):7或6个案例n = 4
:10或8个案例n = 5
:13或13个案例它增长得非常快。
所以,我想知道,你这样做,或者你使用其他方法吗?
或者,更好的是,是否有可能做类似
的事情let compare t1 t2 =
match t1, t2 with
| c1 x, c2 y ->
let c = compare c1 c2 in
if c = 0 then compare x y else c
或者,
let compare (c1 x) (c2 y) =
let c = compare c1 c2 in
if c = 0 then compare x y else c
编辑:如果两个构造函数对于señorDrupup(from Guadalup ;-P)是相等的,则添加一个比较
答案 0 :(得分:6)
您可以使用ppx_deriving生成此功能。
以下将创建一个正确的函数compare : t -> t -> int
:
type t = A of int | B of float | C of string [@@deriving ord]
如果您感到好奇,或者无法使用ppx_deriving
,这里是生成的代码,它使用与Reimer解决方案类似的策略。
% utop -require ppx_deriving.std -dsource
utop # type t = A of int | B of float | C of string [@@deriving ord];;
type t = | A of int | B of float | C of string [@@deriving ord]
let rec (compare : t -> t -> Ppx_deriving_runtime.int) =
((let open! Ppx_deriving_runtime in
fun lhs ->
fun rhs ->
match (lhs, rhs) with
| (A lhs0,A rhs0) -> Pervasives.compare lhs0 rhs0
| (B lhs0,B rhs0) -> Pervasives.compare lhs0 rhs0
| (C lhs0,C rhs0) -> Pervasives.compare lhs0 rhs0
| _ ->
let to_int = function
| A _ -> 0
| B _ -> 1
| C _ -> 2
in
Pervasives.compare (to_int lhs) (to_int rhs))
[@ocaml.warning "-A"]) ;;
type t = A of int | B of float | C of string
val compare : t -> t -> int = <fun>
答案 1 :(得分:4)
有几种选择。首先,您可以使用Obj
模块直接比较表示(尽管这显然取决于实现):
type t = A of int | B of float | C of string
let compare_t a b =
match (a, b) with
| A x, A y -> compare x y
| B x, B y -> compare x y
| C x, C y -> compare x y
| _ -> compare (Obj.tag (Obj.repr a)) (Obj.tag (Obj.repr b))
如果您希望它更具可移植性,您可以编写(或生成)一个为您提供数字标记的函数。事实证明,当前的OCaml编译器似乎正在寻找它并且似乎能够忽略底层函数调用。
let tag_of_t = function
| A _ -> 0
| B _ -> 1
| C _ -> 2
let compare_t a b =
match (a, b) with
| A x, A y -> compare x y
| B x, B y -> compare x y
| C x, C y -> compare x y
| _ -> compare (tag_of_t a) (tag_of_t b)
你仍然需要处理线性增长,而不是二次增长。
答案 2 :(得分:1)
如果您只需要比较使用Map.Make
仿函数,即您不关心特定订单,则可以使用内置比较。特别是它适用于您的示例中给出的类型:
let compare t1 t2 = compare t1 t2
如果某些情况(但并非所有情况都超出了内置比较的范围)(例如它们是函数类型),您仍然可以将剩余的情况视为O(1)代码大小。例如:
type t = A of int -> int | B of float | C of string
let compare t1 t2 = match t1,t2 with
| A f1, A f2 -> ...
| A _, _ -> -1
| _, A _ -> 1
| _, _ -> compare t1 t2
如果一切都失败了,仍然可以选择进行元编程(例如通过camlp5)来自动创建一个长时间的比较。我过去已经这样做了,因为否则就是这样的顺序。
type three = Zero | One | Two
未指定(Pervasives.compare
仅被指定为某些总订单)并且我想要自然顺序。