提问人:phbelov 提问时间:5/3/2016 最后编辑:InSyncphbelov 更新时间:10/4/2023 访问量:285131
如何捕获多个重复组?
How to capture multiple repeated groups?
问:
我需要捕获相同模式的多个组。假设,我有以下字符串:
HELLO,THERE,WORLD
我写了以下模式
^(?:([A-Z]+),?)+$
我希望它做的是捕获每个单词,因此第 1 组是:“HELLO”,第 2 组是“THERE”,第 3 组是“WORLD”。我的正则表达式实际捕获的只是最后一个,即“WORLD”。
我在这里测试我的正则表达式,我想将它与 Swift 一起使用(也许 Swift 中有一种方法可以以某种方式获得中间结果,以便我可以使用它们?
我不想使用 .我现在只需要如何捕获与模式匹配的所有组,而不仅仅是最后一个组。split
答:
如果模式中有一个组,则该组中只能获得一个确切的结果。如果捕获组被模式重复(您在周围的非捕获组上使用了量词),则仅存储与其匹配的最后一个值。+
你必须使用你的语言的正则表达式实现函数来查找模式的所有匹配项,然后你必须删除非捕获组的锚点和量词(你也可以省略非捕获组本身)。
或者,展开正则表达式,让模式包含每个要在结果中获取的组的一个捕获组:
^([A-Z]+),([A-Z]+),([A-Z]+)$
评论
([A-Z]+)
只是为了在答复中提供第2段的补充例子。我不确定在一场比赛中获得三个小组而不是使用一个小组进行三场比赛对您来说有多重要。例如,在 groovy 中:
def subject = "HELLO,THERE,WORLD"
def pat = "([A-Z]+)"
def m = (subject =~ pat)
m.eachWithIndex{ g,i ->
println "Match #$i: ${g[1]}"
}
Match #0: HELLO
Match #1: THERE
Match #2: WORLD
我认为你需要这样的东西:
b = "HELLO,THERE,WORLD"
re.findall('[\w]+',b)
在 Python 3 中将返回:
['HELLO', 'THERE', 'WORLD']
评论
re.findall('\w+',b)
短 2 个字符。不需要字符类,因为只有一个表达式
看完 Byte Commander 的回答后,我想介绍一个微小的改进:
您可以生成一个与任一单词匹配的正则表达式,只要您的是预先确定的。例如,如果我想匹配 1 到 3 个单词,则正则表达式:n
n
^([A-Z]+)(?:,([A-Z]+))?(?:,([A-Z]+))?$
将匹配接下来的句子,具有一个、两个或三个捕获组。
HELLO,LITTLE,WORLD
HELLO,WORLD
HELLO
您可以在 Regex101 上看到有关此正则表达式的完整详细说明。
正如我所说,使用您喜欢的语言为您想要的任何组生成此正则表达式非常容易。由于我不是一个敏捷的人,这里有一个红宝石的例子:
def make_regexp(group_regexp, count: 3, delimiter: ",")
regexp_str = "^(#{group_regexp})"
(count - 1).times.each do
regexp_str += "(?:#{delimiter}(#{group_regexp}))?"
end
regexp_str += "$"
return regexp_str
end
puts make_regexp("[A-Z]+")
话虽如此,我建议在这种情况下不要使用正则表达式,根据您的需要,还有许多其他很棒的工具,从简单到一些标记化模式。恕我直言,正则表达式不是其中之一。例如,在 ruby 中,我会使用类似 或split
str.split(",")
str.scan(/[A-Z]+/)
评论
([A-Z]+)((?:,([A-Z]+))?)+
我知道我的答案来晚了,但它今天发生在我身上,我用以下方法解决了它:
^(([A-Z]+),)+([A-Z]+)$
因此,第一组 (([A-Z]+),)
+ 将匹配所有重复模式,但最后一组 ([A-Z]+)
将与最后一组模式匹配。无论字符串中有多少个重复组,这都是动态的。
评论
您实际上有一个捕获组,该捕获组将多次匹配。不是多个捕获组。
JavaScript (JS) 解决方案:
let string = "HI,THERE,TOM";
let myRegexp = /([A-Z]+),?/g; // modify as you like
let match = myRegexp.exec(string); // js function, output described below
while (match != null) { // loops through matches
console.log(match[1]); // do whatever you want with each match
match = myRegexp.exec(string); // find next match
}
语法:
// matched text: match[0]
// match start: match.index
// capturing group n: match[n]
如您所见,这将适用于任意数量的比赛。
对不起,不是 Swift,只是用手头最接近的语言进行的概念验证。
// JavaScript POC. Output:
// Matches: ["GOODBYE","CRUEL","WORLD","IM","LEAVING","U","TODAY"]
let str = `GOODBYE,CRUEL,WORLD,IM,LEAVING,U,TODAY`
let matches = [];
function recurse(str, matches) {
let regex = /^((,?([A-Z]+))+)$/gm
let m
while ((m = regex.exec(str)) !== null) {
matches.unshift(m[3])
return str.replace(m[2], '')
}
return "bzzt!"
}
while ((str = recurse(str, matches)) != "bzzt!") ;
console.log("Matches: ", JSON.stringify(matches))
注意:如果你真的要使用它,你会使用正则表达式匹配函数给出的匹配位置,而不是字符串替换。
主要区别在于重复捕获的组,而不是捕获重复的组。
正如您已经发现的,区别在于重复捕获的组仅捕获最后一次迭代。捕获重复组将捕获所有小版本。
在 PCRE (PHP) 中:
((?:\w+)+),?
Match 1, Group 1. 0-5 HELLO
Match 2, Group 1. 6-11 THERE
Match 3, Group 1. 12-20 BRUTALLY
Match 4, Group 1. 21-26 CRUEL
Match 5, Group 1. 27-32 WORLD
由于所有捕获都位于第 1 组中,因此您只需要进行替换。$1
我使用了这个正则表达式的以下一般形式:
((?:{{RE}})+)
regex101 中的示例
评论
(\w+),?
g
((?:\w,?)+)
HELLO
WORLD
Group 1
((?:\w+)+),?
((?:\w+)+),?
g
((?:\w+)+),?
(\w+),?
- 设计一个与列表的每个特定元素匹配的正则表达式,而不是与整个列表匹配。使用 /g 应用它
- 遍历匹配项,清除任何垃圾,例如混合在一起的列表分隔符。您可能需要另一个正则表达式,或者您可以使用简单的替换子字符串方法。
示例代码是JS的,对不起:)这个想法必须足够清晰。
const string = 'HELLO,THERE,WORLD';
// First use following regex matches each of the list items separately:
const captureListElement = /^[^,]+|,\w+/g;
const matches = string.match(captureListElement);
// Some of the matches may include the separator, so we have to clean them:
const cleanMatches = matches.map(match => match.replace(',',''));
console.log(cleanMatches);
在正则表达式的组中重复 A-Z 模式。
data="HELLO,THERE,WORLD"
pattern=r"([a-zA-Z]+)"
matches=re.findall(pattern,data)
print(matches)
输出
['HELLO', 'THERE', 'WORLD']
如前所述,尝试的代码的问题在于有一个捕获组重复匹配,因此最终只能保留最后一个匹配项。
相反,指示正则表达式匹配(并捕获)字符串中的所有模式实例,这在任何正则表达式实现(语言)中都可以执行。因此,请为此提出正则表达式模式。
所示示例数据的定义属性是感兴趣的模式用逗号分隔,因此我们可以使用否定字符类匹配除逗号以外的任何内容
[^,]+
并全局匹配(捕获),以获取字符串中的所有匹配项。
如果您的模式需要更具限制性,请调整排除列表。例如,捕获由列出的任何标点符号分隔的单词
[^,.!-]+
这将从 中提取所有单词,而不带标点符号。(本身应该在字符类中排在第一位或最后,除非它在类似 或 的范围内使用。hi,there-again!
-
a-z
0-9
在 Python 中
import re
string = "HELLO,THERE,WORLD"
pattern = r"([^,]+)"
matches = re.findall(pattern,string)
print(matches)
在Perl(和许多其他兼容系统)中
use warnings;
use strict;
use feature 'say';
my $string = 'HELLO,THERE,WORLD';
my @matches = $string =~ /([^,]+)/g;
say "@matches";
(在这个特定示例中,实际上不需要捕获,因为我们收集了匹配的所有内容。但它们不会造成伤害,一般来说它们是需要的。()
上面的方法也适用于其他模式,包括问题中尝试的模式(只要您删除使其过于具体的锚点)。最常见的一种是捕获所有单词(通常是意思),其模式为 。或者,如问题中所示,仅获取大写 ascii 字母的子字符串。[a-zA-Z0-9_]
\w+
[A-Z]+
评论
,
[A-Z]+
[^,]+