如何在Scala中使用正则表达式进行模式匹配?


124

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

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

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


9
请注意:在Scala(和* ML语言)中,模式匹配还有另一个与正则表达式完全不同的含义。

1
您可能想要[a-cA-C]该正则表达式。

2
在斯卡拉2.8,字符串转换为Traversable(像ListArray),如果你想第3个字符,尝试"my string".take(3),第"foo".head
shellholic

Answers:


237

您可以执行此操作,因为正则表达式定义了提取器,但是您需要首先定义正则表达式模式。我无权使用Scala REPL进行测试,但类似的东西应该可以工作。

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

5
请注意,您不能声明捕获组然后再不使用它(即case Pattern()在此处不匹配)
Jeremy Leipzig 2013年

34
请注意,您必须在正则表达式中使用组:val Pattern = "[a-cA-C]".r将不起作用。这是因为区分大小写使用unapplySeq(target: Any): Option[List[String]],它返回匹配的组。
rakensi 2013年

2
这是StringLike上的一个方法,该方法返回Regex
2014年

11
@rakensi号val r = "[A-Ca-c]".r ; 'a' match { case r() => } scala-lang.org/api/current/#scala.util.matching.Regex
som-snytt

3
@JeremyLeipzig忽略群组:val r = "([A-Ca-c])".r ; "C" match { case r(_*) => }
som-snytt'3

120

从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博客文章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 2013年

@Rajish:不知道可能是什么问题。从2.10开始,我答案中的所有内容都是有效的Scala代码。
kiritsuku 2013年

@sschaef:那个case p.firstName.lastName.Map(...模式-我到底是怎么读的?
埃里克·卡普伦2014年

1
@ErikAllik将其读为类似“当'firstName'以'Jo'开头并且'secondName'匹配给定的正则表达式时,匹配成功。” 这更多是Scalas功能的示例,我不会在生产代码中以这种方式编写此用例。顺便说一句,应该将Map的用法替换为List,因为Map是无序的,并且对于更多的值,它不再保证正确的变量与正确的匹配项匹配。
kiritsuku 2014年

1
这对于快速制作原型非常方便,但是请注意,这会在Regex每次检查匹配项时创建一个新实例。这是相当昂贵的操作,涉及到正则表达式模式的编译。
HRJ 2015年

51

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

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

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


25

为了进一步说明Andrew的答案:正则表达式定义提取器这一事实可以很好地使用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.
}

我真的对高帽^感到困惑。我虽然“ ^”的意思是“匹配行的开头”。它与该行的开头不匹配。
Michael Lafayette

@MichaelLafayette:在字符类([])内,插入符号表示否定,因此[^\s]表示“非空白”。
Fabian Steeg

9

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

但是,方便起见,实际Scala代码中的word.firstLetter看起来像:

word(0)

Scala将字符串视为Char的序列,因此,如果出于某种原因您想要显式获取String的第一个字符并进行匹配,则可以使用如下代码:

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

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

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

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

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


3
"Cat"(0).toString可以更清楚地写为"Cat" take 1,恕我直言。
David Winslow

另外(尽管这是一个古老的讨论-我可能正在认真研究):您可以从最后删除“。*”,因为它不会为正则表达式添加任何值。只是“ Cat” .matches(“ ^ [a-cA-C]”)
akauppi 2013年

今天是2.11 ,val r = "[A-Ca-c]".r ; "cat"(0) match { case r() => }
som-snytt'3

嗨帽子(^)是什么意思?
Michael Lafayette

这是一个锚点,意为“起点”(cs.duke.edu/csl/docs/unix_course/intro-73.html)。因此,如果这是行中的第一件事,那么踩hi之后的所有内容都将与该模式匹配。
Janx

9

请注意,@ 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

1
习惯上来说val MY_RE2 = "(foo|bar)".r.unanchored ; "foo123" match { case MY_RE2(_*) => }。更惯用,val re没有大写字母。
som-snytt,2015年

9

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.