如何有效地将列表分成2个,以保留元素的顺序?
这是输入和预期输出的示例
[] should produce ([],[])
[1;] can produce ([1;], []) or ([], [1;])
[1;2;3;4;] should produce ([1; 2;], [3; 4;])
[1;2;3;4;5;] can produce ([1;2;3;], [4;5;]) or ([1;2;], [3;4;5;])
我尝试了几件事,但是我不确定哪个是最有效的……也许有一种解决方案使我完全错过了(对C代码的调用不计在内)。
我的第一个尝试是使用List的分区功能,将ref引用为列表长度的1/2。这种方法有效,但是当您只需要覆盖一半时,您就可以遍历整个列表。
let split_list2 l =
let len = ref ((List.length l) / 2) in
List.partition (fun _ -> if !len = 0 then false else (len := !len - 1; true)) l
我的下一个尝试是使用一个累加器,然后反转它。这只会遍历列表的一半,但我会反向调用以更正累加器的顺序。
let split_list4 l =
let len = List.length l in
let rec split_list4_aux ln acc lst =
if ln < 1
then
(List.rev acc, lst)
else
match lst with
| [] -> failwith "Invalid split"
| hd::tl ->
split_list4_aux (ln - 1) (hd::acc) tl in
split_list4_aux (len / 2) [] l
我最后的尝试是使用累加器的函数闭包,并且它有效,但是我不知道闭包的效率如何。
let split_list3 l =
let len = List.length l in
let rec split_list3_aux ln func lst =
if ln < 1
then
(func [], lst)
else
match lst with
| hd::tl -> split_list3_aux (ln - 1) (fun t -> func (hd::t)) tl
| _ -> failwith "Invalid split" in
split_list3_aux (len / 2) (fun t -> t) l
那么,有没有一种最有效的方法来以OCaml(保留元素顺序)拆分列表?
答案 0 :(得分:1)
您需要遍历所有解决方案的整个列表。 List.length
函数遍历整个列表。但是,您以后的解决方案确实会重用原始列表的末尾,而不是构造新列表。
很难说仅仅通过检查就能确定给定的代码速度有多快。通常,最好以渐近的O(f(n))术语进行思考,然后通过时序测试(对实际数据)详细研究慢速函数。
您所有的答案看起来都是O(n),这是您可以做的最好的事情,因为您显然需要知道列表的长度才能得到答案。
您的split_list2
和split_list3
解决方案对我来说看起来很复杂,因此,我会(直观地)认为它们会变慢。闭包是相当复杂的数据结构,其中包含函数和可访问变量的环境。因此,构建一个对象并不是那么快。
您的split_list4
解决方案是我自己编写的代码。
如果您真的很在意计时,则应将解决方案安排在一些较长的清单上。请记住,在不同的系统上可能会获得不同的计时。
答案 1 :(得分:0)
不能放弃这个问题。我必须找到一种方法可以遍历此列表以创建保留顺序的拆分。.
怎么样?
let split lst =
let cnt = ref 0 in
let acc = ref ([], []) in
let rec split_aux c l =
match l with
| [] -> cnt := (c / 2)
| hd::tl ->
(
split_aux (c + 1) tl;
let (f, s) = (!acc) in
if c < (!cnt)
then
acc := ((hd::f), s)
else
acc := (f, hd::s)
)
in
split_aux 0 lst; !acc