简体   繁体   中英

Scala: how to change this code in functional way?

I have this List for example:

val list = List(
    Map("a" -> "John", "b" -> 20, "c" -> true), 
    Map("a" -> "Derp", "b" -> 10, "c" -> false), 
    Map("b" -> 8, "c" -> true),
    Map("a" -> "asdf", "b" -> 50, "c" -> true)
)

I have this code that process the above list and I want to change it into functional style and in the most efficient way:

var result = ""
breakable {
    for (m <- list) {
        if (m.get("a").isEmpty) {
            result = "Error A"
            break()
        }
        if (m.get("b").isEmpty) {
            result = "Error B"
            break()
        }
    }
}

In above code, if "a" doesn't exist, the if checking is only done once before going out of the loop. If "b" doesn't exist, the checking is done twice.

My attempt to change it to functional code:

val result = (for { 
    m <- list
    a = m.get("a").isEmpty
    b = m.get("b").isEmpty
    if a || b
} yield {
    if (a) 
        "Error A"
    else
        "Error B"
}).headOption.getOrElse("")

It doesn't look good as if "a" doesn't exist, the if checking will be done two times and if "b" doesn't exist, it will be done three times.

Also there is .headOption.getOrElse("") overhead. It will be nice if I can get string result directly.

Anyone can provide better solution?

list.view
    .map(m => if (m.get("a").isEmpty) "Error A" 
              else if (m.get("b").isEmpty) "Error B"
              else "")
    .find(_.nonEmpty)
    .getOrElse("")

Note the view to make it non-strict so it stops after the first failure element.

Slightly neater with if statements as a partial function, not defined if neither "a" or "b" was in the map, and using contains

list.collectFirst{case m if !(m contains "a") => "Error A"
                  case m if !(m contains "b") => "Error B"}
    .getOrElse("")

With index

list.view
    .zipWithIndex
    .map{case (m,i) => if (m.get("a").isEmpty) "Error A:"+ i
                       else if (m.get("b").isEmpty) "Error B:" + i
                       else ""}
    .find(_.nonEmpty)
    .getOrElse("")
    //> res1: String = Error A:2

and using collectFirst

list.zipWithIndex
    .collectFirst{case (m, i) if !(m contains "a") => "Error A:" + i
                  case (m, i) if !(m contains "b") => "Error B:" + i}
    .getOrElse("")

Just for addendum, the other viable solution is by using recursion:

def getError(list: List[Map[String, Any]]): String = {
    if (list.nonEmpty) {
        val m = list.head
        if (m.get("a").isEmpty) 
            "Error A"
        else if (m.get("b").isEmpty)
            "Error B"
        else
            getError(list.tail)
    } else ""
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM