【问题标题】:String pattern matching best practice字符串模式匹配最佳实践
【发布时间】:2011-10-08 18:19:58
【问题描述】:

以下是不起作用的代码,但它描述了我想要做什么。

您能否推荐解决此问题的最佳方法?

def resolveDriver(url: String) = {
  url match {
    case url.startsWith("jdbc:mysql:") => "com.mysql.jdbc.Driver"
    case url.startsWith("jdbc:postgresql:") => "org.postgresql.Driver"
    case url.startsWith("jdbc:h2:") => "org.h2.Driver"
    case url.startsWith("jdbc:hsqldb:") => "org.hsqldb.jdbcDriver"
    case _ => throw new IllegalArgumentException
  }
}

【问题讨论】:

  • 如果匹配的部分恰好是整个协议,请参阅this question 了解解决此问题的另一种方法。

标签: scala pattern-matching


【解决方案1】:

就语法而言,你可以只修改一点你的 case 语句:

case url if url.startsWith("jdbc:mysql:") => "com.mysql.jdbc.Driver"

这只是将值url 绑定到模式表达式(也是url)并添加一个带有测试的保护表达式。这应该使代码编译。

为了让它更像 scala,你可以返回一个 Option[String] (我删除了几个子句,因为它只是为了说明):

def resolveDriver(url: String) = url match {
  case u if u.startsWith("jdbc:mysql:") => Some("com.mysql.jdbc.Driver")
  case u if u.startsWith("jdbc:postgresql:") => Some("org.postgresql.Driver")
  case _ => None
}

除非你想管理异常。

【讨论】:

  • 谢谢!这正是我一直在寻找的!我很高兴我问了这个问题,因为我已经准备好为此创建一个案例类,这听起来有点复杂。我也感谢你纠正我抛出异常的问题。
【解决方案2】:

这是另一种方式。将所有映射存储在一个映射中,然后使用collectFirst 方法查找匹配项。 collectFirst 的类型签名是:

def TraversableOnce[A].collectFirst[B](pf: PartialFunction[A, B]): Option[B]

用法:

scala> val urlMappings = Map("jdbc:mysql:" -> "com.mysql.jdbc.Driver", "jdbc:postgresql:" -> "org.postgresql.Driver")
urlMappings: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(jdbc:mysql: -> com.mysql.jdbc.Drive
r, jdbc:postgresql: -> org.postgresql.Driver)

scala> val url = "jdbc:mysql:somestuff"
url: java.lang.String = jdbc:mysql:somestuff

scala> urlMappings collectFirst { case(k, v) if url startsWith k => v }
res1: Option[java.lang.String] = Some(com.mysql.jdbc.Driver)

【讨论】:

  • 谢谢,但你提出的不是match的抽象吗?
  • @mojojojo:不完全是。 match 之后的case 表达式集构成PartialFunctioncollectFirst 是一个接受 PartialFunction 的方法,循环遍历集合,并返回在 Some 中找到的第一个匹配项。 (如果未找到匹配项,则返回 None。)
【解决方案3】:

Scala 2.13 开始,可以通过unapplying a string interpolator 模式匹配Strings:

val s"jdbc:$dialect:$rest" = "jdbc:mysql:whatever"
// dialect: String = "mysql"
// rest: String = "whatever"

那么在我们的例子中,只需使用Map 将提取的值(sql 方言)映射到适当的驱动程序:

val drivers = Map(
  "postgresql" -> "org.postgresql.Driver",
  "mysql"      -> "com.mysql.jdbc.Driver",
  "h2"         -> "org.h2.Driver"
)
val driver = drivers(dialect)
// driver: String = "com.mysql.jdbc.Driver"

如果您期望输入格式错误,您也可以使用匹配语句:

"jdbc:postgresql:something" match {
  case s"jdbc:$dialect:$rest" => Some(dialect)
  case _                      => None
}
// Option[String] = Some("postgresql")

【讨论】:

  • 所以我认为字符串插值器的匹配也只适用于 >=2.13?
【解决方案4】:
import PartialFunction._  // condOpt

val drivers = Map( 
  "mysql" -> "mysql driver",
  "h2" -> "h2 driver" 
  // ... 
)
val pattern = "^jdbc:(\\w+):.*".r

def resolveDriver(url: String) = condOpt(url) { 
  case pattern(dbms) => drivers.get(dbms) 
}.flatten.getOrElse(throw new IllegalArgumentException)

【讨论】:

  • 纯代码的答案不如带注释的代码有用,尤其是对于这么古老(超过 7 年)的问题,指出您的答案与所有​​以前的答案有何不同是很有用的。
  • 对不起,但如果其他人应该(也)阅读以前的答案,那么我发布的代码是不言而喻的 - 映射驱动相关部分,完全模式匹配捕获 dbms 部分(最终else),然后是部分函数,​​它为我们节省了 None 的声明。所有这一切都考虑到了最初的问题和代码。我这样做。好不好,跟别人有没有区别,只是一个意见。谢谢。
  • 抱歉泛滥,但我不明白你关于这个问题的年龄的观点。年龄如何/为什么重要?新鲜的问题需要更少的阐述?这样的问题不是老生常谈吗?
  • 5-对于新手来说,遇到一个老问题并在不阅读以前的答案的情况下回答它并不少见(太常见了)。代码随附的注释可以清楚地表明您不属于该类别。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-10
  • 2017-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多