shiv455
shiv455

Reputation: 7764

Effective way of pattern matching on multiple Options Scala

I have below function which returns a flag based on some conditions.

Trying to see if there is a better Scala way to achieve the same?

Basically based on the status of the person there are different rules to be applied finally to derive on the pass_ind flag.

If person status is Student or Unemployment there are some rules on income and if the person status is not in Student or Unemployment there are different rules to be applied on score & income

def IncomeScreenStatus(status_cd: Option[String],
                                   score: Option[Int],
                                 income: Option[Double]) : String  = {

 var pass_ind : String = "F"

 if (score.isDefined && income.isDefined && status_cd.isDefined) {

        val score : Int = score.get
        val income : Double = income.get
        val status_cd : String = status_cd.get

        if (status_cd == "STUDENT" || status_cd == "UNEMPLOYMENT") {
          pass_ind = (status_cd, income) match {
            case ("UNEMPLOYMENT", income) if income <= 7000 => "P"
            case ("STUDENT", income) if income <= 18000 => "P"
            case _ => "F"
          }
        }
        else {
          pass_ind = (income, score) match {
            case (income, score) if (score < 100 || score > 150) && income <= 8500 => "P"
            case (income, score) if (score <= 167 && score >= 100) && income <= 10500 => "P"
            case _ => "F"
          }
        }

      }

    pass_ind


}

Upvotes: 0

Views: 694

Answers (5)

Shane Perry
Shane Perry

Reputation: 990

As your example requires each field to be defined, you can zip the Options together. I also like to use extractors in place of explicit guards on my case statements.

If your status is a finite set, I would also recommend using a sealed trait to represent it.

object Example {

  sealed trait Status
  case object Student extends Status
  case object Unemployed extends Status
  case object Other extends Status

  object PoorStudent {
    def unapply(income: Int) = if (income <= 18000) Some(income) else None
  }

  object PoorUnemployed {
    def unapply(income: Int) = if (income <= 7000) Some(income) else None
  }

  object IncomeScoreRange {
    def unapply(score: Int, income: Double) =
      if (((score < 100 || score > 150) && income <= 8500) || (income <= 10500 && (100 to 167 contains score)))
        Some((score, income))
      else
        None
  }

  def compute(status: Option[Status], score: Option[Int], income: Option[Double]): String = {

    (status, score, income).zipped
      .collect {
        case (Student, _, PoorStudent(_))                    => "P"
        case (Unemployed, _, PoorUnemployed(_))              => "P"
        case (Other, IncomeScoreRange(_, _))                 => "P"
      }
      .headOption
      .getOrElse("F")
  }
}

Upvotes: 0

Binzi Cao
Binzi Cao

Reputation: 1085

In functional programming, it is better to have small functions instead of large one. I would prefer doing something as below:

  def screenStudent(income:Double):String = {
    if (income <= 18000) "P" else "F"
  }
  def screenUnemployment(income:Double):String = {
    if (income <= 7000) "P" else "F"
  }
  def screenOthers(income:Double, score:Int):String = {
    (income, score) match {
      case (income, score) if (score < 100 || score > 150) && income <= 8500 => "P"
      case (income, score) if (score <= 167 && score >= 100) && income <= 10500 => "P"
      case _ => "F"
    }
  }
  def incomeScreenStatus(
    status_cd: Option[String],
    score: Option[Int],
    income: Option[Double]
  ) : String  =
  (status_cd , score, income) match {
    case (Some("STUDENT"), Some(_), Some(i)) => screenStudent(i)
    case (Some("UNEMPLOYMENT"), Some(_), Some(i)) => screenUnemployment(i)
    case (Some(_), Some(s), Some(i)) => screenOthers(i, s)
    case _ => "F"
  }

Upvotes: 0

jwvh
jwvh

Reputation: 51271

Here's a first crack at it.

def IncomeScreenStatus(status_cd: Option[String]
                      ,score    : Option[Int]
                      ,income   : Option[Double]) : String  = {
  for {
    stat <- status_cd
    scr  <- score
    incm <- income
  } yield stat match {
    case "UNEMPLOYMENT" => if (incm <=  7000) "P" else "F"
    case "STUDENT"      => if (incm <= 18000) "P" else "F"
    case _ => if ((scr <  100 || scr >  150) && incm <=  8500 ||
                  (scr <= 167 && scr >= 100) && incm <= 10500) "P" else "F"
  }
}.getOrElse("F")

Creating local variables with the same name as the passed in parameters really confuses the compiler.

Upvotes: 1

sarveshseri
sarveshseri

Reputation: 13985

First of all, I have few comments about recommended code-style adopted by Scala,

  • variable/function/def names are supposed to be camelCase
    • incomeScreenStatus instead of IncomeScreenStatus.
    • statusCd instead of status_cd
    • passInd instead of pass_ind

Now,

def incomeScreenStatus(statusCd: Option[String],
                       score: Option[Int],
                       income: Option[Double]) : String  = {
  (score, income, statusCd) match {
    case (Some(scoreV), Some(incomeV), Some(statusCdV)) => {
      (statusCdV, incomeV, scoreV) match {
        case ("UNEMPLOYMENT", _, _) if incomeV <= 7000 => "p"
        case ("STUDENT", _, _) if incomeV <= 18000 => "p"
        case _ if (scoreV < 100 || scoreV > 150) && incomeV <= 8500 => "P"
        case _ if (scoreV <= 167 && scoreV >= 100) && incomeV <= 10500 => "P"
        case _ => "F"
      }
    }
    case _ => "F"
  }

}

Upvotes: 1

Steve Waldman
Steve Waldman

Reputation: 14073

Maybe something like this? (I haven't tried it, no guarantess it's correct.)

def IncomeScreenStatus( mb_status_cd : Option[String], mb_score : Option[Int], mb_income : Option[Double]) : String  = {

  def nsu( score : Int, income : Double ) = { // neither student nor unemployed
    if ( (score < 100 || score > 150) && income <= 8500 )         "P"
    else if ( (score <= 167 && score >= 100) && income <= 10500 ) "P"
    else                                                          "F"
  }

  ( mb_status_cd, mb_score, mb_income ) match {
    case ( Some( "STUDENT"                  ), Some( score ), Some( income ) ) if ( income <= 18000 ) => "P"
    case ( Some( "UNEMPLOYMENT"             ), Some( score ), Some( income ) ) if ( income <=  7000 ) => "P"
    case ( Some( "STUDENT" | "UNEMPLOYMENT" ), Some( _     ), Some( _      ) )                        => "F"
    case ( Some( _                          ), Some( score ), Some( income ) )                        => nsu( score, income )
    case _                                                                                            => "F"
  }
}

Upvotes: 1

Related Questions