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 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.