Counting Coins

To explore the expressive power of pattern matching, let’s write a function that will count the number of nickels, dimes, and quarters in a given collection:

 val coins = List(25, 10, 5, 10, 5, 25, 10, 5, 5, 25)

Using the traditional if-else construct, the code would look like this:

 def countCoins(coins : Seq[Int], nickels : Int = 0,
  dimes : Int = 0, quarters : Int = 0) : Map[String, Int] = {
 
  if (coins.isEmpty)
  Map("nickels" -> nickels, "dimes" -> dimes,
  "quarters" -> quarters)
  else {
  if (5 == coins.head) {
  countCoins(coins.tail, nickels + 1, dimes, quarters)
  }
  else {
  if (10 == coins.head) {
  countCoins(coins.tail, nickels, dimes + 1, quarters)
  } else {
  if (25 == coins.head) {
  countCoins(coins.tail, nickels, dimes, quarters + 1)
  } else {
  countCoins(coins.tail, nickels, dimes, quarters)
  }
  }
  }
  }
 }
 println(countCoins(coins))
 //Map(nickels -> 4, dimes -> 3, quarters -> 3)

The function accepts a collection of coins as the first parameter, followed by the known number of nickels, dimes, and quarters, all set to 0 by default. Within the method, if there are no coins, you return a map of the known counts. Otherwise, you check the first coin using a series of simple if-else statements and recursively process the rest of the coins. You could have used the collection’s filter method for this also, but in this example, we’ll see how to use patterns instead.

That’s quite a bit of code and it appears noisy. But we can use pattern matching to make this function concise. Scala provides a match method that appears something like a switch statement, but is more powerful. It’s an expression that can filter or match the target object against different types and data values. The previous code will reduce to a few lines that are easier to follow.

 def countCoins(coins : Seq[Int], nickels : Int = 0,
  dimes : Int = 0, quarters : Int = 0) : Map[String, Int] = {
 
  if (coins.isEmpty)
  Map("nickels" -> nickels, "dimes" -> dimes,
  "quarters" -> quarters)
  else coins.head match {
  case 5 => countCoins(coins.tail, nickels + 1,
  dimes, quarters)
  case 10 => countCoins(coins.tail, nickels,
  dimes + 1, quarters)
  case 25 => countCoins(coins.tail, nickels,
  dimes, quarters + 1)
  case _ => countCoins(coins.tail, nickels,
  dimes, quarters)
  }
 }
 println(countCoins(coins))
 //Map(nickels -> 4, dimes -> 3, quarters -> 3)

Here you replaced the outermost else block with a call to the match method on the first element or the head of the coins collection. Scala matches the value of the first element with each case of literals you have provided and takes the appropriate route.

The underscore in case _ represents a wildcard, used if all of the other cases fail to match. Scala does not insist that you provide the wildcard case, but without it any mismatch would result in a runtime exception.

The match operation does not restrict you to simple literals. You can replace the top-level if with a match in the coins count example:

 coins match {
  case Seq() =>
  Map("nickels" -> nickels, "dimes" ->
  dimes, "quarters" -> quarters)
  case _ =>
  coins.head match {
  case 5 => countCoins(coins.tail, nickels + 1,
  dimes, quarters)
  case 10 => countCoins(coins.tail, nickels,
  dimes + 1, quarters)
  case 25 => countCoins(coins.tail, nickels,
  dimes, quarters + 1)
  case _ => countCoins(coins.tail, nickels, dimes, quarters)
  }
 }

The first case takes effect if the collection is empty, and the wildcard case takes effect otherwise. You used two levels of match in this example, but that’s not required. The list match also permits you to probe into the contents of the list. Using this, you can let the top-level match check the values of the list and eliminate the nested match.

  coins match {
  case Seq() =>
  Map("nickels" -> nickels, "dimes" ->
  dimes, "quarters" -> quarters)
  case Seq(5, _*) => countCoins(coins.tail, nickels + 1,
  dimes, quarters)
  case Seq(10, _*) => countCoins(coins.tail, nickels,
  dimes + 1, quarters)
  case Seq(25, restOfTheCoins @ _*) =>
  countCoins(restOfTheCoins, nickels, dimes, quarters + 1)
  case _ => countCoins(coins.tail, nickels, dimes, quarters)
  }
 }

The pattern Seq(5, _*) matches a list whose first element is a value 5. The remaining elements in the list are represented by _* and may be captured into a reference, as in the case for 25. Capturing the values is optional, as you can see in the case expressions.