Scala pattern matching: How to match on an element inside a list?

末鹿安然 提交于 2021-02-18 22:28:20

问题


Is it possible to rewrite the following code using Scala pattern matching?

val ls: List[String] = ??? // some list of strings

val res = if (ls.contains("foo")) FOO
     else if (ls.contains("bar")) BAR
     else SOMETHING_ELSE

回答1:


You could implement this using a function like

def onContains[T](xs: Seq[String], actionMappings: (String, T)*): Option[T] = {
  actionMappings collectFirst {
    case (str, v) if xs contains str => v
  }
}

And use it like this:

val x = onContains(items,
  "foo" -> FOO,
  "bar" -> BAR
)



回答2:


You can add if conditions to matches like this:

ls match {
  case x if x.contains("foo") => // FOO
  case x if x.contains("bar") => // BAR
  case _ => // ELSE
}

However, it is not the nicest way, as each if check needs to traverse the list, so this doesn't scale well. There are various different ways to deal with this problem, but we would need to know more about your intensions, as normally the runtime semantics would differ from your code (for example, you could recursively traverse the list looking for either "foo" or "bar", but that would assume you only have either one in the list).




回答3:


As Frank's answer says, it is possible, but expensive if you would do it the dirty way.

It depends on what you want to do. Do you want to return the index of that "foo" or "bar" (for example)? Then you would do something like this:

def indexOf[T]: (List[T], T) => Int = (ls, x) => ls match {
    case Nil => -1
    case e::es if( e.equals(x) ) => 0
    case e::es => val i = indexOf( es, x ); if( i < 0 ) i else i + 1
}

This code is not tested, but you get the idea.




回答4:


If what you need is some sort of command execution with prioritization I can suggest

def executeCommand(input: List[String]): Option[Unit] = {

  val priorities = Map(
    "foo" -> 1,
    "bar" -> 2,
    "baz" -> 3) withDefault(_ => 4)

  def extractCommand(cmds: List[String]): Option[String] = 
    (cmds sortBy priorities).headOption

  extractCommand(input) map {
    case "foo" => println("found foo")
    case "bar" => println("found bar")
    case "baz" => println("found baz")
    case _     => println("no known command")
  }

}

In this specific implementation no meaningful result is returned (you only go for side effects), but if your cases should return some value, you would find it wrapped in an Option as the method result.


UPDATED
based on your additional comment

def execute(input: List[String]): Option[String] = {
  val commands: PartialFunction[String, String] = {
    case "foo" => "result for foo"
    case "bar" => "result for bar"
    case "baz" => "result for baz"
  }

  (input find commands.isDefinedAt) map commands

}

This works only if your commands are exclusive, only one should be in the input List




回答5:


val ls = List[String]("bar", "foo", "baz")  // stuff to check against
val mappy = Map[String, String]("foo" -> "FOO", "bar" -> "BAR")  // conversions go here

val res = ls.flatMap{
  case x: String => mappy.get(x)
} match {
  case Seq(y) => y
  case Nil => "SOMETHING_ELSE"  // the `else` case goes here
  case _ => new Exception("could be more than one thing")  // handle this however you want
}

I believe this to be the most Scalaesque way to do it. The relationship between the cases and their results is concisely stated in the Map, and you have the option of dealing with multiple results however you want. You did say

The list is short (up to 4 or 5 items) and can only contain one of the seeking values

But some may need to deal with that possibility. If you really, really don't care about multiple matches, you can do

val res = ls.flatMap(mappy.get).headOption.getOrElse("SOMETHING_ELSE")

In either case, it traverses the list only once. Enjoy!



来源:https://stackoverflow.com/questions/14643567/scala-pattern-matching-how-to-match-on-an-element-inside-a-list

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!