Skip to main content

99 Scala Problems Challenge – 1/99

Problem 1 – Find the last element of a list

‘P01 (*) Find the last element of a list.

Example: scala> last(List(1, 1, 2, 3, 5, 8)) res0: Int = 8’

One element list scenario

I’ve started with a test, which stated, that one-element list should return that element

import org.scalatest.{FunSuite, Matchers}

class P01Test extends FunSuite with Matchers {

  val p01 = new P01[Int]()

  test("that returns element for single element's list") {
    p01.last(List(3)) should equal(3)
  }
}

Implementation was straightforward. Function ‘last’ of parametrized class ‘P01’ takes list as a parameter and returns its first element.

class P01[T] {
  def last(list: List[T]): T = list(0)
}

How to deal with an empty list?

Just taking a quick look at the code, should trigger a quick question “Yeah, but what if the list is empty?”. And that is a really good question. What is the last element of a non-existing list? There is no such element! Then I guess we will return a null? Not exactly. If you recall, Scala comes with Option type. After digging around the Internet, you will found out the following:

“The Option type is one of the most fundamental idiosyncrasies of Scala”

“Syntax errors are better than runtime errors, because (1) you get them right away, not some arbitrary time later, and (2) the fix is usually obvious.”

“You should avoid null as much as possible in Scala. It really only exists for interoperability with Java. So, instead of null, use Option whenever it’s possible that a function or method can return “no value”, or when it’s logically valid for a member variable to have “no value”.”

I’ve changed signature of the ‘last’ method to return an Option[T] instead of T and wrote the proper test for an empty list scenario


test("that returns none for empty list") {
  p01.last(List()) should be(None)
}

I’ve used pattern matching to make both of my tests pass


class P01[T] {
  def last(list: List[T]): Option[T] = list match {
    case Nil => None
    case _ => Some(list(0))
  }
}

Final test

And at last came time for the final test

test("that returns last element of list") {
  p01.last(List(1, 1, 2, 3, 5, 8)) should be(Some(8))
}

The solution was to extend the previous pattern matching

def last(list: List[T]): Option[T] = list match {
  case Nil =>; None
  case head :: Nil => Some(head)
  case head :: tail => last(tail)
}

I’ve used list pattern matching which allows distinguishing head and tail of a given list.
All three tests passed, my solution was read to compare it with the reference one

Comparing to reference solution

The reference solution to the problem was

object P01 {
  def last[A](ls: List[A]): A = ls match {
    case h :: Nil => h
    case _ :: tail => last(tail)
    case _ => throw new NoSuchElementException
  }
}

1. Instead of returning an Option[A], they’ve used exception to handle empty and null lists. This seemed rather odd for me.
2. Instead of parametrizing the class, they’ve parametrized the function

 

Source code for this blog post is available at Github.

  • Елена Водзянова

    def last(list: List[T]): Option[T] = list match should be def last[T](list: List[T]): Option[T] = list match {