在 F# 中有效地投影列表列表 [英] Projecting a list of lists efficiently in F#

查看:22
本文介绍了在 F# 中有效地投影列表列表的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我必须对列表列表进行投影,该列表返回每个列表中每个元素的所有组合.例如:

I have to do projection of a list of lists which returns all combinations with each element from each list. For example:

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 多秒才能完成.此外,我认为该解决方案是不自然的,因为我必须以两种不同的方式分解同一个列表.对于如何提高函数的性能和可读性有什么建议吗?

The function is quite slow now, with N = 10 it takes more than 10 seconds to complete. Moreover, I think the solution is unnatural because I have to breakdown the same list in two different ways. Any suggestion how I can improve performance and readability of the function?

推荐答案

首先,尽可能避免列表串联 (@),因为它是 O(N) 而不是 O(1) 前置.

First of all, try to avoid list concatenation (@) whenever possible, since it's O(N) instead of O(1) prepend.

我将从一个(相对)易于遵循的计划开始,该计划如何计算列表的笛卡尔外积.

I'd start with a (relatively) easy to follow plan of how to compute the cartesian outer product of lists.

  • 将第一个列表的每个元素添加到其余列表的笛卡尔积中的每个子列表之前.
  • 注意基本情况.

第一个版本:

let rec cartesian = function
  | [] -> [[]]
  | L::Ls -> [for C in cartesian Ls do yield! [for x in L do yield x::C]]

这是将上面的句子直接翻译成代码.

This is the direct translation of the sentences above to code.

现在加快速度:使用列表串联和映射代替列表推导式:

Now speed this up: instead of list comprehensions, use list concatenations and maps:

let rec cartesian2 = function
  | [] -> [[]]
  | L::Ls -> cartesian2 Ls |> List.collect (fun C -> L |> List.map (fun x->x::C))

这可以通过一个序列按需计算列表来加快:

This can be made faster still by computing the lists on demand via a sequence:

let rec cartesian3 = function
  | [] -> Seq.singleton []
  | L::Ls -> cartesian3 Ls |> Seq.collect (fun C -> L |> Seq.map (fun x->x::C))

最后一种形式是我自己使用的,因为我通常只需要迭代结果,而不是一次性获得所有结果.

This last form is what I use myself, since I most often just need to iterate over the results instead of having them all at once.

我机器上的一些基准测试:测试代码:

Some benchmarks on my machine: Test code:

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

这篇关于在 F# 中有效地投影列表列表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆