At the top of the collection hierarchy is the trait Traversable
. Its only abstract operation is foreach
:
def foreach[U](f: Elem => U)
Collection classes that implement Traversable
just need to define this method; all other methods can be inherited from Traversable
.
The foreach
method is meant to traverse all elements of the collection, and apply the given operation, f
, to each element. The type of the operation is Elem => U
, where Elem
is the type of the collection's elements and U
is an arbitrary result type. The invocation of f
is done for its side effect only; in fact any function result of f
is discarded by foreach
.
Traversables are the superclass of List
, Array
, Map
, Set
, Stream
and more. The methods involved can be applied to each other in a different type.
++
appends two Traversable
s together. The resulting Traversable
is the same type of the first element.
val set = Set(1, 9, 10, 22)
val list = List(3, 4, 5, 10)
val result = set ++ list
result.size should be(res0)
val result2 = list ++ set
result2.size should be(res1)
map
will apply the given function on all elements of a Traversable
and return a new collection of the result:
val set = Set(1, 3, 4, 6)
val result = set.map(_ * 4)
result.lastOption should be(res0)
flatten
will "pack" all child Traversables
into a single Traversable
:
val list = List(List(1), List(2, 3, 4), List(5, 6, 7), List(8, 9, 10))
list.flatten should be(res0)
flatMap
will not only apply the given function on all elements of a Traversable
, but all elements within the elements and flatten
the results:
val list = List(List(1), List(2, 3, 4), List(5, 6, 7), List(8, 9, 10))
val result = list.flatMap(_.map(_ * 4))
result should be(res0)
flatMap
of Option
s will filter out all None
s but keep the Some
s:
val list = List(1, 2, 3, 4, 5)
val result = list.flatMap(it => if (it % 2 == 0) Some(it) else None)
result should be(res0)
collect
will apply a partial function to all elements of a Traversable
and return a different collection. In this exercise, a case fragment is a partial function:
val list = List(4, 6, 7, 8, 9, 13, 14)
val result = list.collect {
case x: Int if (x % 2 == 0) => x * 3
}
result should be(res0)
Two case fragments can be chained to create a more robust result:
val list = List(4, 6, 7, 8, 9, 13, 14)
val partialFunction1: PartialFunction[Int, Int] = {
case x: Int if x % 2 == 0 => x * 3
}
val partialFunction2: PartialFunction[Int, Int] = {
case y: Int if y % 2 != 0 => y * 4
}
val result = list.collect(partialFunction1 orElse partialFunction2)
result should be(res0)
foreach
will apply a function to all elements of a Traversable
, but unlike the map
function, it will not return anything since the return type is Unit
- an equivalent to a void
return type in Java/C++:
val list = List(4, 6, 7, 8, 9, 13, 14)
list.foreach(num => println(num * 4))
list should be(res0)
toArray
will convert any Traversable
to an Array
, which is a special wrapper around a primitive Java array:
val set = Set(4, 6, 7, 8, 9, 13, 14)
val result = set.toArray
result.isInstanceOf[Array[Int]] should be(res0)
toList
will convert any Traversable
to a List
.
val set = Set(4, 6, 7, 8, 9, 13, 14)
val result = set.toList
result.isInstanceOf[List[_]] should be(res0)
toList
, as well as other conversion methods such as toSet
and toArray
, will not convert if the collection type is the same:
val list = List(5, 6, 7, 8, 9)
val result = list.toList
result eq list should be(res0)
toIterable
will convert any Traversable
to an Iterable
. This is a base trait
for all Scala collections that define an iterator method to iterate through the collection's elements:
val set = Set(4, 6, 7, 8, 9, 13, 14)
val result = set.toIterable
result.isInstanceOf[Iterable[_]] should be(res0)
toSeq
will convert any Traversable
to a Seq
which is an ordered Iterable
and the superclass to List
, Queue
, and Vector
. Sequences
provide a method apply for indexing. Indices range from 0 up to the length of a sequence:
val set = Set(4, 6, 7, 8, 9, 13, 14)
val result = set.toSeq
result.isInstanceOf[Seq[_]] should be(res0)
toIndexedSeq
will convert any Traversable
to an IndexedSeq
which is an indexed sequence used in Vectors
and Strings
:
val set = Set(4, 6, 7, 8, 9, 13, 14)
val result = set.toIndexedSeq
result.isInstanceOf[IndexedSeq[_]] should be(res0)
toStream
will convert any Traversable
to a LazyList
where elements are evaluated as they are needed:
val list = List(4, 6, 7, 8, 9, 13, 14)
val result = list.to(LazyList)
result.isInstanceOf[LazyList[_]] should be(res0)
(result take 3) should be(res1)
toSet
will convert any Traversable
to a Set
which is a collection of unordered, unique values:
val list = List(4, 6, 7, 8, 9, 13, 14)
val result = list.toSet
result.isInstanceOf[Set[_]] should be(res0)
toMap
will convert any Traversable
to a Map
. How it's used depends on the original collection; if it's a List
or Seq
, it should be of parameterized type Tuple2
:
val list = List("Phoenix" -> "Arizona", "Austin" -> "Texas")
val result = list.toMap
result.isInstanceOf[Map[_, _]] should be(res0)
toMap
will also convert a Set
to a Map
; it should be of parameterized type Tuple2
:
val set = Set("Phoenix" -> "Arizona", "Austin" -> "Texas")
val result = set.toMap
result.isInstanceOf[Map[_, _]] should be(res0)
isEmpty
is pretty self-evident:
val map = Map("Phoenix" -> "Arizona", "Austin" -> "Texas")
map.isEmpty should be(res0)
val set = Set()
set.isEmpty should be(res1)
nonEmpty
is pretty self-evident too:
val map = Map("Phoenix" -> "Arizona", "Austin" -> "Texas")
map.nonEmpty should be(res0)
val set = Set()
set.nonEmpty should be(res1)
size
provides the size of the traversable:
val map = Map("Phoenix" -> "Arizona", "Austin" -> "Texas")
map.size should be(res0)
knownSize
will return the number of elements if the traversable has a finite end, otherwise -1
:
val map = Map("Phoenix" -> "Arizona", "Austin" -> "Texas")
map.knownSize should be(res0)
val stream = cons(0, cons(1, LazyList.empty))
stream.knownSize should be(res1)
head
will return the first element of an ordered collection, or some random element if order is not defined like in a Set
or Map
:
val list = List(10, 19, 45, 1, 22)
list.head should be(res0)
headOption
will return the first element as an Option
of an ordered collection, or some random element if order is not defined. If a first element is not available, then None
is returned:
val list = List(10, 19, 45, 1, 22)
list.headOption should be(res0)
val list2 = List()
list2.headOption should be(res1)
last
will return the last element of an ordered collection, or some random element if order is not defined:
val list = List(10, 19, 45, 1, 22)
list.last should be(res0)
lastOption
will return the last element as an Option
of an ordered collection, or some random element if order is not defined. If a last element is not available, then None
is returned:
val list = List(10, 19, 45, 1, 22)
list.lastOption should be(res0)
val list2 = List()
list2.lastOption should be(res1)
find
will locate the first item that matches the predicate p
as Some
, or None
if an element is not found:
val list = List(10, 19, 45, 1, 22)
list.find(_ % 2 != 0) should be(res0)
val list2 = List(4, 8, 16)
list2.find(_ % 2 != 0) should be(res1)
tail
will return the rest of the collection without the head:
val list = List(10, 19, 45, 1, 22)
list.tail should be(res0)
init
will return the rest of the collection without the last:
val list = List(10, 19, 45, 1, 22)
list.init should be(res0)
Given a from
index, and a to
index, slice
will return the part of the collection including from
, and excluding to
:
val list = List(10, 19, 45, 1, 22)
list.slice(1, 3) should be(res0)
take
will return the first number of elements given:
val list = List(10, 19, 45, 1, 22)
list.take(3) should be(res0)
take
is used often with LazyList
, since they are also Traversable
:
def makeLazyList(v: Int): LazyList[Int] = cons(v, makeLazyList(v + 1))
val a = makeLazyList(2)
(a take 3 toList) should be(res0)
drop
will take the rest of the Traversable
except the number of elements given:
def makeLazyList(v: Int): LazyList[Int] = cons(v, makeLazyList(v + 1))
val a = makeLazyList(2)
((a drop 6) take 3).toList should be(res0)
takeWhile
will continually accumulate elements until a predicate is no longer satisfied:
val list = List(87, 44, 5, 4, 200, 10, 39, 100)
list.takeWhile(_ < 100) should be(res0)
dropWhile
will continually drop elements until a predicate is no longer satisfied:
val list = List(87, 44, 5, 4, 200, 10, 39, 100)
list.dropWhile(_ < 100) should be(res0)
filter
will take out all elements that don't satisfy a predicate. (An Array
is also Traversable
.)
val array = Array(87, 44, 5, 4, 200, 10, 39, 100)
array.filter(_ < 100) should be(res0)
filterNot
will take out all elements that satisfy a predicate:
val array = Array(87, 44, 5, 4, 200, 10, 39, 100)
array.filterNot(_ < 100) should be(res0)
splitAt
will split a Traversable
at a position, returning a 2 product Tuple
. splitAt
is also defined as (xs take n, xs drop n)
val array = Array(87, 44, 5, 4, 200, 10, 39, 100)
val result = array splitAt 3
result._1 should be(res0)
result._2 should be(res1)
span
will split a Traversable
according to a predicate, returning a 2 product Tuple
. span
is also defined as (xs takeWhile p, xs dropWhile p)
val array = Array(87, 44, 5, 4, 200, 10, 39, 100)
val result = array span (_ < 100)
result._1 should be(res0)
result._2 should be(res1)
partition
will split a Traversable
according to a predicate, returning a 2 product Tuple
. The left-hand side contains the elements satisfied by the predicate whereas the right hand side contains the rest of the elements. partition
is also defined as (xs filter p, xs filterNot p)
val array = Array(87, 44, 5, 4, 200, 10, 39, 100)
val result = array partition (_ < 100)
result._1 should be(res0)
result._2 should be(res1)
groupBy
will categorize a Traversable
according to a given function and return a map with the results. This exercise uses partial function chaining:
val array = Array(87, 44, 5, 4, 200, 10, 39, 100)
val oddAndSmallPartial: PartialFunction[Int, String] = {
case x: Int if x % 2 != 0 && x < 100 => "Odd and less than 100"
}
val evenAndSmallPartial: PartialFunction[Int, String] = {
case x: Int if x != 0 && x % 2 == 0 && x < 100 => "Even and less than 100"
}
val negativePartial: PartialFunction[Int, String] = {
case x: Int if x < 0 => "Negative Number"
}
val largePartial: PartialFunction[Int, String] = {
case x: Int if x > 99 => "Large Number"
}
val zeroPartial: PartialFunction[Int, String] = {
case x: Int if x == 0 => "Zero"
}
val result = array groupBy {
oddAndSmallPartial orElse
evenAndSmallPartial orElse
negativePartial orElse
largePartial orElse
zeroPartial
}
(result("Even and less than 100") size) should be(res0)
(result("Large Number") size) should be(res1)
forall
will determine if a predicate is valid for all members of a Traversable
:
val list = List(87, 44, 5, 4, 200, 10, 39, 100)
val result = list forall (_ < 100)
result should be(res0)
exists
will determine if a predicate is valid for some members of a Traversable
:
val list = List(87, 44, 5, 4, 200, 10, 39, 100)
val result = list exists (_ < 100)
result should be(res0)
count
will count the number of elements that satisfy a predicate in a Traversable
:
val list = List(87, 44, 5, 4, 200, 10, 39, 100)
val result = list count (_ < 100)
result should be(res0)
foldLeft
will combine an operation starting with a seed and combining from the left. foldLeft
takes as a first parameter the initial value of the fold. Once the fold is established, you provide a function that takes two arguments. The first argument is the running total of the operation, and the second element is the next element of the list.
Given a Traversable (x1, x2, x3, x4)
, an initial value of init
, an operation op
, foldLeft
is defined as: (((init op x1) op x2) op x3) op x4)
val list = List(5, 4, 3, 2, 1)
val result = list.foldLeft(0) { (`running total`, `next element`) =>
`running total` - `next element`
}
result should be(res0)
val result2 = list.foldLeft(0)(_ - _) //Short hand
result2 should be(res1)
(((((0 - 5) - 4) - 3) - 2) - 1) should be(res2)
foldRight
will combine an operation starting with a seed and combining from the right. foldRight
takes as a first parameter the initial value of the fold. Once the fold is established, you provide a function that takes two elements. The first is the next element of the list, and the second element is the running total of the operation.
Given a Traversable (x1, x2, x3, x4)
, an initial value of init
, an operation op
, foldRight
is defined as: x1 op (x2 op (x3 op (x4 op init)))
val list = List(5, 4, 3, 2, 1)
val result = list.foldRight(0) { (`next element`, `running total`) =>
`next element` - `running total`
}
result should be(res0)
val result2 = list.foldRight(0)(_ - _) //Short hand
result2 should be(res1)
(5 - (4 - (3 - (2 - (1 - 0))))) should be(res2)
reduceLeft
is similar to foldLeft
, except that the seed is the head value:
val intList = List(5, 4, 3, 2, 1)
intList.reduceLeft {
_ + _
} should be(res0)
val stringList = List("Do", "Re", "Me", "Fa", "So", "La", "Te", "Do")
stringList.reduceLeft {
_ + _
} should be(res1)
reduceRight
is similar to foldRight
, except that the seed is the last value:
val intList = List(5, 4, 3, 2, 1)
intList.reduceRight {
_ + _
} should be(res0)
val stringList = List("Do", "Re", "Me", "Fa", "So", "La", "Te", "Do")
stringList.reduceRight {
_ + _
} should be(res1)
There are some methods that take much of the folding work out by providing basic functionality. sum
will add all the elements, product
will multiply, min
would determine the smallest element, and max
the largest:
val intList = List(5, 4, 3, 2, 1)
intList.sum should be(res0)
intList.product should be(res1)
intList.max should be(res2)
intList.min should be(res3)
The naive recursive implementation of reduceRight
is not tail recursive and would lead to a stack overflow if used on larger traversables.
However, reduceLeft
can be implemented with tail recursion.
To avoid the potential stack overflow with the naive implementation of reduceRight
we can easily implement it based on reduceLeft
by reversing the list and the inverting the reduce function.
The same applies for folding operations.
There is also a reduce
(and fold
) available, which works exactly like reduceLeft
(and foldLeft
) and it should be the prefered method to call unless there is a strong reason to use reduceRight
(or foldRight
).
val intList = List(5, 4, 3, 2, 1)
intList.reduceRight((x, y) => x - y) should be(res0)
intList.reverse.reduceLeft((x, y) => y - x) should be(res1)
intList.reverse.reduce((x, y) => y - x) should be(res2)
transpose
will take a traversable of traversables and group them by their position in it's own traversable, e.g.:
((x1, x2),(y1, y2)).transpose = (x1, y1), (x2, y2)
or
((x1, x2, x3),(y1, y2, y3),(z1, z2, z3)).transpose = ((x1, y1, z1), (x2, y2, z2), (x3, y3, z3))
val list = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9))
list.transpose should be(List(res0, res1, res2))
val list2 = List(List(1), List(4))
list2.transpose should be(List(res3))
mkString
will format a Traversable
using a given string as the delimiter:
val list = List(1, 2, 3, 4, 5)
list.mkString(",") should be(res0)
mkString
will also take a beginning and ending string to surround the list.:
val list = List(1, 2, 3, 4, 5)
list.mkString(">", ",", "<") should be(res0)
addString
will take a StringBuilder to add the contents of list into the builder.
val stringBuilder = new StringBuilder()
val list = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)
stringBuilder.append("I want all numbers 6-12: ")
list.filter(it => it > 5 && it < 13).addString(stringBuilder, ",")
stringBuilder.mkString should be(res0)