【问题标题】:Efficient way to convert Scala Array to Unique Sorted List将 Scala 数组转换为唯一排序列表的有效方法
【发布时间】: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))

既然性能很关键,有没有更好的办法?

谢谢。

【问题讨论】:

    标签: arrays list scala optimization sorting


    【解决方案1】:

    这条简单的代码是迄今为止最快的代码之一:

    someArray.toList.filter (_ > 0).sortWith (_ > _).distinct
    

    但到目前为止,明显的赢家是 - 由于我的测量 - Jed Wesley-Smith。也许如果 Rex 的代码是固定的,它看起来会有所不同。

    典型免责声明 1 + 2:

    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 是文字而不是变量,!= 是符号方法名称。
    猜你喜欢
    • 1970-01-01
    • 2022-06-10
    • 1970-01-01
    • 2021-02-09
    • 2020-04-27
    • 2015-12-22
    • 1970-01-01
    • 2021-06-01
    • 1970-01-01
    相关资源
    最近更新 更多