如何在OCaml中交叉两个列表?

时间:2010-03-04 11:44:49

标签: list ocaml intersection

当我在OCaml中有两个列表时,例如

e1 = [3; 4; 5; 6; 7]

e2 = [1; 3; 5; 7; 9]

有没有一种有效的方法来获得这两个列表的交集? 即:

[3; 5; 7]

因为我不喜欢在列表e2中扫描列表e1中每个元素的每个元素,因此创建了一个大的订单n ^ 2。

7 个答案:

答案 0 :(得分:8)

正如Franck和Rémi所说,将列表转换为集合(来自stdlib模块集)需要n log(n),然后Sets提供了交集的线性实现。 Franck还提到了对列表进行排序的等效替代方法,然后以同步方式遍历它们。这些大致相同(顺便说一句,在这两种情况下,您都需要能够为列表中的元素提供总排序)。

如果交叉点是算法的一个重要部分,并且您希望它们在两组元素略有不同的情况下更快,则需要切换到可合并结构,例如帕特里夏树。请参阅http://www.lri.fr/~filliatr/ftp/ocaml/ds/中的文件pt*

如果你需要在所有情况下快速交叉,你可以使用哈希模式Patricia树。 Hash-consing有助于识别结构相同的子树,并通过比较便宜来帮助为以前的操作构建有效的缓存。

Patricia树不能使用任意类型作为键(通常它们以int作为键表示)。但是,您有时可以通过在创建时将每个要用作键的值编号来解决此限制。

答案 1 :(得分:5)

我的OCaml不是最好的,但我一起攻击了这个函数,它将与排序列表相交:

let rec intersect l1 l2 =
    match l1 with [] -> []
        | h1::t1 -> (
          match l2 with [] -> []
              | h2::t2 when h1 < h2 -> intersect t1 l2
              | h2::t2 when h1 > h2 -> intersect l1 t2
              | h2::t2 -> (
                match intersect t1 t2 with [] -> [h1]
                    | h3::t3 as l when h3 = h1 -> l
                    | h3::t3 as l -> h1::l
              )
        );;

应该在O(n + m)时间运行。基本上它会检查每个列表的第一个元素。如果它们相等,则将递归调用的结果存储到它们的尾部,然后检查存储结果的头部是否等于列表的头部。如果不是,则插入它,否则它是重复的并且忽略它。

如果它们不相等,它只会向较小的那个前进。

答案 2 :(得分:3)

我不知道OCaml(语法方面),但通常你可以用两种方式做到这一点:

  1. 如果您的语言支持Set-datastructure,则将两个列表转换为Sets并使用set-intersection操作。

  2. 更一般地说:对两个列表进行排序,然后扫描已排序的列表,这样可以更有效地查找重复列表。您可以使用n log(n)进行排序,然后可以在线性时间内找到重复项。

答案 3 :(得分:3)

正如@Frank建议的那样,你可以使用集来解决这个问题,虽然它不是有史以来最好的答案,但这里有一个简短的代码清单,展示了如何在OCaml中实现这一点:

module Int_set = Set.Make (struct
                             type t = int
                             let compare = compare
                           end);;

(* iters through a list to construct a set*)
let set_of_list = List.fold_left (fun acc x -> Int_set.add x acc) Int_set.empty;;

let e1 = [3; 4; 5; 6; 7];;
let e2 = [1; 3; 5; 7; 9];;

let s1 = set_of_list e1;;
let s2 = set_of_list e2;;

(*result*)
let s3 = Int_set.inter s1 s2;;


(*testing output*)
Int_set.iter (fun elt -> print_int elt;print_string "\n") s3;;

输出结果为:

3
5
7
- : unit = ()

答案 4 :(得分:1)

如果您的列表只包含有限大小的整数,那么O(n)中也有一个解决方案:

1。)在原始列表中创建一个大小为最大整数值加1的布尔数组(例如在您的示例中为“9 + 1”);将所有字段设置为false;

let m = Array.create 10 false

- &GT; [|false; false; false; false; false; false; false; false; false; false|]

2。)迭代第一个列表:对于遇到的每个元素,将布尔值设置为相应的偏移量为“true”;在你的例子中,这将产生

List.iter (fun x -> m.(x) <- true) e1

- &GT; [|false; false; false; true; true; true; true; true; false; false|]

3.。)过滤第二个列表,只保留数组中相应字段为真的那些元素

List.filter (fun x -> m.(x) = true) e2

- &GT; [3; 5; 7]

答案 5 :(得分:0)

我不认为我的解决方案是O(n),但是它很短,对于不受复杂性约束限制的人们可能很有趣(因为此答案是“ Ocaml相交列表”的第一个搜索结果之一“)

当交集不为空时,此函数返回true。换句话说,它检查两个列表是否共享元素,如果是,则为true,否则为false。

let intersect l1 l2 =
    List.fold_left (fun acc x -> if (List.exists (fun y -> y = x) l1) then true else acc) false l2;;

非常相似,此函数返回实际的交点。

let intersect l1 l2 =
    List.fold_left (fun acc x -> if (List.exists (fun y -> y = x) l1) then x::acc else acc) [] l2;;

如果我的解决方案不正确,请随时纠正我。它应该是多态的,因为x和y可以是任何可以比较的类型。

答案 6 :(得分:0)

正如之前的一位发帖人所说,这是“OCaml 中的相交列表”的第一个搜索结果之一,所以我在这里发布了一个简单的答案,并没有解决复杂性问题。

    # let e1 = [3; 4; 5; 6; 7];;
    # let e2 = [1; 3; 5; 7; 9];;
    # List.filter (fun x -> List.mem x e1) e2;;
    - : int list = [3; 5; 7]
    #