从无序的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:我相信如果我可以计算出差异并且一分为二,那么它应该会提高性能,但我不知道如何做到这一点,一个想法?
提前致谢
答案 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.pairwise
和Seq.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
表达式时的完全相同的。解释匹配表达式会使这个答案太长,所以如果你还没有读过它,我会给你一个很好的参考:
答案 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