Recursion

Matching Brackets
Matching Brackets in Scala
object MatchingBrackets {
  private val brackets = Map('(' -> ')', '{' -> '}', '[' -> ']')
  private val ends = Set(')', '}', ']')

  private def isValid(stack: List[Char], char: Char): Boolean =
    stack.length > 0 && stack.head == char
  private def safeTail(stack: List[Char]): List[Char] = {
    if (!stack.isEmpty) stack.tail else stack
  }

  def isPaired(input: String): Boolean = {
    isPairedRecur(input, List[Char]())
  }

  @scala.annotation.tailrec
  private def isPairedRecur(input: String, stack: List[Char]): Boolean = {
    if (input.isEmpty) return stack.isEmpty
    (input.head, stack) match {
      case (chr, stack) if ends.contains(chr) =>
        if (isValid(stack, chr))
          isPairedRecur(input.tail, safeTail(stack))
        else
          false
      case (chr, stack) if (brackets.contains(chr)) =>
        isPairedRecur(input.tail, brackets(chr) :: stack)
      case _ => isPairedRecur(input.tail, stack)
    }
  }
}

This approach starts by defining a Map for associating beginning brackets with their ending brackets. A Set is defined to hold the ending brackets.

A private helper method is defined to determine if an ending bracket is valid. Another private helper method returns all but the first element from a List, or returns the List as is if it is already empty.

The isPaired() method returns the result of calling the private recursive method, to which it passes the input String. and an empty List for the "stack".

Note

Note that the immutable Stack exists only for historical reason and as an analogue of mutable stacks. Instead of an immutable stack you can just use a list.

The mutable Stack was deprecated since version 2.12.0. Stack is an inelegant and potentially poorly-performing wrapper around List. Use a List assigned to a var instead.

The recursive method is annotated with the @tailrec annotation to verify that the method can be compiled with tail call optimization.

A tail call is a particular form of recursion where the last call in the method is a call to the same method and nothing else.

In other words, if the last call in recurMe() is recurMe(arg1, arg2) + 1, the + 1 makes the recursion non-tail recursive.

If the last call in recurMe() is recurMe(arg1, arg2, acc + 1), then the recursion is a tail call, because only the method is being called with no other operation being peformed on it.

If the input String is empty, then the method returns if the "stack" List is empty.

The head method is used to get the first character in the input String. It is wrapped in a tuple with the "stack" List, which is passed to the match.

The pattern matching uses the contains() method to check if the character is an ending bracket. If so, and it is valid, then the recursive method calls itself, passing the tail of the input String and all but the first element of the "stack" List. If it is not a valid ending bracket, then the recursive method immediately returns false.

If the character is not an ending bracket, then the contains() method is used to check for it being a beginning bracket. If so, then the recursive method calls itself, passing the tail of the input String and the the associated ending bracket prepended to the "stack" List.

Note that the operations on the List create a new List, thus supporting immutability.

If the character is not any bracket, then the underscore (_) wildcard is used to have the the recursive method call itself, passing the tail of the input String and the "stack" List as is.

16th Aug 2024 · Found it useful?