Skip to main content

balanced brackets

· 3 min read

description

this problem is a classic. i got this one from an interview.

you’re given the string as input consisting solely of (, ), and any alpha lower case chars a-z. determine whether the parentheses are balanced.

solution

there are multiple solution to this challenge, but we will try to use scala feats.

first, we define out test data:

// test data
val testData = List(
"()",
"(()",
"(())",
")(",
")()",
"(((this(is)(my(test)(string(and)I)want)to)know((if)it)is)balanced)"
)

this solution levarege:

  • scala's tailrec optimization
  • scala's immutable.List as a stack (last-in first-out)
  • and, some kind of short-circuiting i.e. str = ")("
import scala.annotation.tailrec

def isBalanced(str: String): Boolean = {
@tailrec
def validate(cs: List[Char], queue: List[Char]): Boolean = {
cs match {
case Nil => queue.isEmpty
case '(' :: next => validate(next, '(' +: queue)
case ')' :: next =>
if (queue.headOption.contains('(') && queue.nonEmpty) validate(next, queue.tail)
else false
case _ :: next => validate(next, queue)
}
}

validate(str.toList, List.empty)
}

testData.map { str =>
s"""is "$str" balanced? = ${isBalanced(str)}"""
}
// res0: List[String] = List(
// "is \"()\" balanced? = true",
// "is \"(()\" balanced? = false",
// "is \"(())\" balanced? = true",
// "is \")(\" balanced? = false",
// "is \")()\" balanced? = false",
// "is \"(((this(is)(my(test)(string(and)I)want)to)know((if)it)is)balanced)\" balanced? = true"
// )

this looks nice and dandy but, let's handle more types of brackets:

val testData2 = List(
"(a)[b]{c}",
"([{}])",
"([{]})",
"( ([{}]) {} [] )"
)

we should be able to just improve our previous solution and preserve the 3 scala' feats listed above

val open  = Set('(','[','{')
val close = Set(')',']','}')

def isBalanced2(str: String): Boolean = {
@tailrec
def validate(cs: List[Char], queue: List[Char]): Boolean = {
cs match {
case Nil => queue.isEmpty
case h :: next if open.contains(h) => validate(next, h +: queue)
case h :: next if close.contains(h) =>

if queue.isEmpty then false
else
queue.headOption match {
case Some(o) =>
(o, h) match {
case ('(', ')') => validate(next, queue.tail)
case ('[', ']') => validate(next, queue.tail)
case ('{', '}') => validate(next, queue.tail)
case (_, _) => false
}
case None => false
}

case _ :: next => validate(next, queue)
}
}

validate(str.toList, List.empty)
}

okie dokie, isBalanced2 preserves the 3 feats listed above. let's give it a shot:

testData2.map { str =>
s"""is "$str" balanced? = ${isBalanced2(str)}"""
}
// res1: List[String] = List(
// "is \"(a)[b]{c}\" balanced? = true",
// "is \"([{}])\" balanced? = true",
// "is \"([{]})\" balanced? = false",
// "is \"( ([{}]) {} [] )\" balanced? = true"
// )