Scala - Collections

Updated: 2018-12-08
  • Range: immutable, IndexedSeq
  • List: immutable, LinearSeq
  • LinkedList: mutable, LinearSeq
  • Array: mutable, IndexedSeq
  • Vector: immutable, IndexedSeq

Comparisons

IndexedSeq vs LinearSeq

  • IndexedSeq: efficient for random access
  • LinearSeq: efficient for add/delete head/tail

Tuple vs List

  • Tuple: elements may have different types

  • List: same type

  • Tuple: cannot iterate

  • List: can iterate

  • Tuple: 1-based index

  • List: 0-based index

Tuple means a product between all of its elements, but a non-empty List is a product between its head and tail.

scala> List(1,2).productIterator.foreach(println)
1
List(2)

scala> Tuple2(1,2).productIterator.foreach(println)
1
2

Range vs List vs Array vs Vector vs ...

scala> (1 to 5).toList
res4: List[Int] = List(1, 2, 3, 4, 5)

scala> (1 to 5).toVector
res5: Vector[Int] = Vector(1, 2, 3, 4, 5)

scala> (1 to 5).toArray
res68: Array[Int] = Array(1, 2, 3, 4, 5)

scala> List.range(1,6)
res1: List[Int] = List(1, 2, 3, 4, 5)

immutable: always return a copy with changes

scala> val x = (1 to 5).toVector
x: Vector[Int] = Vector(1, 2, 3, 4, 5)

change a value in vector(the function is called updated, not update)

scala> x.updated(0, 100)
res13: scala.collection.immutable.Vector[Int] = Vector(100, 2, 3, 4, 5)

For a full list: https://docs.scala-lang.org/tutorials/FAQ/collections.html

Seq

3 Seq: collection.Seq, collection.mutable.Seq and collection.immutable.Seq, The immutable one is the "default".

  • ParSeq: parallel
  • GenSeq: parent to Seq and ParSeq

List

List is highly optimized by compiler and libraries, and it's a fundamental data type in functional programming.

scala> List("a", "b", "c").zipWithIndex
res0: List[(String, Int)] = List((a,0), (b,1), (c,2))

scala> List("a", "b", "c") zip (Stream from 1)
res1: List[(String, Int)] = List((a,1), (b,2), (c,3))

scala> (List("a", "b", "c") zip (Stream from 1)).map({case (x,y)=>(y,x)})
res2: List[(Int, String)] = List((1,a), (2,b), (3,c))

Create repeated elements

scala> Seq.fill(5)("a")
res1: Seq[String] = List(a, a, a, a, a)

Vector

A hybrid of List and Array.

Tuple

There's no Entry object as in Java, instead Scala uses Tuple2

scala> val t1 = 1 -> 2
t1: (Int, Int) = (1,2)

scala> val t2 = Tuple2(1,2)
t2: (Int, Int) = (1,2)

scala> t1 == t2
res1: Boolean = true

Since t1 and t2 are the same, you will only find one in the created Map; for the same key, the latter will override the existing value:

scala> Map(t1, t2, 3->4)
res12: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2, 3 -> 4)

scala> Map(t1, t2, 3->4, 3->5)
res13: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2, 3 -> 5)
scala> val info = (5, "abc", true)
info: (Int, String, Boolean) = (5,abc,true)

scala> val name = info._2
name: String = abc

create 2-sized tuple

scala> 1 -> 2
res13: (Int, Int) = (1,2)

Map

Mutable map

3 ways

scala> val map = scala.collection.mutable.Map[String,String]()
map: scala.collection.mutable.Map[String,String] = Map()


scala> map("k1") = "v1"

scala> map
res1: scala.collection.mutable.Map[String,String] = Map((k1,v1))

scala> map += "k2" -> "v2"
res2: map.type = Map((k1,v1), (k2,v2))

scala> map.put("k3","v3")
res3: Option[String] = None

scala> map
res4: scala.collection.mutable.Map[String,String] = Map((k3,v3), (k1,v1), (k2,v2))

Immutable map

var map

scala> var map = scala.collection.immutable.Map[String, String]()
map: scala.collection.immutable.Map[String,String] = Map()

scala> map += ("key4" -> "val4")

scala> map
res1: scala.collection.immutable.Map[String,String] = Map(key4 -> val4)

val map

Create a new map.

scala> val map = scala.collection.immutable.Map[String, String]()
map: scala.collection.immutable.Map[String,String] = Map()

scala> val map2 = map + ("key4" -> "val4")
map2: scala.collection.immutable.Map[String,String] = Map(key4 -> val4)

Range:

Create a Range by to

scala> 1 to 5
res1: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5)

Or use until

scala> 1 until 6
res47: scala.collection.immutable.Range = Range(1, 2, 3, 4, 5)

set step

scala> 1 to 5 by 2
res48: scala.collection.immutable.Range = Range(1, 3, 5)

NumericRange:

scala> val x = 1.0 to 5.0
x: Range.Partial[Double,scala.collection.immutable.NumericRange[Double]] = scala.collection.immutable.Range$Partial@29ad44e3

scala> val y = x by 0.5
y: scala.collection.immutable.NumericRange[Double] = NumericRange(1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0)

Inclusive vs Exclusive

until

scala> val y = 1 until 6
y: scala.collection.immutable.Range = Range(1, 2, 3, 4, 5)

scala> x.start
res8: Int = 1

scala> x.end
res9: Int = 5
scala> x.isInclusive
res11: Boolean = true

scala> y.start
res12: Int = 1

scala> y.end
res13: Int = 6

scala> y.isInclusive
res14: Boolean = false

Sequence Comprehension

scala> for (x <- 1 to 5) { println(x) }
1
2
3
4
5

scala> for (x <- 1 to 5 if x % 2 == 0) { println(x) }
2
4

Array to lookup map

scala> val a = Array("hello", "world", "foo", "bar")
a: Array[String] = Array(hello, world, foo, bar)

scala> val lookup = a.map(word => word -> word.length).toMap
lookup: scala.collection.immutable.Map[String,Int] = Map(hello -> 5, world -> 5, foo -> 3, bar -> 3)

scala> lookup("world")
res3: Int = 5