问题
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