将 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 如何在后代排序中做到这一点? @TianyiLiang sortWith(_ < _)【参考方案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) &lt;= 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 中,: 几乎总是值和类型之间的分隔符(例外是 :&lt;:&gt;: 在类型参数上),所以 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]

Scala - 将列表转换为单个列表:List[List[A]] 到 List[A]

将 Scala 列表转换为元组?

将 C# 列表转换为 javascript 数组

转换对象数组以在javascript中进行排序?

在Scala中将元组转换为数组