F#列表优化

时间:2017-08-03 20:51:05

标签: performance f#

从无序的int列表中,我希望两个元素之间的差异最小。我有一个正在运行但速度慢的代码。任何人都可以采取一些改变以改善性能吗?请解释为什么您进行了更改以及性能提升的效果。

let allInt = [ 5; 8; 9 ]

let sortedList = allInt |> List.sort;

let differenceList = [ for a in 0 .. N-2 do yield sortedList.Item a - sortedList.Item a + 1 ]

printfn "%i" (List.min differenceList) // print 1 (because 9-8 smallest difference)

我认为我正在做很多列表创建或迭代,但我不知道如何在F#中以不同方式编写它...

编辑:我正在列表上测试此代码,包含10万个或更多项目。

编辑2:我相信如果我可以计算出差异并且一分为二,那么它应该会提高性能,但我不知道如何做到这一点,一个想法?

提前致谢

4 个答案:

答案 0 :(得分:6)

F#的内置列表类型是作为链表实现的,这意味着按索引访问元素必须每次都将列表一直枚举到索引。在你的情况下,你有两次索引访问重复N-2次,每次迭代越来越慢,因为索引增长,每次访问需要经过列表的较长部分。

第一种方法是使用数组而不是列表,这是一个微不足道的变化,但可以让您更快地获得索引。

(*
    [| and |] let you define an array literal,
    alternatively use List.toArray allInt
*)
let allInt = [| 5; 8; 9 |] 
let sortedArray = allInt |> Array.sort;
let differenceList = [ for a in 0 .. N-2 do yield sortedArray.[a] - sortedArray.[a + 1] ]

另一种方法可能是将列表中的邻居配对,减去它们然后找到最小值。

let differenceList =
    sortedList
    |> List.pairwise
    |> List.map (fun (x,y) -> x - y)

List.pairwise获取元素列表并返回相邻对的列表。例如。在您的示例List.pairwise [ 5; 8; 9 ] = [ (5, 8); (8, 9) ]中,以便您可以在下一步中轻松使用对,即减法映射。

这种方式更好,但是List模块中的这些函数将列表作为输入并生成一个新列表作为输出,必须通过列表3次(pairwise为1,{{1为1最后为map添加1)。要解决此问题,您可以使用min模块中的函数,这些函数与.NET Seq接口一起使用,允许延迟评估,通常会减少传递。

幸运的是,在这种情况下IEnumerable<'a>为我们在这里使用的所有函数定义了替代方案,因此下一步是微不足道的:

Seq

这应该只需要列表的一个枚举(当然不包括排序阶段)。

但我不能保证哪种方法最快。我打赌只是使用数组而不是列表,但要找出答案,你必须尝试一下,自己测量数据和硬件。 BehchmarkDotNet库可以帮助您。

答案 1 :(得分:6)

List.Item在O(n)时间内执行,可能是代码中的主要性能瓶颈。 sortedList的评估通过索引迭代sortedList的元素,这意味着性能在O((N-2)(2 (N-2)))附近,这简化为O(N ^ 2),其中N是let data = [ let rnd = System.Random() for i in 1..100000 do yield rnd.Next() ] #time let result = data |> List.sort |> List.pairwise // convert list from [a;b;c;...] to [(a,b); (b,c); ...] |> List.map (fun (a,b) -> a - b |> abs) // Calculates the absolute difference |> List.min #time 中元素的数量。对于长列表,这最终会表现不佳。

我要做的是取消对Item的调用,而是使用List.pairwise操作

--> Timing now on

Real: 00:00:00.029, CPU: 00:00:00.031, GC gen0: 1, gen1: 1, gen2: 0
val result : int = 0

--> Timing now off

#time指令允许我测量F#Interactive中的执行时间,运行此代码时得到的输出是:

>>> le = preprocessing.LabelEncoder()
>>> le.fit(train["capital city"])
LabelEncoder()
>>> list(le.classes_)
['amsterdam', 'paris', 'tokyo']
>>> le.transform(["tokyo", "tokyo", "paris"])
array([2, 2, 1])
>>> list(le.inverse_transform([2, 2, 1]))
['tokyo', 'tokyo', 'paris']

答案 2 :(得分:4)

其他问题已被其他答案充分涵盖,因此我不会复制它们。但是,还没有人解决您在编辑2 中提出的问题。要回答这个问题,如果您正在进行计算,然后想要计算的最小结果,那么您需要List.minBy。您想要List.minBy的一个线索就是当您发现自己正在执行map后执行min操作时(正如其他答案正在做的那样):这是您的经典标志想要minBy,它在一次操作中而不是两次操作。

使用List.minBy时需要注意的是:它返回原始值,而不是计算的结果。即,如果你ints |> List.pairwise |> List.minBy (fun (a,b) -> abs (a - b)),那么List.minBy将要返回的是项目的,而不是差异。它是这样编写的,因为如果它给你原始值,但你真的想要结果,你总是可以重新计算结果;但是如果它给你结果并且你真的想要原始值,你可能无法得到它。 (8和9之间的差异是1的差异,还是4到5之间的差异?)

所以在你的情况下,你可以这样做:

let allInt = [5; 8; 9]
let minPair =
    allInt
    |> List.pairwise
    |> List.minBy (fun (x,y) -> abs (x - y))
let a, b = minPair
let minDifference = abs (a - b)
printfn "The difference between %d and %d was %d" a b minDifference

List.minBy操作也存在于序列中,因此如果您的列表足够大以至于您想避免创建对的中间列表,请改用Seq.pairwiseSeq.minBy:< / p>

let allInt = [5; 8; 9]
let minPair =
    allInt
    |> Seq.pairwise
    |> Seq.minBy (fun (x,y) -> abs (x - y))
let a, b = minPair
let minDifference = abs (a - b)
printfn "The difference between %d and %d was %d" a b minDifference

编辑:是的,我看到您已获得100,000件商品的清单。所以你肯定想要Seq版本。 F#seq类型只是IEnumerable,因此,如果您习惯使用C#,请将Seq函数视为LINQ表达式,并且您有正确的想法。

P.S。有一点需要注意:看看我是如何做let a, b = minPair的?那被称为解构分配,它真的很有用。我也可以这样做:

let a, b =
    allInt
    |> Seq.pairwise
    |> Seq.minBy (fun (x,y) -> abs (x - y))

它会给我相同的结果。 Seq.minBy返回一个包含两个整数的元组,let a, b = (tuple of two integers)表达式获取该元组,将其与模式a, b匹配,从而赋予a以获得该元组的值#39; s第一项,b具有该元组第二项的值。注意我是如何使用短语&#34;将它与模式匹配&#34;:这是与使用match表达式时的完全相同的。解释匹配表达式会使这个答案太长,所以如果你还没有读过它,我会给你一个很好的参考:

https://fsharpforfunandprofit.com/posts/match-expression/

答案 3 :(得分:0)

这是我的解决方案:

let minPair xs =
    let foo (x, y) = abs (x - y)
    xs
    |> List.allPairs xs
    |> List.filter (fun (x, y) -> x <> y)
    |> List.minBy foo
    |> foo