【问题标题】:How to pattern match using regular expression in Scala?如何在 Scala 中使用正则表达式进行模式匹配?
【发布时间】:2011-06-05 21:58:39
【问题描述】:

我希望能够在单词的第一个字母和“ABC”等组中的一个字母之间找到匹配项。在伪代码中,这可能类似于:

case Process(word) =>
   word.firstLetter match {
      case([a-c][A-C]) =>
      case _ =>
   }
}

但是如何在 Scala 而不是 Java 中获取第一个字母?如何正确表达正则表达式?是否可以在 case class 中执行此操作?

【问题讨论】:

  • 警告:在 Scala(和 *ML 语言)中,模式匹配有另一个与正则表达式非常不同的含义。
  • 您可能需要[a-cA-C] 来表示该正则表达式。
  • 在 scala 2.8 中,字符串被转换为Traversable(如ListArray),如果你想要前3个字符,尝试"my string".take(3),第一个"foo".head

标签: regex scala


【解决方案1】:

您可以这样做,因为正则表达式定义了提取器,但您需要先定义正则表达式模式。我无法访问 Scala REPL 来测试它,但这样的东西应该可以工作。

val Pattern = "([a-cA-C])".r
word.firstLetter match {
   case Pattern(c) => c bound to capture group here
   case _ =>
}

【讨论】:

  • 注意你不能声明一个捕获组然后不使用它(即 case Pattern() 在这里不匹配)
  • 请注意,您必须在正则表达式中使用组:val Pattern = "[a-cA-C]".r 将不起作用。这是因为 match-case 使用 unapplySeq(target: Any): Option[List[String]],它返回匹配的组。
  • 这是StringLike 上的一个方法,它返回一个Regex
  • @rakensi 编号val r = "[A-Ca-c]".r ; 'a' match { case r() => } scala-lang.org/api/current/#scala.util.matching.Regex
  • @JeremyLeipzig 忽略组:val r = "([A-Ca-c])".r ; "C" match { case r(_*) => }
【解决方案2】:

从 2.10 版本开始,可以使用 Scala 的字符串插值功能:

implicit class RegexOps(sc: StringContext) {
  def r = new util.matching.Regex(sc.parts.mkString, sc.parts.tail.map(_ => "x"): _*)
}

scala> "123" match { case r"\d+" => true case _ => false }
res34: Boolean = true

更好的是可以绑定正则表达式组:

scala> "123" match { case r"(\d+)$d" => d.toInt case _ => 0 }
res36: Int = 123

scala> "10+15" match { case r"(\d\d)${first}\+(\d\d)${second}" => first.toInt+second.toInt case _ => 0 }
res38: Int = 25

也可以设置更详细的绑定机制:

scala> object Doubler { def unapply(s: String) = Some(s.toInt*2) }
defined module Doubler

scala> "10" match { case r"(\d\d)${Doubler(d)}" => d case _ => 0 }
res40: Int = 20

scala> object isPositive { def unapply(s: String) = s.toInt >= 0 }
defined module isPositive

scala> "10" match { case r"(\d\d)${d @ isPositive()}" => d.toInt case _ => 0 }
res56: Int = 10

关于Dynamic 的可能性的一个令人印象深刻的例子显示在博客文章中Introduction to Type Dynamic

object T {

  class RegexpExtractor(params: List[String]) {
    def unapplySeq(str: String) =
      params.headOption flatMap (_.r unapplySeq str)
  }

  class StartsWithExtractor(params: List[String]) {
    def unapply(str: String) =
      params.headOption filter (str startsWith _) map (_ => str)
  }

  class MapExtractor(keys: List[String]) {
    def unapplySeq[T](map: Map[String, T]) =
      Some(keys.map(map get _))
  }

  import scala.language.dynamics

  class ExtractorParams(params: List[String]) extends Dynamic {
    val Map = new MapExtractor(params)
    val StartsWith = new StartsWithExtractor(params)
    val Regexp = new RegexpExtractor(params)

    def selectDynamic(name: String) =
      new ExtractorParams(params :+ name)
  }

  object p extends ExtractorParams(Nil)

  Map("firstName" -> "John", "lastName" -> "Doe") match {
    case p.firstName.lastName.Map(
          Some(p.Jo.StartsWith(fn)),
          Some(p.`.*(\\w)$`.Regexp(lastChar))) =>
      println(s"Match! $fn ...$lastChar")
    case _ => println("nope")
  }
}

【讨论】:

  • 非常喜欢这个答案,但是当试图在 REPL 之外使用它时它被锁定(即在 REPL 中工作的完全相同的代码在运行应用程序中不起作用)。使用$ 符号作为行尾模式也存在问题:编译器抱怨缺少字符串终止。
  • @Rajish:不知道可能是什么问题。自 2.10 以来,我的答案中的所有内容都是有效的 Scala 代码。
  • @sschaef:case p.firstName.lastName.Map(... 模式——我到底是怎么读到的?
  • @ErikAllik 将其解读为“当 'firstName' 以 'Jo' 开头并且 'secondName' 匹配给定的正则表达式时,匹配成功”。这更像是 Scalas 功能的一个例子,我不会在生产代码中以这种方式编写这个用例。顺便说一句,Map 的使用应该由 List 代替,因为 Map 是无序的,并且对于更多值,不再保证正确的变量与正确的匹配器匹配。
  • 这对于快速原型制作非常方便,但请注意,每次检查匹配时都会创建一个Regex 的新实例。这是一个相当昂贵的操作,涉及到正则表达式模式的编译。
【解决方案3】:

正如 delnan 所指出的,Scala 中的 match 关键字与正则表达式无关。要确定字符串是否与正则表达式匹配,可以使用String.matches 方法。要确定一个字符串是以小写还是大写的 a、b 或 c 开头,正则表达式如下所示:

word.matches("[a-cA-C].*")

您可以将此正则表达式读作“字符 a、b、c、A、B 或 C 之一,后跟任何字符”(. 表示“任何字符”,* 表示“零次或多次”,所以“.*”是任何字符串)。

【讨论】:

    【解决方案4】:

    稍微扩展Andrew's answer:正则表达式定义提取器的事实可用于分解由正则表达式匹配的子字符串,使用Scala的模式匹配非常好,例如:

    val Process = """([a-cA-C])([^\s]+)""".r // define first, rest is non-space
    for (p <- Process findAllIn "aha bah Cah dah") p match {
      case Process("b", _) => println("first: 'a', some rest")
      case Process(_, rest) => println("some first, rest: " + rest)
      // etc.
    }
    

    【讨论】:

    • 我真的被高帽子弄糊涂了^。我虽然“^”的意思是“匹配行首”。它与行首不匹配。
    • @MichaelLafayette:在字符类 ([]) 内部,插入符号表示否定,因此 [^\s] 表示“非空白”。
    【解决方案5】:

    String.matches 是在正则表达式意义上进行模式匹配的方法。

    但顺便说一句,实际 Scala 代码中的 word.firstLetter 看起来像:

    word(0)
    

    Scala 将字符串视为 Char 的序列,因此如果出于某种原因您想显式获取字符串的第一个字符并匹配它,您可以使用如下内容:

    "Cat"(0).toString.matches("[a-cA-C]")
    res10: Boolean = true
    

    我并不是建议将其作为进行正则表达式模式匹配的一般方法,但它与您提出的首先查找字符串的第一个字符然后将其与正则表达式匹配的方法一致。

    编辑: 需要明确的是,正如其他人所说,我这样做的方式是:

    "Cat".matches("^[a-cA-C].*")
    res14: Boolean = true
    

    只是想展示一个尽可能接近您的初始伪代码的示例。干杯!

    【讨论】:

    • "Cat"(0).toString 可以更清楚地写成"Cat" take 1,恕我直言。
    • 另外(虽然这是一个古老的讨论 - 我可能是在挖坟墓):您可以从末尾删除“。*”,因为它不会为正则表达式添加任何值。只是 "Cat".matches("^[a-cA-C]")
    • 今天 2.11,val r = "[A-Ca-c]".r ; "cat"(0) match { case r() =&gt; }.
    • 嗨帽 (^) 是什么意思?
    • 这是一个锚点,意思是“行首”(cs.duke.edu/csl/docs/unix_course/intro-73.html)。因此,如果它是行中的第一件事,则 hi hat 后面的所有内容都将匹配该模式。
    【解决方案6】:

    请注意,@AndrewMyers 答案中的方法将 整个 字符串与正则表达式匹配,其效果是使用 ^$ 在字符串的两端锚定正则表达式。示例:

    scala> val MY_RE = "(foo|bar).*".r
    MY_RE: scala.util.matching.Regex = (foo|bar).*
    
    scala> val result = "foo123" match { case MY_RE(m) => m; case _ => "No match" }
    result: String = foo
    
    scala> val result = "baz123" match { case MY_RE(m) => m; case _ => "No match" }
    result: String = No match
    
    scala> val result = "abcfoo123" match { case MY_RE(m) => m; case _ => "No match" }
    result: String = No match
    

    最后没有.*

    scala> val MY_RE2 = "(foo|bar)".r
    MY_RE2: scala.util.matching.Regex = (foo|bar)
    
    scala> val result = "foo123" match { case MY_RE2(m) => m; case _ => "No match" }
    result: String = No match
    

    【讨论】:

    • 惯用语,val MY_RE2 = "(foo|bar)".r.unanchored ; "foo123" match { case MY_RE2(_*) =&gt; }。更通俗地说,val re 没有全部大写。
    【解决方案7】:

    首先我们应该知道正则表达式可以单独使用。这是一个例子:

    import scala.util.matching.Regex
    val pattern = "Scala".r // <=> val pattern = new Regex("Scala")
    val str = "Scala is very cool"
    val result = pattern findFirstIn str
    result match {
      case Some(v) => println(v)
      case _ =>
    } // output: Scala
    

    其次,我们应该注意到,将正则表达式与模式匹配结合起来会非常强大。这是一个简单的例子。

    val date = """(\d\d\d\d)-(\d\d)-(\d\d)""".r
    "2014-11-20" match {
      case date(year, month, day) => "hello"
    } // output: hello
    

    其实正则表达式本身已经很强大了;我们唯一需要做的就是通过 Scala 让它变得更强大。以下是 Scala 文档中的更多示例:http://www.scala-lang.org/files/archive/api/current/index.html#scala.util.matching.Regex

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多