在二维列表中合并具有相同标题的列表(OCaml)

在二维列表中合并具有相同标题的列表(OCaml),ocaml,Ocaml,我正在使用OCaml中的列表列表,我正在尝试编写一个函数,将共享同一头部的所有列表组合在一起。这就是我目前所拥有的,我使用了List.hd内置函数,但毫不奇怪,我得到了失败“hd”错误: 例如,如果我有以下列表: [[Sentence; Quiet]; [Sentence; Grunt]; [Sentence; Shout]] 我想把它组合成: [[Sentence; Quiet; Grunt; Shout]] 我编写的函数uniq只是删除列表中的所有重复项。请让我知道我将如何完成这项工作。

我正在使用OCaml中的列表列表,我正在尝试编写一个函数,将共享同一头部的所有列表组合在一起。这就是我目前所拥有的,我使用了List.hd内置函数,但毫不奇怪,我得到了失败“hd”错误:

例如,如果我有以下列表:

[[Sentence; Quiet]; [Sentence; Grunt]; [Sentence; Shout]]
我想把它组合成:

[[Sentence; Quiet; Grunt; Shout]]

我编写的函数uniq只是删除列表中的所有重复项。请让我知道我将如何完成这项工作。提前谢谢

首先,我通常避免使用像
List.hd
这样的函数,因为模式加工通常更清晰,更不容易出错。在这种情况下,您的
if
可以替换为受保护的模式(模式后的
when
子句)。我认为造成错误的原因是当
t
[]
时,代码失败;保护模式通过使案例更显式来帮助避免这种情况。因此,当x=y作为
match
表达式中的子句时,可以执行
(x::xs)::(y::ys)::t检查列表前两个元素的头是否相同。在OCaml中,除了防护装置之外,具有几个相同的连续模式并不少见

进一步的事情:你不需要
[]@nlist
——这和写
nlist
是一样的

而且,它看起来像您的
nlist@h
和类似的表达式试图在将列表传递给递归调用之前连接列表;然而,在OCaml中,函数应用程序的绑定比任何操作符都要紧密,因此它实际上将递归调用的结果附加到
h


我没有这个函数的正确版本。但是我会先用保护模式来编写它,然后看看这能让你在多大程度上完成它。

考虑使用
映射或哈希表来跟踪头部以及为每个头部找到的元素。如果具有相同标题的列表不相邻,则
nlist
辅助列表没有多大帮助,如本例所示:

# combineSameHead [["A"; "a0"; "a1"]; ["B"; "b0"]; ["A"; "a2"]]
- : list (list string) = [["A"; "a0"; "a1"; "a2"]; ["B"; "b0"]]

您想要的操作有一个简单的递归描述:递归地处理列表的尾部,然后使用head执行“插入”操作,该操作查找以相同head开头的列表,如果找到,则插入除head之外的所有元素,并在末尾附加它。然后,您可以反转结果以获得预期的列表列表

在OCaml中,此算法如下所示:

let process list = 
  let rec insert (head,tail) = function
    | [] -> head :: tail 
    | h :: t -> 
      match h with 
      | hh :: tt when hh = head -> (hh :: (tail @ t)) :: t 
      | _ -> h :: insert (head,tail) t
  in
  let rec aux = function 
    | [] -> []
    | [] :: t -> aux t
    | (head :: tail) :: t -> insert (head,tail) (aux t) 
  in
  List.rev (aux list)

我可能会按照安东纳科斯的建议做点什么。这将完全避免在列表中搜索的O(n)开销。您还可能发现,使用
StringSet.tstringmap.t
进行进一步处理更容易。当然,可读性是最重要的,我仍然认为这符合这一标准

module OrderedString =
    struct
        type t = string
        let compare = Pervasives.compare
    end

module StringMap = Map.Make (OrderedString)
module StringSet = Set.Make (OrderedString)

let merge_same_heads lsts =
    let add_single map = function
        | hd::tl when StringMap.mem hd map ->
            let set = StringMap.find hd map in
            let set = List.fold_right StringSet.add tl set in
            StringMap.add hd set map
        | hd::tl ->
            let set = List.fold_right StringSet.add tl StringSet.empty in
            StringMap.add hd set map
        | []     ->
            map
    in
    let map = List.fold_left add_single StringMap.empty lsts in
    StringMap.fold (fun k v acc-> (k::(StringSet.elements v))::acc) map []

仅使用标准库,您就可以做很多事情:

(* compares the head of a list to a supplied value.  Used to partition a lists of lists *)
let partPred x = function h::_ -> h = x
  | _ -> false

let rec combineHeads = function [] -> []
  | []::t -> combineHeads t (* skip empty lists *)
  | (hh::_ as h)::t -> let r, l = List.partition (partPred hh) t in (* split into lists with the same head as the first, and lists with different heads *)
  (List.fold_left (fun x y -> x @ (List.tl y)) h r)::(combineHeads l) (* combine all the lists with the same head, then recurse on the remaining lists *)

combineHeads [[1;2;3];[1;4;5;];[2;3;4];[1];[1;5;7];[2;5];[3;4;6]];;
- : int list list = [[1; 2; 3; 4; 5; 5; 7]; [2; 3; 4; 5]; [3; 4; 6]]
但这不会很快(分区、左折叠和concat都是O(n))

(* compares the head of a list to a supplied value.  Used to partition a lists of lists *)
let partPred x = function h::_ -> h = x
  | _ -> false

let rec combineHeads = function [] -> []
  | []::t -> combineHeads t (* skip empty lists *)
  | (hh::_ as h)::t -> let r, l = List.partition (partPred hh) t in (* split into lists with the same head as the first, and lists with different heads *)
  (List.fold_left (fun x y -> x @ (List.tl y)) h r)::(combineHeads l) (* combine all the lists with the same head, then recurse on the remaining lists *)

combineHeads [[1;2;3];[1;4;5;];[2;3;4];[1];[1;5;7];[2;5];[3;4;6]];;
- : int list list = [[1; 2; 3; 4; 5; 5; 7]; [2; 3; 4; 5]; [3; 4; 6]]