Scala Tutorial Part 3
I’m gonna cover some more List and Array methods this time, Options, and little bit of IO. If you haven’t already, take a look at part 1 and part 2. They are really short reads and have tons of examples on how to do everything. If you have any doubts or comments or corrections, feel free to let me know. Let’s get started!
Get or Remove Elements
For the rest of this tutorial, much of the code will work with val nums which is an Array and val words which is a List.
drop(n: Int) removes the first n elements and returns a new Collection. Key word: Collection. That means it is not limited to Arrays as shown in the figure.
dropRight(n: Int) removes the last n elements and returns a new Collection.
head gives you the first element and last gives you the last element.
patch(from: Int, replacements: Seq[A], n: Int) produces a new collection by replacing n elements starting at the index given by from with values in replacements.
slice(from: Int, until: Int) returns a new Collection with elements from from to until, with until being exclusive.
splitAt(index: Int) produces a tuple after splitting the original collection at the given index.
take(n: Int) and takeRight(n: Int) give you the first n elements and the last n elements, respectively.
Set Manipulation Methods
The following methods will look familiar if you have ever taken a discrete math class. It’s all set theory.
distinct produces a collection of all unique elements in a given collection.
intersect(that: Seq[A]) produces a new collection that returns the intersection of the current collection and that.
union(that: Seq[A]) produces a new collection that is the union of the current collection and that.
diff(that: Seq[A]) produces a new collection that is the difference between the current collection and that.
max, min, product and sum are self-explanatory, right?
zip(that:Seq[B]) produces a new collection of tuples containing elements from the current collection and that. The resulting collection is no longer than the smaller of the two input collections.
zipWithIndex produces a collection of tuples by zipping the elements in current collection with their respective indices.
tail returns a collection of all elements after the first element.
mkString converts a collection into a String.
Both Arrays and Lists can work with patterns. So, you can do some cool things like in the figure above. This is actually a really common practice in Scala, so you’re going to want to get used to working with this kind of code. At the bottom of the image, you will see some interesting things. You can convert a List to an Array and back. It is very easy, so you can make methods that take only Lists, word with Arrays and vice versa very easily.
The following methods should be familiar to anyone who has worked with functional methods before.
foreach(A => Unit) produces a Unit (void in Java) and performs the given lambda expression on each element. This means that if your lambda expression returns anything, it is ignored.
map(A => B) produces a new collection by applying a lambda expression to each element.
filter(A => Boolean) produces a new collection by filtering out elements in the current collection by applying a lambda expression to each element.
split(delimiter: String) should look familiar to anyone who has worked with the method in Java. It splits a String based on the delimiter.
Obviously, you can mix and match these methods as much as you want. There is no restriction there. Just remember to save the outputs to intermediate values if you have really long combinations of these methods. This will improve readability.
count(p: A => Boolean) returns the number of elements that satisfy the predicate.
dropWhile(p: A => Boolean) produces a new collection where all members at the front the satisfy the predicate have been removed.
exists(p: A => Boolean) returns where or not there is an element in the sequence that satisfies the predicate.
filterNot(p: A => Boolean) does the opposite of filter. It creates a new collection with the elements that do not satisfy the predicate.
find(p: A => Boolean) returns an Option[A] with the first value that
satisfied the predicate.
forall(p: A => Boolean) tells you if the predicate is true for all values in the sequence.
indexWhere(p: A => Boolean) returns the index of the first element that
satisfies the predicate.
lastIndexWhere(p: A => Boolean) returns the index of the last element
that satisfies the predicate.
partition(p: A => Boolean) produces a tuple with two elements: a sequence of the elements that satisfy the predicate and a sequence of those that do not.
prefixLength(p: A => Boolean) returns the number of elements at the beginning of the sequence that satisfy the predicate.
takeWhile(p: A => Boolean) produces a new sequence with the elements
from the beginning of this collection that satisfy the predicate.
foldLeft(z: B)(op: (B, A) B) is a curried method that produces an element
of type B. B is the type of the first argument to the function, the first argument to the operation, and the output of the operation. It works by going from left to right through the collection, and for each element it passes the accumulated value and the next collection element into the op function. The result is used as the accumulated value for the next call. z is the first value for the accumulator, and the last one is the result of the function.
reduceLeft(op: (A, A) A) is a more restricted version of foldLeft. There is no initial accumulator, as the first call to op is passed the first and second elements. The output of that is then passed in with the third element and so on. The value after the last element is the result of the reduce.
scanLeft(z: B)(op: (B, A) B) is like foldLeft, but it returns a sequence with all the intermediate results, not just the last one.
A Few Others
flatMap(f: A => Seq[B])is like the map function, but is useful
when the function produces sequences and you do not want embedded Lists or Arrays (ex: List[List[B]]). It flattens the result to give you just a basic sequence.
maxBy(f: A => B) returns the maximum element based on comparison of the B values produced by passing the elements of this collection into f.
minBy(f: A => B) returns the minimum element based on comparison of
the B values produced by passing the elements of this collection into f.
sortBy(f: A => B) produces a new collection with the elements sorted based
on the B values produces be passing the elements of this collection to f.
sortWith(lt: (A, A) => Boolean) produces a new collection with the elements sorted based on the comparison function that is passed in.
Make sure you pay attention to the way capital letters differ from lower case letters in the above methods.
Options are a great option when you want to avoid passing back null values. You’ll recognize this if you have ever worked with Optional in Java. There are two types: Some and None. Some holds a single value. None represents the absence of a value. By returning an Option or Optional, the goal is to avoid a NullPointerException. These objects are great when you use it in combination with match (Pattern Matching). You could get the value in a Some by using the get() method, but you will get an exception if you call this method on None. There is a way to get around this: use getOrElse(alternative: B).
Finally, Some IO
Let’s read something from a file. We are gonna use scala.io.Source which we will instantiate with the fromFile() methods. A Source is actually an Iterator[Char]. You’re probably going to want to read one line after another, instead of one character at a time. We can do this by calling getLines() which returns an Iterator[String].
Unlike a List or Array, once you use an Iterator, it’s contents are consumed, which means you can only use an Iterator once. This is like Streams in Java, and it is a very efficient way of going through files because it frees up memory once you’re done reading through the file. Iterator also has hasNext() and next(), which tell you if there are any elements, and gives you the element, respectively. You can iterate through an Iterator using higher order functions or a while loop. hasNext() and next() will come in handy if you do use a while loop.
The above code reads a text file contains numbers separated by commas. It reads each line, splits it up by the comma and maps the toDouble function to each value. Lastly, it converts the whole thing to an Array. Simple and elegant.
This method is great when out file contains values of a single type. But, if each line consists of a mix of String, Int and Double, it would be a better option to use a while loop and read value at a time and map it properly. Feel free to play around and see what you can do. Best way to learn anything.
That’s it for this time. Next time, I’m gonna go over currying and a few more basics. But, as of now, you know enough to get started and write rudimentary applications. Go out an explore!