如何解决在F#中有效地投影列表列表
| 我必须对列表列表进行投影,该列表将返回每个列表中每个元素的所有组合。例如:projection([[1]; [2; 3]]) = [[1; 2]; [1; 3]].
projection([[1]; [2; 3]; [4; 5]]) = [[1; 2; 4]; [1; 2; 5]; [1; 3; 4]; [1; 3; 5]].
我想出一个函数:
let projection lss0 =
let rec projectionUtil lss accs =
match lss with
| [] -> accs
| ls::lss\' -> projectionUtil lss\' (List.fold (fun accs\' l ->
accs\' @ List.map (fun acc -> acc @ [l]) accs)
[] ls)
match lss0 with
| [] -> []
| ls::lss\' ->
projectionUtil lss\' (List.map (fun l -> [l]) ls)
和一个测试用例:
#time \"on\";;
let N = 10
let fss0 = List.init N (fun i -> List.init (i+1) (fun j -> j+i*i+i));;
let fss1 = projection fss0;;
现在该功能相当慢,使用N = 10
会花费超过10秒的时间。而且,我认为解决方案是不自然的,因为我必须以两种不同的方式细分相同的列表。有什么建议可以改善功能的性能和可读性吗?
解决方法
首先,请尽量避免使用列表串联(@),因为它是O(N)而不是O(1)的前缀。
我将从一个(相对)容易遵循的计划开始,该计划是如何计算列表的笛卡尔乘积。
将第一个列表的每个元素放在其余列表的笛卡尔积的每个子列表之前。
照顾基本情况。
第一版:
let rec cartesian = function
| [] -> [[]]
| L::Ls -> [for C in cartesian Ls do yield! [for x in L do yield x::C]]
这是上面句子到代码的直接翻译。
现在加快速度:代替列表解析,使用列表串联和映射:
let rec cartesian2 = function
| [] -> [[]]
| L::Ls -> cartesian2 Ls |> List.collect (fun C -> L |> List.map (fun x->x::C))
通过按需计算序列,可以使速度更快:
let rec cartesian3 = function
| [] -> Seq.singleton []
| L::Ls -> cartesian3 Ls |> Seq.collect (fun C -> L |> Seq.map (fun x->x::C))
最后一种形式是我自己使用的形式,因为我通常只需要遍历结果,而不是一次拥有所有结果。
我的机器上的一些基准测试:
测试代码:
let test f N =
let fss0 = List.init N (fun i -> List.init (i+1) (fun j -> j+i*i+i))
f fss0 |> Seq.length
FSI结果:
> test projection 10;;
Real: 00:00:18.066,CPU: 00:00:18.062,GC gen0: 168,gen1: 157,gen2: 7
val it : int = 3628800
> test cartesian 10;;
Real: 00:00:19.822,CPU: 00:00:19.828,GC gen0: 244,gen1: 121,gen2: 3
val it : int = 3628800
> test cartesian2 10;;
Real: 00:00:09.247,CPU: 00:00:09.250,GC gen0: 94,gen1: 52,gen2: 2
val it : int = 3628800
> test cartesian3 10;;
Real: 00:00:04.254,CPU: 00:00:04.250,GC gen0: 359,gen1: 1,gen2: 0
val it : int = 3628800
, 该函数是Haskell的序列(尽管sequence
更通用)。转换为F#:
let sequence lss =
let k l ls = [ for x in l do for xs in ls -> x::xs ]
List.foldBack k lss [[]]
在互动中:
> test projection 10;;
Real: 00:00:12.240,CPU: 00:00:12.807,GC gen0: 163,gen1: 155,gen2: 4
val it : int = 3628800
> test sequence 10;;
Real: 00:00:06.038,CPU: 00:00:06.021,GC gen0: 75,gen1: 74,gen2: 0
val it : int = 3628800
总体思路:避免显式递归以支持标准组合器(折叠,地图等)
, 这是尾递归版本。它的速度不如其他解决方案快(仅比原始功能快25%),但是内存使用量是恒定的,因此适用于非常大的结果集。
let cartesian l =
let rec aux f = function
| [] -> f (Seq.singleton [])
| h::t -> aux (fun acc -> f (Seq.collect (fun x -> (Seq.map (fun y -> y::x) h)) acc)) t
aux id l
, 由于@(即List concat)操作很慢,因此实现速度很慢,并且以递归方式进行了很多次。 @变慢的原因是在函数编程中List是Linked列表,并且要连接2个list,您必须首先到达列表的末尾(一个一个地遍历元素),然后追加另一个list。
请在注释中查看建议的参考。希望这些对您有所帮助。
, let crossProduct listA listB listC listD listE =
listA |> Seq.collect (fun a ->
listB |> Seq.collect (fun b ->
listC |> Seq.collect (fun c ->
listD |> Seq.collect (fun d ->
listE |> Seq.map (fun e -> a,b,c,d,e))
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。