Andrey
Andrey

Reputation: 800

Scala way to program bunch of if's

I'm starting out with scala, and trying to apply the functional way to it, but I came out with bunch of nested if\else constructions which is hard to read, and I wonder is there nicer way to program such things?

For example I wrote a script, that performs parentheses balancing:

def balance(chars: List[Char]): Boolean = {
    def checkParentesys(chars: List[Char], parentesis: List[Char]): Boolean =
      if (chars.isEmpty && parentesis.isEmpty)
        true
      else
        if (chars.head == '(')
            checkParentesys(chars.tail, '(' :: parentesis)
        else
            if (parentesis.isEmpty)
                false
            else
                checkParentesys(chars.tail, parentesis.tail)

    checkParentesys(chars.filter(s => s == '(' || s == ')'), List())
  }

How can I write it to be more functional and more scala like?

Upvotes: 16

Views: 9583

Answers (4)

Don
Don

Reputation: 31

var parens :List[Char] =  Nil
def matcher(chrs: List[Char]): Boolean = {
     if (chrs.isEmpty) {
        return parens.isEmpty
     }
     else {
         chrs.head match {
           case '(' =>  parens = '(' :: parens ;matcher(chrs.tail)
           case ')' =>  if (parens.isEmpty) return false 
                            else if (parens.apply(0) ==  '(') parens = parens.drop(1) 
                            else return false;  
                            matcher(chrs.tail);
           case _ => matcher(chrs.tail)
         }
     }
}

As you can see I didn't use count because count won't work on ())(

Upvotes: 3

stew
stew

Reputation: 11366

I don't think there is any reason to filter the list before you traverse it. You can just ignore the non parentheses as you traverse the list. I think it is also unnecessary to build the second list. All you really want to know is that the count of open parenthesis is never negative:

def balance(chars: List[Char]): Boolean = {
  @tailrec
  def _balance(chars: List[Char], count: Int) : Boolean = 
    chars match {
        case Nil => count == 0   // end of the string did we close every open?
        case '(' :: xs => _balance(xs, count+1)  
        case ')' :: xs => (count > 0) && _balance(xs, count-1) 
        case _ => _balance(chars.tail, count) // uninteresting char, skip it
    }

  _balance(chars, 0)
}

Upvotes: 19

Luigi Plinge
Luigi Plinge

Reputation: 51109

It might be nicer to write it as a fold:

def balance(chars: List[Char]): Boolean = chars.foldLeft(0){
  case (0, ')') => return false
  case (x, ')') => x - 1
  case (x, '(') => x + 1
  case (x, _  ) => x
} == 0

Upvotes: 27

dhg
dhg

Reputation: 52701

Well:

  1. You could start by writing it with else if conditions.
  2. Go ahead an annotate it with tailrec since it's tail-recursive.
  3. The filter condition can be written more simply as Set('(', ')'), which is a function from Char to Boolean
  4. I think you're missing the condition where chars is empty but parenthesis is not.

So it would look like:

def balance(chars: List[Char]): Boolean = {
  @tailrec
  def checkParentesys(chars: List[Char], parentesis: List[Char]): Boolean =
    if (chars.isEmpty && parentesis.isEmpty)
      true
    else if (chars.head == '(')
      checkParentesys(chars.tail, '(' :: parentesis)
    else if (chars.isEmpty || parentesis.isEmpty)
      false
    else
      checkParentesys(chars.tail, parentesis.tail)

  checkParentesys(chars.filter(Set('(', ')')), List())
}

You could also just turn the whole thing into a pattern match:

def balance(chars: List[Char]): Boolean = {
  @tailrec
  def checkParentesys(chars: List[Char], parentesis: List[Char]): Boolean =
    (chars, parentesis) match {
      case (Nil, Nil) => true
      case ('(' :: charsTail, _) => checkParentesys(charsTail, '(' :: parentesis)
      case (Nil, _) => false
      case (_, Nil) => false
      case (')' :: charsTail, '(' :: parentesisTail) => checkParentesys(charsTail, parentesisTail)
    }
  checkParentesys(chars.filter(Set('(', ')')), List())
}

Upvotes: 4

Related Questions