OCaml是否真的没有从列表转换为集合的函数?
如果是这种情况,是否可以制作通用函数list_to_set
?我试图制作一个没有运气的多态集。
致以最诚挚的问候,Lasse Espeholt
答案 0 :(得分:17)
基本问题:列表可以包含任何类型的元素。设置(假设您的意思是标准库的Set模块),相反,依靠元素比较操作来保持平衡树。如果您在t list
上没有比较操作,则无法将t
转换为集合。
实际问题:标准库的Set
模块是functorized:它将模块作为输入,表示元素类型及其比较操作,并生成作为输出表示集合的模块。使用列表的简单参数多态性来完成这项工作有点运动。
要做到这一点,最简单的方法是将set_of_list函数包装在仿函数中,以便它本身通过比较函数进行参数化。
module SetOfList (E : Set.OrderedType) = struct
module S = Set.Make(E)
let set_of_list li =
List.fold_left (fun set elem -> S.add elem set) S.empty li
end
然后,您可以使用String模块,该模块提供合适的compare
函数。
module SoL = SetOfList(String);;
SoL.S.cardinal (SoL.set_of_list ["foo"; "bar"; "baz"]);; (* returns 3 *)
也可以使用非functorized的集合的不同实现,例如Batteries和Extlib'PSet'实现(documentation)。建议使用仿函数设计,因为它具有更好的打字保证 - 您不能使用不同的比较操作来混合相同元素类型的集合。
注意:当然,如果已经有一个给定的set模块,从Set.Make仿函数中实例化,你不需要这一切;但是你的转换函数不会是多态的。例如,假设我的代码中定义了StringSet
模块:
module StringSet = Set.Make(String)
然后我可以使用stringset_of_list
和StringSet.add
轻松编写StringSet.empty
:
let stringset_of_list li =
List.fold_left (fun set elem -> StringSet.add elem set) StringSet.empty li
如果您不熟悉折叠,这里是一个直接的非尾递归递归版本:
let rec stringset_of_list = function
| [] -> StringSet.empty
| hd::tl -> StringSet.add hd (stringset_of_list tl)
答案 1 :(得分:10)
Ocaml 3.12具有扩展(Explicit naming of type variables和First-class modules),可以实例化并传递模块以获取多态值。
在此示例中,make_set
函数为给定的比较函数返回Set
模块,build_demo
函数构造给定模块和值列表的集合:
let make_set (type a) compare =
let module Ord = struct
type t = a
let compare = compare
end
in (module Set.Make (Ord) : Set.S with type elt = a)
let build_demo (type a) set_module xs =
let module S = (val set_module : Set.S with type elt = a) in
let set = List.fold_right S.add xs S.empty in
Printf.printf "%b\n" (S.cardinal set = List.length xs)
let demo (type a) xs = build_demo (make_set compare) xs
let _ = begin demo ['a', 'b', 'c']; demo [1, 2, 3]; end
但这并不能完全解决问题,因为编译器不允许返回值具有依赖于模块参数的类型:
let list_to_set (type a) set_module xs =
let module S = (val set_module : Set.S with type elt = a) in
List.fold_right S.add xs S.empty
Error: This `let module' expression has type S.t
In this type, the locally bound module name S escapes its scope
可能的解决方法是返回对隐藏设置值进行操作的函数集合:
let list_to_add_mem_set (type a) set_module xs =
let module S = (val set_module : Set.S with type elt = a) in
let set = ref (List.fold_right S.add xs S.empty) in
let add x = set := S.add x !set in
let mem x = S.mem x !set in
(add, mem)
答案 2 :(得分:5)
如果您不介意非常粗略的方法,可以使用多态哈希表接口。元素类型为单位的哈希表只是一个集合。
# let set_of_list l =
let res = Hashtbl.create (List.length l)
in let () = List.iter (fun x -> Hashtbl.add res x ()) l
in res;;
val set_of_list : 'a list -> ('a, unit) Hashtbl.t = <fun>
# let a = set_of_list [3;5;7];;
val a : (int, unit) Hashtbl.t = <abstr>
# let b = set_of_list ["yes";"no"];;
val b : (string, unit) Hashtbl.t = <abstr>
# Hashtbl.mem a 5;;
- : bool = true
# Hashtbl.mem a 6;;
- : bool = false
# Hashtbl.mem b "no";;
- : bool = true
如果您只是需要测试会员资格,这可能就足够了。如果你想要其他的集合操作(比如union和intersection),这不是一个很好的解决方案。从打字的角度来看,它绝对不是很优雅。
答案 3 :(得分:2)
只需扩展原始类型,如图所示 http://www.ffconsultancy.com/ocaml/benefits/modules.html 对于List模块:
module StringSet = Set.Make (* define basic type *)
(struct
type t = string
let compare = Pervasives.compare
end)
module StringSet = struct (* extend type with more operations *)
include StringSet
let of_list l =
List.fold_left
(fun s e -> StringSet.add e s)
StringSet.empty l
end;;
答案 4 :(得分:0)
List.fold_right ( add ) l empty
因为我不期待大量的名单,而且我已经在扩展名单。
答案 5 :(得分:0)
使用核心库,您可以执行以下操作:
let list_to_set l =
List.fold l ~init:(Set.empty ~comparator:Comparator.Poly.comparator)
~f:Set.add |> Set.to_list
例如:
list_to_set [4;6;3;6;3;4;3;8;2]
-> [2; 3; 4; 6; 8]
或者:
list_to_set ["d";"g";"d";"a"]
-> ["a"; "d"; "g"]