【问题标题】:Preferred way to create a Scala list创建 Scala 列表的首选方式
【发布时间】:2010-11-17 11:45:54
【问题描述】:

有几种方法可以在 Scala 中构造不可变列表(请参阅下面的人为示例代码)。您可以使用可变的 ListBuffer,创建一个 var 列表并对其进行修改,使用 tail recursive 方法,可能还有其他我不知道的方法。

本能地,我使用 ListBuffer,但我没有这样做的充分理由。是否存在用于创建列表的首选或惯用方法,或者是否存在一种方法优于另一种方法的最佳情况?

import scala.collection.mutable.ListBuffer

// THESE are all the same as: 0 to 3 toList.
def listTestA() ={
    var list:List[Int] = Nil

    for(i <- 0 to 3) 
        list = list ::: List(i)
    list
}


def listTestB() ={
    val list = new ListBuffer[Int]()

    for (i <- 0 to 3) 
        list += i
    list.toList
}


def listTestC() ={
    def _add(l:List[Int], i:Int):List[Int] = i match {
        case 3 => l ::: List(3)
        case _ => _add(l ::: List(i), i +1)
    }
    _add(Nil, 0)
}

【问题讨论】:

    标签: scala


    【解决方案1】:

    只是一个使用 collection.breakOut 的例子

    scala> val a : List[Int] = (for( x <- 1 to 10 ) yield x * 3)(collection.breakOut)
    a: List[Int] = List(3, 6, 9, 12, 15, 18, 21, 24, 27, 30)
    
    scala> val b : List[Int] = (1 to 10).map(_ * 3)(collection.breakOut)
    b: List[Int] = List(3, 6, 9, 12, 15, 18, 21, 24, 27, 30)
    

    【讨论】:

      【解决方案2】:

      注意:此答案是为旧版本的 Scala 编写的。

      Scala 集合类将从 Scala 2.8 开始重新设计,因此请准备好尽快改变创建列表的方式。

      创建列表的前向兼容方式是什么?我不知道,因为我还没有阅读 2.8 文档。

      A PDF document describing the proposed changes of the collection classes

      【讨论】:

      • 大部分变化都发生在内部实现的方式上,以及投影等高级事物中。您创建列表的方式不受影响。
      • 好的,很高兴知道。如果您使用 collection.jcl 包中的任何类,您也会受到影响。
      【解决方案3】:

      像这样使用List.tabulate

      List.tabulate(3)( x => 2*x )
      res: List(0, 2, 4)
      
      List.tabulate(3)( _ => Math.random )
      res: List(0.935455779102479, 0.6004888906328091, 0.3425278797788426)
      
      List.tabulate(3)( _ => (Math.random*10).toInt )
      res: List(8, 0, 7)
      

      【讨论】:

        【解决方案4】:

        作为一个新的 scala 开发人员,我编写了一个小测试来检查列表创建时间与上面建议的方法。它看起来像 (for ( p

        import java.util.Date
        object Listbm {
        
          final val listSize = 1048576
          final val iterationCounts = 5
          def getCurrentTime: BigInt = (new Date) getTime
        
          def createList[T] ( f : Int => T )( size : Int ): T = f ( size )
        
          // returns function time execution
          def experiment[T] ( f : Int => T ) ( iterations: Int ) ( size :Int ) : Int  = {
        
            val start_time = getCurrentTime
            for ( p <- 0 to iterations )  createList ( f ) ( size )
            return (getCurrentTime - start_time) toInt
        
          }
        
          def printResult ( f:  => Int ) : Unit = println ( "execution time " + f  )
        
          def main( args : Array[String] ) {
        
        
            args(0) match {
        
              case "for" =>  printResult ( experiment ( x => (for ( p <- ( 0 to x ) ) yield p) toList  ) ( iterationCounts ) ( listSize ) )
              case "range"  =>  printResult ( experiment ( x => ( 0 to x ) toList ) ( iterationCounts ) ( listSize ) )
              case "::" => printResult ( experiment ( x => ((0 to x) :\ List[Int]())(_ :: _) ) ( iterationCounts ) ( listSize ) )
              case _ => println ( "please use: for, range or ::\n")
            }
          }
        }
        

        【讨论】:

          【解决方案5】:

          您通常希望通过消除任何 var 来关注 Scala 中的不变性。 可读性对你的同胞来说仍然很重要,所以:

          试试:

          scala> val list = for(i <- 1 to 10) yield i
          list: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
          

          在大多数情况下,您甚至可能不需要转换为列表 :)

          索引序列将包含您需要的一切:

          也就是说,您现在可以处理那个 IndexedSeq:

          scala> list.foldLeft(0)(_+_)
          res0: Int = 55
          

          【讨论】:

          • 注: Vector 现在也是默认的 Seq 实现。
          【解决方案6】:

          对于简单的情况:

          val list = List(1,2,3) 
          

          :)

          【讨论】:

          • 不要忘记 cons 运算符! 1 :: 2 :: 3 :: 无
          【解决方案7】:

          我总是更喜欢 List,我在“理解”之前使用“折叠/减少”。但是,如果需要嵌套“折叠”,则首选“理解”。如果我无法使用“fold/reduce/for”完成任务,递归是最后的手段。

          所以对于你的例子,我会这样做:

          ((0 to 3) :\ List[Int]())(_ :: _)
          

          在我做之前:

          (for (x <- 0 to 3) yield x).toList
          

          注意:由于“_”的顺序,我在这里使用“foldRight(:\)”而不是“foldLeft(/:)”。对于不抛出 StackOverflowException 的版本,请改用“foldLeft”。

          【讨论】:

          • 我强烈反对;您喜欢的形式看起来就像线路噪音。
          • 我会吗?我第一次学习 Haskell 是在 1999 年,并且已经涉足 Scala 几年了。我认为折叠很棒,但如果在任何给定情况下应用折叠需要编写一个神秘的标点符号字符串,我会考虑另一种方法。
          • @Matt R:我同意。有过头这种事,这就是其中之一。
          • @WalterChang 我喜欢所有这些表情符号的外观。等一下,是密码吗? :P
          • 拨打((0 to 3) :\ List[Int]())(_ :: _)表情码公平吗?
          【解决方案8】:

          嗯.. 这些对我来说似乎太复杂了。我可以提议吗

          def listTestD = (0 to 3).toList
          

          def listTestE = for (i <- (0 to 3).toList) yield i
          

          【讨论】:

          • 感谢您的回答,但问题是您在不平凡的情况下会做什么。我在代码中添加了注释,解释它们都相当于 0 到 3 toList。
          • 糟糕,对不起!坦率地说,我从不使用 ListBuffer。
          【解决方案9】:

          ListBuffer 是一个可变列表,它具有恒定时间追加和恒定时间转换为List

          List 是不可变的,具有恒定时间前置和线性时间附加。

          如何构建列表取决于您将使用列表的算法以及获取元素以创建列表的顺序。

          例如,如果您以与将要使用它们的时间相反的顺序获取元素,那么您可以只使用 List 并做前置。您是否会使用尾递归函数 foldLeft 或其他东西来执行此操作并不真正相关。

          如果您按照使用它们的相同顺序获取元素,那么ListBuffer 很可能是更可取的选择,如果性能至关重要的话。

          但是,如果您不在关键路径上并且输入足够低,您可以稍后随时 reverse 列表,或者只是 foldRightreverse 输入,这是线性时间。

          不要做的是使用List 并附加到它。这将给您带来比仅在最后进行前置和反转更差的性能。

          【讨论】:

          • What you DON'T do is use a List and append to it 是因为创建了一个新列表吗?然而,使用前置操作不会创建新列表?
          • @KevinMeredith 是的。追加是 O(n),前置是 O(1)。
          • @pgoggijr 这不是真的。首先,任何地方都没有“变化”,因为它是不可变的。遍历是必需的,因为所有元素都必须被复制,所以最后一个元素的副本可以指向一个新元素而不是Nil。其次,prepend 没有任何类型的副本:创建一个指向现有列表的元素,仅此而已。
          猜你喜欢
          • 1970-01-01
          • 2011-11-29
          • 1970-01-01
          • 2018-10-21
          • 2012-05-24
          • 1970-01-01
          • 2023-03-16
          • 1970-01-01
          • 2011-04-30
          相关资源
          最近更新 更多