Scala模式匹配:如何在列表中匹配元素?

8
可以使用Scala模式匹配重写以下代码吗?
val ls: List[String] = ??? // some list of strings

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

更新:该列表很短(最多4或5项),且只能包含一个寻找值。实际上,列表表示树状结构中的路径。我想要确定路径所代表的子树。因此,我正在寻找路径中的特定节点,如果找到,则返回该子树的标识符。问题是,我要寻找的节点可能在树的不同层次上,因此我不知道它是路径(列表)中的第1个、第2个还是第4个元素。 - Alex Vayda
5个回答

21
您可以像这样在匹配中添加if条件:
ls match {
  case x if x.contains("foo") => // FOO
  case x if x.contains("bar") => // BAR
  case _ => // ELSE
}

然而,这不是最好的方法,因为每个if检查都需要遍历列表,所以这种方法无法很好地扩展。有各种不同的方法来解决这个问题,但我们需要了解更多您的意图,因为通常运行时语义会与您的代码不同(例如,您可以递归遍历列表,寻找"foo"或"bar",但那将假定列表中只有一个)。


我原以为可以那样做,但由于我已经使用了 'if' ,因此 'case' 看起来是多余的。 我想写类似这样的代码 { case _ :: "foo" :: _ => ??? }。 - Alex Vayda

9
您可以使用类似以下功能实现此操作:

function functionName()

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

并且像这样使用:

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

感谢您的回答。在 actionMappings: (String, T)* 中,* 的意思是什么? - Kevin Meredith
2
它允许定义一个方法,该方法具有可变数量的参数,可以作为“Seq”集合访问。http://www.tutorialspoint.com/scala/functions_variable_arguments.htm - Marius Danila

2

正如Frank的回答所述,这是可能的,但如果您采用不正当手段,将会很昂贵。

这取决于您想做什么。您想返回“foo”或“bar”的索引(例如)吗?那么您可以这样做:

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
}

这段代码没有经过测试,但你可以理解其中的思路。


0
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
}

我认为这是最符合Scala风格的方法。在Map中简洁地说明了案例和结果之间的关系,并且您可以根据自己的需要处理多个结果。您确实说过:

列表很短(最多4或5个项目),并且只能包含一个寻找值

但有些人可能需要处理这种可能性。如果您真的不关心多个匹配,可以这样做:

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

在任何一种情况下,它只遍历列表一次。享受吧!


0
如果您需要一些带有优先级的命令执行,我可以建议。
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")
  }

}

在这种具体的实现中,不会返回任何有意义的结果(你只关注副作用),但如果你的情况应该返回一些值,你会发现它被包装在一个Option作为方法的结果。

更新
根据您的额外评论

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

}

这仅在您的命令是互斥的情况下有效,只有一个命令应该在输入列表中。


是的,我的案例应该返回一个值(请参见我对问题的评论)。我喜欢你的方法,尽管它比简单的if-else结构更冗长。但我需要在代码中将我的常量(foo、bar、baz等)列出两次。我有几十个这样的常量,所以我想避免这种情况。 - Alex Vayda

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接