Scala正则表达式:如何将匹配结果返回为数组或列表

17

有没有一种简单的方法将正则表达式匹配结果返回为数组?
以下是我在2.7.7中尝试的方法:

val s = """6 1 2"""
val re = """(\d+)\s(\d+)\s(\d+)""".r
for (m <- re.findAllIn (s)) println (m) // prints "6 1 2"
re.findAllIn (s).toList.length // 3? No! It returns 1!

但是我之后尝试了:

s match {
  case re (m1, m2, m3) => println (m1)
}

这很好!m1为6,m2为1等都可以正常工作。

接着我发现了一些让我更加困惑的东西:

val mit = re.findAllIn (s)
println (mit.toString)
println (mit.length)
println (mit.toString)

这将打印:

non-empty iterator
1
empty iterator

"length"方法在某种程度上修改了该迭代器的状态,这是怎么回事?


你调用findAllIn(s)方法匹配了整个字符串,因此你得到的列表不是List(6 1 2),而是长度为1的List("6 1 2")。 - Mitch Blevins
3个回答

29

首先要明白的是,findAllIn 返回的是一个 Iterator。一个 Iterator 是一个只能消费一次的可变对象。对它进行的任何操作都会改变它。如果您对迭代器不熟悉,请阅读相关文档。如果您想要重复使用该迭代器,则需要将 findAllIn 的结果转换为 List,仅使用该列表。

现在,看起来您想要的是所有匹配的分组,而不是所有匹配项。方法 findAllIn 将返回字符串上可找到的 整个 正则表达式的所有匹配项。例如:

scala> val s = """6 1 2, 4 1 3"""
s: java.lang.String = 6 1 2, 4 1 3

scala> val re = """(\d+)\s(\d+)\s(\d+)""".r
re: scala.util.matching.Regex = (\d+)\s(\d+)\s(\d+)

scala> for(m <- re.findAllIn(s)) println(m)
6 1 2
4 1 3

注意到有两个匹配项,它们都不包括字符串中间的", ",因为这不是任何匹配项的一部分。

如果你想要捕获组,可以像这样获取它们:

scala> val s = """6 1 2"""
s: java.lang.String = 6 1 2

scala> re.findFirstMatchIn(s)
res4: Option[scala.util.matching.Regex.Match] = Some(6 1 2)

scala> res4.get.subgroups
res5: List[String] = List(6, 1, 2)

或者,使用findAllIn,像这样:

scala> val s = """6 1 2"""
s: java.lang.String = 6 1 2

scala> for(m <- re.findAllIn(s).matchData; e <- m.subgroups) println(e)
6
1
2

matchData 方法会生成一个 Iterator,返回的是 Match 而不是 String


我不明白为什么findAllIn返回一个MatchIterator,而不是只返回一个Iterator[Match]。这很令人困惑,因为一些集合级别的方法无法使用。例如,如果您尝试像re.findAllIn(s).subgroups这样调用subgroups,它将失败,尽管re.findAllIn(s).groupCount非零(2.11.x)。 - Luciano
@Luciano MatchIteratorIterator [Match]之间的区别在于前者也是MatchData。然而,scaladoc警告:所有从scala.util.matching.Regex.MatchData继承的方法在匹配器初始化之前都会抛出java.lang.IllegalStateException。可以通过调用hasNextnext()或导致调用这些方法来初始化匹配器,例如通过调用toString或迭代遍历迭代器的元素。 - Daniel C. Sobral

10

unapplySeq 和 findAllIn 处理多个匹配组的方式不同。findAllIn 会在字符串上扫描你的模式并返回每一个匹配的子串(如果匹配成功,则前进到匹配结束的位置;如果匹配失败,则前进一位)。

举个例子:

scala> val s = "gecko 6 1 2 3 4 5"
scala> re.findAllIn(s).toList
res3: List[String] = List(6 1 2, 3 4 5)

另一方面,unapplySeq 假定与序列完全匹配。

scala> re.unapplySeq(s)
res4: Option[List[String]] = None

如果你想解析一个精确的正则表达式字符串中指定的分组,请使用unapplySeq。如果你想查找与你的正则表达式模式相似的子字符串集合,请使用findAllIn。如果你两者都想做,就自己链起来:

scala> re.findAllIn(s).flatMap(text => re.unapplySeq(text).elements )
res5: List[List[String]] = List(List(6, 1, 2), List(3, 4, 5))

2

试试这个:

  val s = """6 1 2"""
  val re = """\d+""".r
  println(re.findAllIn(s).toList) // List(6, 1, 2)
  println(re.findAllIn(s).toList.length) // 3

如果你真的需要一个单一正则表达式中匹配组的列表:

  val s = """6 1 2"""
  val Re = """(\d+)\s(\d+)\s(\d+)""".r
  s match {  // this is just sugar for calling Re.unapplySeq(s)
      case Re(mg@_*) => println(mg) // List(6, 1, 2)
  }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接