将 Scala 数组转换为唯一排序列表的有效方法
Posted
技术标签:
【中文标题】将 Scala 数组转换为唯一排序列表的有效方法【英文标题】:Efficient way to convert Scala Array to Unique Sorted List 【发布时间】:2011-12-30 21:29:57 【问题描述】:任何人都可以在 Scala 中优化以下语句:
// maybe large
val someArray = Array(9, 1, 6, 2, 1, 9, 4, 5, 1, 6, 5, 0, 6)
// output a sorted list which contains unique element from the array without 0
val newList=(someArray filter (_>0)).toList.distinct.sort((e1, e2) => (e1 > e2))
既然性能很关键,有没有更好的办法?
谢谢。
【问题讨论】:
【参考方案1】:这条简单的代码是迄今为止最快的代码之一:
someArray.toList.filter (_ > 0).sortWith (_ > _).distinct
但到目前为止,明显的赢家是 - 由于我的测量 - Jed Wesley-Smith。也许如果 Rex 的代码是固定的,它看起来会有所不同。
典型免责声明 1 + 2:
-
我修改了代码以接受一个数组并返回一个列表。
典型的基准注意事项:
这是随机数据,平均分布。对于 100 万个元素,我创建了一个 0 到 100 万之间的 100 万个整数数组。因此,或多或少的零,或多或少的重复,它可能会有所不同。
可能取决于机器等。我使用的是单核 CPU,Intel-Linux-32bit,jdk-1.6,scala 2.9.0.1
这是用于生成图形 (gnuplot) 的底层 benchcoat-code and the concrete code 。 Y 轴:以秒为单位的时间。 X 轴:Array 中 100 000 到 1 000 000 个元素。
更新:
在发现 Rex 代码的问题后,他的代码和 Jed 的代码一样快,但最后一个操作是将他的 Array 转换为 List(以填充我的基准接口)。使用var result = List [Int]
和result = someArray (i) :: result
可以加快他的代码速度,因此它的速度大约是 Jed-Code 的两倍。
另一个可能很有趣的发现是:如果我按照过滤器/排序/不同 (fsd) => (dsf, dfs, fsd, ...) 的顺序重新排列我的代码,所有 6 种可能性都没有显着差异.
【讨论】:
+1 - 在回答性能问题时,基准非常有用! 感谢您的工作。这就解释了一切。这是否表明 Scala 更愿意处理函数式编程风格? 如果您想看到差异,您需要有大量的负值或零值。如果它们是总数的一半,那么首先过滤或区分将胜过排序。 (我认为 filter-first 会是最好的,但我不记得 distinct 的实现,所以我不肯定。) @RexKerr:你是对的。如果我的生成函数创建了 N 个值 random.nextInt (N/10),但其中大约 15% 为 0,则 ufs (unique-filter-sort) 和 usf 的性能比 suf/sfu 好得多。哦,我现在的图表太多了,我不能在这里全部显示! :)【参考方案2】:我没有测量,但我和邓肯在一起,排序然后使用类似的东西:
util.Sorting.quickSort(array)
array.foldRight(List.empty[Int])
case (a, b) =>
if (!b.isEmpty && b(0) == a)
b
else
a :: b
理论上这应该是相当有效的。
【讨论】:
谢谢你,杰德。它似乎是使用 Scala 风格的最佳版本。【参考方案3】:如果没有基准测试,我无法确定,但我认为以下方法非常有效:
val list = collection.SortedSet(someArray.filter(_>0) :_*).toList
还可以尝试在您的版本中的 someArray 之后添加 .par
。它不能保证更快,它可能是。您应该运行基准测试和实验。
sort
已弃用。请改用.sortWith(_ > _)
。
【讨论】:
+1 我喜欢简洁,但您还需要在其中添加一个过滤器。 @LuigiPlinge 如何在后代排序中做到这一点? @TianyiLiangsortWith(_ < _)
【参考方案4】:
装箱原语会给您带来 10-30 倍的性能损失。因此,如果你真的性能有限,你会想要处理原始原始数组:
def arrayDistinctInts(someArray: Array[Int]) =
java.util.Arrays.sort(someArray)
var overzero = 0
var ndiff = 0
var last = 0
var i = 0
while (i < someArray.length)
if (someArray(i)<=0) overzero = i+1
else if (someArray(i)>last)
last = someArray(i)
ndiff += 1
i += 1
val result = new Array[Int](ndiff)
var j = 0
i = overzero
last = 0
while (i < someArray.length)
if (someArray(i) > last)
result(j) = someArray(i)
last = someArray(i)
j += 1
i += 1
result
如果你小心点,你会得到比这稍微好一点的(请注意,我在脑海中输入了这个;我可能打错了一些东西,但这是要使用的样式),但如果你发现现有版本太慢了,这应该至少快 5 倍,甚至可能更多。
编辑(除了修复之前的代码,使其实际工作):
如果您坚持以列表结尾,那么您可以随时构建列表。您可以递归地执行此操作,但我认为在这种情况下它不会比迭代版本更清晰,所以:
def listDistinctInts(someArray: Array[Int]): List[Int] =
if (someArray.length == 0 || someArray(someArray.length-1) <= 0) List[Int]()
else
java.util.Arrays.sort(someArray)
var last = someArray(someArray.length-1)
var list = last :: Nil
var i = someArray.length-2
while (i >= 0)
if (someArray(i) < last)
last = someArray(i)
if (last <= 0) return list;
list = last :: list
i -= 1
list
此外,如果您不能通过排序破坏原始数组,那么您最好复制数组并破坏副本(基元的数组副本非常快)。
请记住,根据数据的性质,有些特殊情况的解决方案速度要快得多。例如,如果您知道自己有一个长数组,但数字的范围很小(例如 -100 到 100),那么您可以使用 bitset 来跟踪您遇到了哪些。
【讨论】:
ArrayIndexOOB in second while:result(j) = someArray(i)
我想我发现了 2 个错误:if (someArray (i) <= 0) overzero = i
第一次必须以 `+= 1` 结尾,而第二次则缺少 last = someArray (i)
。在我个人对 Jed 的解决方案(包含多达 600 万个元素)进行的基准测试中,您在 10 个案例中赢了 9 个,但使用了更多内存,但是为了参加我的比赛,我不得不将结果与 toList 转换为最后的 List。从一开始就使用列表有很大帮助(数组中的 8M 元素需要 5 秒对 10 秒)。
@userunknown - 谢谢!这就是为什么我不应该尝试在没有 REPL 的情况下输入超过几行代码的原因。
但是您知道 SimplyScala 和 IDEONE! :)
@userunknown - 确实如此。我通常使用我的个人库编写东西,然后将它们剥离出来作为示例,但在这种情况下不是这样,这样就可以了。【参考方案5】:
为了效率,看你的大值:
val a = someArray.toSet.filter(_>0).toArray
java.util.Arrays.sort(a) // quicksort, mutable data structures bad :-)
res15: Array[Int] = Array(1, 2, 4, 5, 6, 9)
请注意,这是使用 qsort 对未装箱数组进行排序。
【讨论】:
List#sortWith 在幕后使用了同样的方法(查看SeqLike
source)
我同意。我相当肯定排序会在对象引用上完成,而不是像我的版本那样的原语。【参考方案6】:
我无法衡量,但还有一些建议......
在转换为列表之前对数组进行适当的排序可能会更有效,您可能会考虑手动从排序列表中删除 dup,因为它们将组合在一起。在排序之前或之后删除 0 的成本也取决于它们与其他条目的比率。
【讨论】:
我无法在转换之前进行排序,因为该数组将用于大程序中的另一次迭代,除非它生成一个新数组。 @TianyiLiang 在这种情况下,只需使用Array#copy
首先复制数组。这是一个非常快速的操作。
@LuigiPlinge Array.clone 怎么样?它是否具有与 Array 复制相同的性能。对了,你对问题8173329有什么建议吗。
@TianyiLiang 如果没有我会很惊讶,但我不知道
视频处理让我吃惊的一件事是,在事情明显变慢之前,您可以复制几兆字节的缓冲区。【参考方案7】:
将所有内容添加到排序集中怎么样?
val a = scala.collection.immutable.SortedSet(someArray filter (0 !=): _*)
当然,您应该对代码进行基准测试,以检查什么更快,更重要的是,这确实是一个热点。
【讨论】:
这是什么意思:(0 !=): _*
?
@nedim 在 Scala 中,:
几乎总是值和类型之间的分隔符(例外是 :
、<:
和 >:
在类型参数上),所以 someArray filter (0 !=)
是一个值,_*
是一个类型。 _*
类型用于告诉 Scala 将序列作为多个参数传递给具有可变数量参数的方法。所以Set(Seq(1, 2, 3))
是一个包含一个Seq[Int]
类型元素的集合,Set(Seq(1, 2, 3): _*)
是一个包含三个Int
类型元素的集合。 (0 !=)
是 !=
应用于对象 0
的方法。
@daniel-c-sobral 哇,我知道 Scala 喜欢吹嘘它很简洁,但是这个 sn-p 真的很棒!我了解_?
现在是如何工作的(这种类型/结构的名称是什么?)。它看起来类似于我在 Python 中看到的带有参数(取消)打包的内容,只是它是由类型转换执行的。很酷,我错过了 Python 中的那个。但是,它仍然让我感到困惑 (0 !=)
在没有下划线的情况下如何工作,即 (0 != _)
。我仍在学习 Scala,我对此感到非常兴奋。
@nedim 它的工作原理与 Java 8 中的差不多。如果你在 Java 8 上写 x::m
,你指的是对象 m
上的方法 x
。在 Scala 中,因为它没有搞乱一切的静态方法,所以你只使用.
。如果我写somevar.equals
,我指的是somevar
上的方法equals
。这几乎是一回事,除了 0
是文字而不是变量,!=
是符号方法名称。以上是关于将 Scala 数组转换为唯一排序列表的有效方法的主要内容,如果未能解决你的问题,请参考以下文章
Scala - 将字符串数组转换为Set [PhoneNumbers]