创建Scala列表的首选方法


116

有多种方法可以在Scala中构造不可变列表(请参见下面的伪造示例代码)。您可以使用可变的ListBuffer,创建var列表并对其进行修改,使用尾部递归方法以及其他我不知道的方法。

本能地,我使用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)
}

Answers:


108

ListBuffer是一个可变列表,具有固定时间追加,并且可以将恒定时间转换为List

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

构造列表的方式取决于使用列表的算法以及获取元素创建列表的顺序。

例如,如果您获得与要使用元素时相反的顺序,则可以只使用a List并进行前置。无论是使用尾递归函数foldLeft还是其他无关紧要的功能。

如果以与您使用它们相同的顺序获得元素,则ListBuffer在性能至关重要的情况下,a 是最可能的首选。

但是,如果你不是一个关键路径上和输入足够低,可以随时reverse列表后,或只是foldRight,或reverse输入,这是线性时间。

不要做的是使用a List并将其附加。这将使您的性能远不止最后添加和反转。


What you DON'T do is use a List and append to it那是因为创建了新列表吗?而使用前置操作将不会创建新列表?
凯文·梅雷迪斯

2
@KevinMeredith是的。追加为O(n),前置为O(1)。
Daniel C. Sobral

@pgoggijr这是不正确的。首先,任何地方都没有“改变”,因为它是不可变的。由于必须复制所有元素,因此需要进行遍历,这样可以使最后一个元素的副本指向而不是指向一个新元素Nil。其次,在前缀上没有任何形式的副本:创建了一个指向现有列表的元素,仅此而已。
Daniel C. Sobral


22

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

def listTestD = (0 to 3).toList

要么

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

感谢您的回答,但问题是在非平凡的情况下您会怎么做。我在代码中加了一条注释,说明它们都等效于0到3 toList。
09年

糟糕,抱歉!坦白说,我从不使用ListBuffer。
亚历山大·阿扎罗夫

5

您通常希望通过消除任何变量来关注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)

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

索引的seq将满足您的所有需求:

也就是说,您现在可以在该IndexedSeq上工作:

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

NB Vector现在也是默认Seq实现。
康纳·道尔

2

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

因此,以您为例,我将这样做:

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

在我做之前:

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

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


18
我非常不同意;您喜欢的形式看起来就像是线路噪音。
Matt R

14
可以吗 我于1999年首次学习Haskell,并在Scala涉足了两年。我认为折痕很棒,但是如果在任何给定情况下应用折痕都需要编写一个隐含的标点符号字符串,那么我会考虑采用另一种方法。
Matt R

11
@马特R:我同意。过大就是一件事情,这就是其中之一。
ryeguy

8
@WalterChang我喜欢所有这些表情符号的外观。等一下,那是代码吗?:P
David J.

4
称呼((0 to 3) :\ List[Int]())(_ :: _)表情是否公平?
David J.

2

这样使用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)

2

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

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

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

PDF文件,描述了收集类别的拟议变更


2
大多数更改是在内部实现事物的方式以及在预测等高级事物中。创建列表的方式不受影响。
马库斯·唐宁

好的,很高兴知道。如果在collection.jcl包中使用任何类,也会受到影响。
安德烈·拉斯洛

1

作为一个新的Scala开发人员,我编写了一个小型测试来检查上述建议方法的列表创建时间。看起来(对于(p <-(0至x))yield 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")
    }
  }
}

0

只是一个使用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)

0

要创建字符串列表,请使用以下命令:

val l = List("is", "am", "are", "if")

1
在回答这个古老(10年)的问题并且现有很多答案(9)时,最好解释一下为什么您的答案与所有​​其他答案都不同。实际上,您似乎不了解这个问题。
jwvh
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.