Scala教程–使用scala.util.matching API进行正则表达式,匹配和替换

前言

这是初学者接触Scala的教程的第6部分。 该博客上还有其他文章,您可以在我正在为其创建的计算语言学课程的链接页面上找到这些链接和其他资源。 此外,您可以在“ JCG Java教程”页面上找到本教程和其他教程系列。

这篇文章是关于正则表达式(regexes)的第二篇,对许多编程任务,尤其是对计算语言学任务而言,必不可少。 如果您还没有阅读过,则可能要从有关regexes的第一篇文章开始。 对于它的价值,这篇文章实际上可能对已经相当熟悉Scala但还没有使用过正则表达式的程序员有所帮助:它可能会节省一些时间来弄清楚如何去做已经知道的事情在其他语言中表现很好

使用正则表达式来捕获变量表达式和匹配表达式中的个案的值是一种非常干净,经过深思熟虑且非常有用的特性,它支持Scala语言中的正则表达式。 但是,坦率地说,使用它们进行更复杂的字符串匹配和替换要比使用内置支持正则表达式的语言(例如Perl(在Perl中说了很多代码))简单得多。 *不*希望用于一般编程)。 Scala具有完全的功能,因为您可以完全使用正则表达式,但是您需要通过Regex API使用它。 换句话说,您需要使用许多命令,而并非所有命令都尽可能简单。 (尽管我确实希望在Scala中更自然地支持正则表达式,但这并不是a琐的事情。)

尽管我将在下面使用Regex API来指代我正在做的事情,但我首先要指出的是,这听起来比实际要大得多。 这只是意味着您直接使用scala.util.matching包中的类和对象,而不是使用某些特殊的语法以及与我们在上一篇文章中看到的Scala模式匹配的集成。

更广泛的匹配

首先,让我们做上篇文章中的模式匹配操作,但是现在使用Regex类及其可用的方法来达到相同的目的。 然后,我们可以开始寻找多个匹配项并进行替换。

回顾一下,回顾一下名称正则表达式以及我们如何使用它基于匹配给定字符串来初始化一组变量。

scala> val Name = """(Mr|Mrs|Ms)\. ([A-Z][a-z]+) ([A-Z][a-z]+)""".r
Name: scala.util.matching.Regex = (Mr|Mrs|Ms)\. ([A-Z][a-z]+) ([A-Z][a-z]+)
 
scala> val smith = "Mr. John Smith"
smith: java.lang.String = Mr. John Smith
 
scala> val Name(title, first, last) = smith
title: String = Mr
first: String = John
last: String = Smith

与其以这种方式进行操作,不如使用API​​方法。 我们首先使用正则表达式查找匹配项(如果有)。 Regex的findAllIn方法为我们完成了此任务。

scala> val matchesFound = Name.findAllIn(smith)
matchesFound: scala.util.matching.Regex.MatchIterator = non-empty iterator

结果是一个迭代器 ,它是一个类似于列表的对象,您可以使用for表达式和foreach遍历其元素,使用map转换其值,等等。

scala> matchesFound.foreach(println)
Mr. John Smith

但是,与列表不同,您只能一次执行此操作。 如下所示,迭代一次之后,它的元素就用完了。

scala> val matchesFound = Name.findAllIn(smith)
matchesFound: scala.util.matching.Regex.MatchIterator = non-empty iterator
 
scala> matchesFound.foreach(println)
Mr. John Smith
 
scala> matchesFound.foreach(println)

另一个区别是您不能直接索引其元素。

scala> val matchesFound = Name.findAllIn(smith)
matchesFound: scala.util.matching.Regex.MatchIterator = non-empty iterator
 
scala> matchesFound(0)
<console>:11: error: scala.util.matching.Regex.MatchIterator does not take parameters
matchesFound(0)
^

如果您希望这样做,则只需在MatchIterator上调用toList 即可

scala> val matchList = Name.findAllIn(smith).toList
matchList: List[String] = List(Mr. John Smith)
 
scala> matchList.foreach(println)
Mr. John Smith
 
scala> matchList.foreach(println)
Mr. John Smith

在本教程的其余部分中,我主要将匹配结果作为列表使用。 但是,请注意,在进行编程时,应考虑是否确实需要执行此操作—通常,迭代器就足够了,并且具有效率更高的优点。

上面请注意,我们拥有的是List [String] 。 这意味着我们可以看到字符串的哪些部分匹配,其中可能包括多个匹配项。

scala> val sentence = "Mr. John Smith said hello to Ms. Jane Hill and then to Mr. Bill Brown."
sentence: java.lang.String = Mr. John Smith said hello to Ms. Jane Hill and then to Mr. Bill Brown.
 
scala> val matchList = Name.findAllIn(sentence).toList
matchList: List[String] = List(Mr. John Smith, Ms. Jane Hill, Mr. Bill Brown)

这在许多情况下将很有用,但不允许我们访问正则表达式中定义的匹配组。 为此,我们需要使用matchData方法,该方法将MatchIterator(将String作为其元素)转换为Iterator [Match] (将Match对象作为其元素)。

scala> val matchList = Name.findAllIn(smith).matchDatamatchList: java.lang.Object with Iterator[scala.util.matching.Regex.Match] = non-empty iterator

让我们将其转换为列表,然后获取第一个元素。

scala> val matchList = Name.findAllIn(smith).matchData.toList
matchList: List[scala.util.matching.Regex.Match] = List(Mr. John Smith)
 
scala> val firstMatch = matchList(0)
firstMatch: scala.util.matching.Regex.Match = Mr. John Smith

这个Match对象包含我们可以使用group方法访问的捕获组。 第一个索引为0,返回整个匹配项,其余索引访问捕获的组。

scala> firstMatch.group(0)
res8: String = Mr. John Smith
 
scala> val title = firstMatch.group(1)
title: String = Mr
 
scala> val first = firstMatch.group(2)
first: String = John
 
scala> val last = firstMatch.group(3)
last: String = Smith

通过将它们打包为元组,我们可以更接近原始模式匹配变量分配。

scala> val (title, first, last) = (firstMatch.group(1), firstMatch.group(2), firstMatch.group(3))
title: String = Mr
first: String = John
last: String = Smith

更新 :使用范围1到3并在该范围上映射firstMatch.group,是一种更简洁的方法。 这将创建一个Seq(uence),我们可以对其进行模式匹配。 (感谢@missingfaktor。)

val Seq(title, first, last) = 1 to 3 map firstMatch.group

这应该说明为什么Scala在模式匹配中对Regexes的支持非常好。 使用API​​所获得的是能够匹配字符串中某个模式的多个实例,然后即时使用Match结果执行计算的能力。 例如,让我们返回其中有多个名称的句子,并使用Name regex对其中找到的每个名称问好。

scala> Name.findAllIn(sentence).matchData.foreach(m => println("Hello, " + m.group(0)))
Hello, Mr. John Smith
Hello, Ms. Jane Hill
Hello, Mr. Bill Brown

当然,您可以选择仅打印名称的子部分,例如标题和姓氏。

scala> Name.findAllIn(sentence).matchData.foreach(m => println("Hello, " + m.group(1) + ". " + m.group(3)))
Hello, Mr. Smith
Hello, Ms. Hill
Hello, Mr. Brown

或者您可以过滤结果,例如仅过滤先生的,然后仅打印名字。

scala> Name.findAllIn(sentence).matchData.filter(m=>m.group(1) == "Mr").foreach(m => println("Hello, " + m.group(2)))
Hello, John
Hello, Bill

请注意,在上面的几行中,我没有将MatchIterator转换为List,因为我很高兴只浏览了一次列表并执行了一些操作。

执行替代

您获得的另一件事是使用正则表达式将一次表达式替换为另一类的能力。 例如,假设(出于某种奇怪的原因)您想颠倒每个人的名字,以使“ John Smith先生 ”成为“ Smith John先生 ”。 这可以通过使用Regex方法replaceAllIn来实现,该方法带有两个参数:第一个是原始字符串,第二个是使用Match对象并返回String的函数。

scala> val swapped = Name.replaceAllIn(sentence, m => m.group(1) + ". " + m.group(3) + " " + m.group(2))
swapped: String = Mr. Smith John said hello to Ms. Hill Jane and then to Mr. Brown Bill.

上面的变量m依次引用所标识的每个Match对象。 这意味着我们可以像以前一样访问组。 最初可能感到奇怪的是,匿名函数m => m.group(1)+“。 “ + m.group(3)+” + m.group(2)是一个参数。 它与以下内容没有太大区别,在以下内容中,我们首先创建一个命名函数,然后将其作为参数传递。

scala> def swapFirstLast = (m: scala.util.matching.Regex.Match) => m.group(1) + ". " + m.group(3) + " " + m.group(2)
swapFirstLast: (util.matching.Regex.Match) => java.lang.String
 
scala> val swapped = Name.replaceAllIn(sentence, swapFirstLast)swapped: String = Mr. Smith John said hello to Ms. Hill Jane and then to Mr. Brown Bill.

注意,既然我们已经定义了它,我们就可以使用相同的函数将findAllIn返回的Matchs映射到它们的交换版本。

scala> val swappedNames = Name.findAllIn(sentence).matchData.map(swapFirstLast).toList
swappedNames: List[java.lang.String] = List(Mr. Smith John, Ms. Hill Jane, Mr. Brown Bill)

区别在于,使用findAllIn可以为我们本身提供Match结果,而replaceAllIn可以在String中原位替换它们。 是否需要做一个还是另一个取决于您的编程需求。

使用Regex API确定整个字符串是否匹配

如果您只是想知道整个给定的字符串是否与Regex匹配,那么不幸的是Scala为您提供了一种round回的方法。 首先,这里是语法,测试Name是否在变量smith句子上匹配。

scala> Name.pattern.matcher(smith).matches
res21: Boolean = true
 
scala> Name.pattern.matcher(sentence).matches
res22: Boolean = false

因此, 句子不匹配(尽管其中包含三个名称),因为整个字符串不是与Name的单个匹配。

这里发生的是,我们实际上是使用Java中定义的类来处理正则表达式。 首先,我们获得与scala.util.matching.Regex对象关联的java.util.regex.Pattern对象。

scala> Name.pattern
res16: java.util.regex.Pattern = (Mr|Mrs|Ms)\. ([A-Z][a-z]+) ([A-Z][a-z]+)

然后,我们使用该模式来获取字符串的java.util.regex.Matcher

scala> Name.pattern.matcher(smith)
res17: java.util.regex.Matcher = java.util.regex.Matcher[pattern=(Mr|Mrs|Ms)\. ([A-Z][a-z]+) ([A-Z][a-z]+) region=0,14 lastmatch=]

匹配器类有一个匹配的方法,它告诉我们是否有该串的匹配与否。

scala> Name.pattern.matcher(smith).matches
res18: Boolean = true

因此,long不休,但您可以做到。

注意 :还有另一种使用Scala的标准模式匹配范例来实现此目的的方法,该范例在上一则正则表达式中讨论过。

scala> smith match { case Name(_,_,_) => true; case _ => false }
res23: Boolean = true
 
scala> sentence match { case Name(_,_,_) => true; case _ => false }
res24: Boolean = false

但是,这需要指定捕获组的额外工作,而捕获组无论如何都将被丢弃。

用第二个正则表达式进行简单替换

还有另一个replaceAllIn方法,该方法将定义(相当)标准的常规表达式替换的String作为其第二个参数,而不是从Matches到Strings的函数。 该自变量定义了与Perl编程语言中的标准s ///替代中使用的正则表达式类似的正则表达式,例如以下表达式 ,它转换为类似“ xyzaaaabbb123 ”,“ int“ xyzbbbaaaa123 “的字符串。

s/(a+)(b+)/\2\1/

与Perl(与Jurafsky和Martin的书中讨论的语法相同)不同,Scala使用$ 1$ 2等。作为示例,请考虑我们之前所做的姓氏交换。 在此重复:

scala> val swapped = Name.replaceAllIn(sentence, m => m.group(1) + ". " + m.group(3) + " " + m.group(2))
swapped: String = Mr. Smith John said hello to Ms. Hill Jane and then to Mr. Brown Bill.

通过使用引用组的$ n变量构造替换字符串,可以更轻松地获得完全相同的效果。

scala> val swapped2 = Name.replaceAllIn(sentence, "$1. $3 $2")
swapped2: String = Mr. Smith John said hello to Ms. Hill Jane and then to Mr. Brown Bill.

这比上面的m.group()样式更加简洁和易读,因此在这种情况下更可取。 但是,有时您可能需要对每个组中的值进行一些更有趣的处理,例如将标题更改为另一种语言并仅输出名字的首字母缩写:例如,“ John Smith先生 ”将变为“ Sr史密斯 ”和“ 简·希尔夫人 ”将成为“斯拉。 J. Hill”。 对我来说,尚不清楚如何用$ n替换来做到这一点(如果有些读者知道,请告诉我)。 要使用Match => String函数做到这一点,很简单。 首先,让我们定义一个将标题从英语映射到西班牙语的方法。

def engTitle2Esp (title: String) = title match {
  case "Mr" => "Sr"
  case "Mrs" => "Sra"
  case "Ms" => "Srta"
}

然后,我们使用engTitle2Esp(m.group(1) 通过该函数传递m.group(1) ,并通过将其索引为m.group(2)(0)来获取组2的第一个字符。

scala> val spanishized = Name.replaceAllIn(sentence, m => engTitle2Esp(m.group(1)) + ". " + m.group(2)(0) + ". " + m.group(3))
spanishized: String = Sr. J. Smith said hello to Srta. J. Hill and then to Sr. B. Brown.

这样您就可以很好地控制如何处理替换。

参考: Scala入门程序员的第一步,来自BCG博客的JCG合作伙伴 Jason Baldridge的 第6部分

相关文章 :


翻译自: https://www.javacodegeeks.com/2011/10/scala-tutorial-regular-expressions_05.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值