使用索引

时间:2015-07-24 17:07:00

标签: list split ocaml

我有一个名为t的整数列表,其长度为n = List.length t。我想得到两个列表,t从索引0到(n / 2 - 1)的分区,以及从索引(n / 2)到(n-1)的t分区。换句话说,我想将列表t拆分为两部分,但我找不到任何在List模块中执行此操作的内容(有List.filter,但它不按索引进行过滤,它需要一个函数而不是。)

我想做的一个例子:

let t = [8 ; 66 ; 4 ; 1 ; -2 ; 6  ; 4 ; 1] in
(* Split it to get t1 = [ 8 ; 66 ; 4 ; 1] and t2 = [-2 ; 6 ; 4 ; 1] *)

现在,我有类似的东西

let rec split t1 t2 n =
match t1 with
| hd :: tl when (List.length tl > n) -> split tl (hd :: t2) n;
| hd :: tl when (List.length tl = n) -> (t1,t2);
| _ -> raise (Failure "Unexpected error");;
let a = [1;2;3;4;7;8];;
let b,c = split a [] (List.length a / 2 - 1);;
List.iter (fun x -> print_int x) b;
print_char '|';
List.iter (fun x -> print_int x) c;

输出是: 478|321,订单已被撤销!

1 个答案:

答案 0 :(得分:4)

计算列表的长度需要遍历列表,因此在列表的长度上需要时间线性。您的尝试计算每一步的剩余列表的长度,这使得总运行时间呈二次方式。但实际上你不需要这样做!首先计算列表的总长度。在那之后,剪切的地方是从开始的一半,您可以通过在列表中递增计数器来找到它。

至于逆转,让我们来看看列表的第一个元素会发生什么。在第一次调用split时,累加器t2是空列表,因此h放在列表的末尾。下一个元素将放在该元素之前,依此类推。您需要将第一个元素放在列表的头部,因此将它添加到递归调用构建的列表中。

let rec split_at1 n l =
  if n = 0 then ([], l) else
  match l with
  | [] -> ([], [])    (*or raise an exception, as you wish*)
  | h :: t -> let (l1, l2) = split_at1 (n-1) t in (h :: l1, l2);;
let split_half1 l = split_at1 (List.length l / 2) l;;

这在线性时间内运行。这种实现的潜在缺点是它所做的递归调用不是尾调用,因此它会在大型列表上消耗大量的堆栈。您可以通过将前半部分构建为传递给函数的累加器来解决此问题。如上所述,这会以相反的顺序创建一个列表。所以最后扭转它。这是使用列表时常见的习语。

let rec split_at2 n acc l =
  if n = 0 then (List.rev acc, l) else
  match l with
  | [] -> (List.rev acc, [])
  | h :: t -> split_at2 (n-1) (h :: acc) t;;
let split_half2 l = split_at2 (List.length l / 2) [] l;;