在2D列表中组合具有相同头的列表(OCaml)

时间:2011-01-17 02:08:05

标签: ocaml

我正在使用OCaml中的列表列表,并且我正在尝试编写一个功能,该功能组合了共享同一个头的所有列表。这就是我到目前为止所做的,并且我使用了List.hd内置函数,但不出意外,我得到了失败" hd"错误:

let rec combineSameHead list nlist = match list with
 | [] -> []@nlist
 | h::t -> if List.hd h = List.hd (List.hd t)
    then combineSameHead t nlist@uniq(h@(List.hd t))
    else combineSameHead t nlist@h;;

例如,如果我有这个列表:

[[Sentence; Quiet]; [Sentence; Grunt]; [Sentence; Shout]]

我想把它合并到:

[[Sentence; Quiet; Grunt; Shout]]

我写的函数uniq只删除列表中的所有重复项。请告诉我如何完成此操作。提前谢谢!

5 个答案:

答案 0 :(得分:3)

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

其他事项:您不需要[]@nlist - 这与撰写nlist一样。

此外,看起来你的nlist@h和类似的表达式试图在将列表传递给递归调用之前连接列表;但是,在OCaml中,函数应用程序比任何运算符都绑定得更紧密,因此它实际上将递归调用的结果附加到h

我没有,副手,有一个正确版本的功能。但是我会先用保护模式编写它,然后看看你能用它做多远。

答案 1 :(得分:2)

您的预期操作有一个简单的递归描述:递归处理列表的尾部,然后执行“插入”操作,头部查找以相同头部开头的列表,如果找到,则插入所有元素但是头部,并在最后附加。然后,您可以反转结果以获得预期的列表清单。

在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)

答案 2 :(得分:0)

考虑使用Map或哈希表来跟踪每个头部的头部和元素。如果具有相同磁头的列表不相邻,则nlist辅助列表不是很有用,如下例所示:

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

答案 3 :(得分:0)

我可能会按照antonakos的建议做一些事情。这将完全避免在列表中搜索的O(n)成本。您还可能会发现使用StringSet.t StringMap.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 []

答案 4 :(得分:0)

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

(* 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]]

然而,这不会很快(分区,fold_left和concat都是O(n))。