【问题标题】:Scala pattern matching: How to match on an element inside a list?Scala模式匹配:如何匹配列表中的元素?
【发布时间】:2015-10-31 06:13:23
【问题描述】:

是否可以使用 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 项),并且只能包含搜索值之一。列表实际上表示树状结构中的路径。我想确定路径地址是哪个子树。所以我正在寻找路径中的特定节点,如果找到,我会返回该子树的标识符。问题是我正在寻找的节点可能位于树中的不同级别,所以我不知道它是路径(列表)中的第一个、第二个还是第四个元素。

标签: list scala pattern-matching


【解决方案1】:

您可以将if 条件添加到这样的匹配中:

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

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

【讨论】:

  • 我以为是这样做的,但“案例”看起来是多余的,因为无论如何我都有那些“如果”。我想写类似 { case _ :: "foo" :: _ => ??? }
【解决方案2】:

你可以使用类似的函数来实现它

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
)

【讨论】:

【解决方案3】:

正如弗兰克的回答所说,如果你以肮脏的方式去做,这是可能的,但代价高昂。

这取决于你想做什么。你想返回那个“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
}

此代码未经测试,但您明白了。

【讨论】:

    【解决方案4】:

    如果您需要某种优先执行命令我可以建议

    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
    
    }
    

    这只有在你的命令是独占的时候才有效,只有一个应该在input 列表中

    【讨论】:

    • 是的,我的案例应该返回一个值(请参阅我对问题的评论)。我喜欢你的方法,尽管它比简单的 if-else 构造要简洁得多。但我需要在代码中列出两次常量(foo、bar、baz 等)。我有几十个,所以我会避免这种情况。
    【解决方案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
    }
    

    我相信这是最 Scalaesque 的方式。案例及其结果之间的关系在Map 中简明扼要地说明,您可以选择处理多个结果,但您需要。你确实说过

    列表很短(最多 4 或 5 项),并且只能包含其中一个搜索值

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

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

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

    【讨论】:

      猜你喜欢
      • 2015-05-30
      • 2018-10-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-14
      • 2017-10-14
      • 1970-01-01
      • 1970-01-01
      • 2014-08-27
      相关资源
      最近更新 更多