Rory
Rory

Reputation: 838

Scala alternative to series of if statements that append to a list?

I have a Seq[String] in Scala, and if the Seq contains certain Strings, I append a relevant message to another list.

Is there a more 'scalaesque' way to do this, rather than a series of if statements appending to a list like I have below?

val result = new ListBuffer[Err]()

val malformedParamNames = // A Seq[String]

if (malformedParamNames.contains("$top")) result += IntegerMustBePositive("$top")
if (malformedParamNames.contains("$skip")) result += IntegerMustBePositive("$skip")
if (malformedParamNames.contains("modifiedDate")) result += FormatInvalid("modifiedDate", "yyyy-MM-dd")
...

result.toList

Upvotes: 2

Views: 1514

Answers (4)

Philluminati
Philluminati

Reputation: 2789

In this solution we define a list of mappings that take your IF condition and THEN statement in pairs and we iterate over the inputted list and apply the changes where they match.

//                     IF              THEN
case class Operation(matcher :String, action :String)

def processInput(input :List[String]) :List[String] = {

    val operations = List(
        Operation("$top", "integer must be positive"),
        Operation("$skip", "skip value"),
        Operation("$modify", "modify the date")
    )

    input.flatMap { in =>
        operations.find(_.matcher == in).map { _.action }
    }

}

println(processInput(List("$skip","$modify", "$skip")));

A breakdown

operations.find(_.matcher == in)    // find an operation in our
                                    // list matching the input we are
                                    // checking. Returns Some or None

.map { _.action }                   // if some, replace input with action
                                    // if none, do nothing

input.flatMap { in =>               // inputs are processed, converted
                                    // to some(action) or none and the
                                    // flatten removes the some/none
                                    // returning just the strings.

Upvotes: 0

Chirlo
Chirlo

Reputation: 6130

Most 'scalesque' version I can think of while keeping it readable would be:

  val map = scala.collection.immutable.ListMap(
    "$top" -> IntegerMustBePositive("$top"),
    "$skip" -> IntegerMustBePositive("$skip"),
    "modifiedDate" -> FormatInvalid("modifiedDate", "yyyy-MM-dd"))

 val result = for {
   (k,v) <- map
   if malformedParamNames contains k
 } yield v 

 //or 

 val result2 = map.filterKeys(malformedParamNames.contains).values.toList

Upvotes: 2

Levi Ramsey
Levi Ramsey

Reputation: 20591

Benoit's is probably the most scala-esque way of doing it, but depending on who's going to be reading the code later, you might want a different approach.

// Some type definitions omitted
val malformations = Seq[(String, Err)](
  ("$top", IntegerMustBePositive("$top")),
  ("$skip", IntegerMustBePositive("$skip")),
  ("modifiedDate", FormatInvalid("modifiedDate", "yyyy-MM-dd")
)

If you need a list and the order is siginificant:

val result = (malformations.foldLeft(List.empty[Err]) { (acc, pair) =>
  if (malformedParamNames.contains(pair._1)) {
    pair._2 ++: acc // prepend to list for faster performance
  } else acc
}).reverse // and reverse since we were prepending

If the order isn't significant (although if the order's not significant, you might consider wanting a Set instead of a List):

val result = (malformations.foldLeft(Set.empty[Err]) { (acc, pair) =>
  if (malformedParamNames.contains(pair._1)) {
    acc ++ pair._2
  } else acc
}).toList // omit the .toList if you're OK with just a Set

If the predicates in the repeated ifs are more complex/less uniform, then the type for malformations might need to change, as they would if the responses changed, but the basic pattern is very flexible.

Upvotes: 1

Benoit
Benoit

Reputation: 3598

If you want to use some scala iterables sugar I would use

sealed trait Err
case class IntegerMustBePositive(msg: String) extends Err
case class FormatInvalid(msg: String, format: String) extends Err

val malformedParamNames = Seq[String]("$top", "aa", "$skip", "ccc", "ddd", "modifiedDate")

val result = malformedParamNames.map { v =>
  v match {
    case "$top" => Some(IntegerMustBePositive("$top"))
    case "$skip" => Some(IntegerMustBePositive("$skip"))
    case "modifiedDate" => Some(FormatInvalid("modifiedDate", "yyyy-MM-dd"))
    case _ => None
  }
}.flatten


result.toList

Be warn if you ask for scala-esque way of doing things there are many possibilities.

The map function combined with flatten can be simplified by using flatmap

sealed trait Err
case class IntegerMustBePositive(msg: String) extends Err
case class FormatInvalid(msg: String, format: String) extends Err

val malformedParamNames = Seq[String]("$top", "aa", "$skip", "ccc", "ddd", "modifiedDate")

val result = malformedParamNames.flatMap {
  case "$top" => Some(IntegerMustBePositive("$top"))
  case "$skip" => Some(IntegerMustBePositive("$skip"))
  case "modifiedDate" => Some(FormatInvalid("modifiedDate", "yyyy-MM-dd"))
  case _ => None
}


result

Upvotes: 3

Related Questions