121

There are several ways to construct an immutable list in Scala (see contrived example code below). You can use a mutable ListBuffer, create a var list and modify it, use a tail recursive method, and probably others that I don't know about.

Instinctively, I use the ListBuffer, but I don't have a good reason for doing so. Is there a preferred or idiomatic method for creating a list, or are there situations that are best for one method over another?

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)
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
agilefall
  • 3,876
  • 3
  • 31
  • 27

9 Answers9

110

ListBuffer is a mutable list which has constant-time append, and constant-time conversion into a List.

List is immutable and has constant-time prepend and linear-time append.

How you construct your list depends on the algorithm you'll use the list for and the order in which you get the elements to create it.

For instance, if you get the elements in the opposite order of when they are going to be used, then you can just use a List and do prepends. Whether you'll do so with a tail-recursive function, foldLeft, or something else is not really relevant.

If you get the elements in the same order you use them, then a ListBuffer is most likely a preferable choice, if performance is critical.

But, if you are not on a critical path and the input is low enough, you can always reverse the list later, or just foldRight, or reverse the input, which is linear-time.

What you DON'T do is use a List and append to it. This will give you much worse performance than just prepending and reversing at the end.

Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
  • `What you DON'T do is use a List and append to it` Is that because a **new list** gets created? Whereas, using a prepend operation will not create a new list? – Kevin Meredith Aug 24 '15 at 00:49
  • 2
    @KevinMeredith Yes. Append is O(n), prepend is O(1). – Daniel C. Sobral Aug 31 '15 at 19:33
  • @pgoggijr That is not true. First, there's no "change" anywhere, because it's immutable. A traversal is required because all elements have to be copied, just so a copy of the last element can be made pointing to a new element instead of `Nil`. Second, there's no copy of any kind on prepend: an element is created pointing to the existing list, and that's it. – Daniel C. Sobral Feb 12 '16 at 22:57
65

And for simple cases:

val list = List(1,2,3) 

:)

Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
23

Uhmm.. these seem too complex to me. May I propose

def listTestD = (0 to 3).toList

or

def listTestE = for (i <- (0 to 3).toList) yield i
Alexander Azarov
  • 12,971
  • 2
  • 50
  • 54
5

You want to focus on immutability in Scala generally by eliminating any vars. Readability is still important for your fellow man so:

Try:

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)

You probably don't even need to convert to a list in most cases :)

The indexed seq will have everything you need:

That is, you can now work on that IndexedSeq:

scala> list.foldLeft(0)(_+_)
res0: Int = 55
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
JasonG
  • 5,794
  • 4
  • 39
  • 67
2

I always prefer List and I use "fold/reduce" before "for comprehension". However, "for comprehension" is preferred if nested "folds" are required. Recursion is the last resort if I can not accomplish the task using "fold/reduce/for".

so for your example, I will do:

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

before I do:

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

Note: I use "foldRight(:\)" instead of "foldLeft(/:)" here because of the order of "_"s. For a version that does not throw StackOverflowException, use "foldLeft" instead.

Walter Chang
  • 11,547
  • 2
  • 47
  • 36
  • 18
    I strongly disagree; your preferred form just looks like line noise. – Matt R Aug 07 '09 at 07:26
  • 1
    Well, all I can say is if you stick with Scala and functional programming long enough, you will learn to love it. – Walter Chang Aug 07 '09 at 08:03
  • 14
    Will I? I first learned Haskell in 1999, and have been dabbling in Scala for a couple of years. I think folds are great, but if applying a fold in any given situation requires writing a cryptic string of punctuation symbols, I'd consider a different approach. – Matt R Aug 07 '09 at 09:12
  • 11
    @Matt R: I agree. There is such a thing as overdoing it, and this is one of them. – ryeguy Sep 17 '09 at 14:21
  • 8
    @WalterChang I like the look of all those emoticons. Wait a minute, is that code? :P – David J. Sep 27 '11 at 02:44
  • 4
    Is it fair to call `((0 to 3) :\ List[Int]())(_ :: _)` emoticode? – David J. Sep 27 '11 at 02:44
  • 1
    I actually find this syntax easier to follow than a string of alphabet soup. It depends on your background in mathematics I think because those with a strong background in mathematics spend a lot of time looking at symbols anyway. If this is hard to follow then the Taylor–Proudman theorem, Navier–Stokes equations, or all of Maxwell's Equations are impossible to follow (simply not true). – Coder Guy Feb 24 '15 at 21:44
  • @JonathanNeufeld The question is not whether it's *possible* to follow this code. The goal is to be able to glance at a line of code you haven't seen before and discern its function quickly. – Rag Apr 28 '15 at 18:59
2

Note: This answer is written for an old version of Scala.

The Scala collection classes are going to be redesigned as of Scala 2.8, so be prepared to change the way you create lists very soon.

What is the forward compatible way of creating a List? I have no idea since I haven't read the 2.8 docs yet.

A PDF document describing the proposed changes of the collection classes

André Laszlo
  • 15,169
  • 3
  • 63
  • 81
  • 2
    Most of the changes are in the way things are implemented internally, and in advanced things like projections. How you create a list isn't affected. – Marcus Downing Aug 07 '09 at 08:30
  • Ok, that's good to know. You will also get affected if you use any class in the collection.jcl package. – André Laszlo Aug 07 '09 at 09:09
2

Using List.tabulate, like this,

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)
elm
  • 20,117
  • 14
  • 67
  • 113
1

As a new scala developer i wrote small test to check list creation time with suggested methods above. It looks like (for ( p <- ( 0 to x ) ) yield p) toList the fastest approach.

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")
    }
  }
}
Yuli Reiri
  • 591
  • 7
  • 18
0

just an example that uses 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)
Arne
  • 7,921
  • 9
  • 48
  • 66