如何捕获多个重复组?

How to capture multiple repeated groups?

提问人:phbelov 提问时间:5/3/2016 最后编辑:InSyncphbelov 更新时间:10/4/2023 访问量:285131

问:

我需要捕获相同模式的多个组。假设,我有以下字符串:

HELLO,THERE,WORLD

我写了以下模式

^(?:([A-Z]+),?)+$

我希望它做的是捕获每个单词,因此第 1 组是:“HELLO”,第 2 组是“THERE”,第 3 组是“WORLD”。我的正则表达式实际捕获的只是最后一个,即“WORLD”。

在这里测试我的正则表达式,我想将它与 Swift 一起使用(也许 Swift 中有一种方法可以以某种方式获得中间结果,以便我可以使用它们?

我不想使用 .我现在只需要如何捕获与模式匹配的所有组,而不仅仅是最后一个组。split

SWIFT 正则表达式

评论

8赞 rock321987 5/3/2016
为什么不拆分?,
0赞 rock321987 5/3/2016
为什么不使用或捕获结果[A-Z]+[^,]+
0赞 phbelov 5/3/2016
rock321987,我更新了输入字符串。我需要准确提取遵循上述模式的字符串。我需要让所有组都与模式匹配,而不仅仅是最后一个。我想知道如何使用正则表达式来做到这一点。
7赞 phbelov 5/3/2016
rock321987,有什么不清楚的?我需要字符串的每个单词都是一个匹配的组,但我的模式只捕获最后一个(“WORLD”)。
1赞 rock321987 5/3/2016
使用此答案查找所有匹配项

答:

119赞 Byte Commander 5/3/2016 #1

如果模式中有一个组,则该组中只能获得一个确切的结果。如果捕获组被模式重复(您在周围的非捕获组上使用了量词),则仅存储与其匹配的最后一个值。+

你必须使用你的语言的正则表达式实现函数来查找模式的所有匹配项,然后你必须删除非捕获组的锚点和量词(你也可以省略非捕获组本身)。

或者,展开正则表达式,让模式包含每个要在结果中获取的组的一个捕获组:

^([A-Z]+),([A-Z]+),([A-Z]+)$

评论

52赞 Chris 2/27/2018
如何调整以考虑不同数量的字符串?例如,HELLO,WORLD 和 HELLO,THERE,MY,WORLD。我正在寻找一个表达式来处理这两个示例,并具有内置的灵活性,用于更长的字符串数组
22赞 Barmar 10/5/2018
@Chris 它不能一概而论。正如答案所述,捕获组只能捕获一件事,并且无法创建动态数量的捕获组。
2赞 zdim 1/20/2022
Re “How would it will adjust to account different number of string?”(如何调整以考虑不同数量的字符串?) -- 对于那些仍然访问此页面的人 -- 使用手头语言的工具动态构建它。将子模式(此处)作为字符串或正则表达式模式(取决于语言)并连接其中的 N(在本例中为逗号),然后将其转换为正则表达式模式或仅在正则表达式中使用它(同样,取决于语言)。这通常相当简单。(我认为这个答案是理所当然的,人们可以动态地构建它。([A-Z]+)
4赞 dumbledad 12/5/2022
真是太可惜了,你只列出了 Alternative 的代码,而不是答案本身的代码。
3赞 AndyJ 5/19/2016 #2

只是为了在答复中提供第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
11赞 Tim Seed 12/13/2018 #3

我认为你需要这样的东西:

b = "HELLO,THERE,WORLD"
re.findall('[\w]+',b)

在 Python 3 中将返回:

['HELLO', 'THERE', 'WORLD']

评论

13赞 Jean-François Fabre 1/4/2021
re.findall('\w+',b)短 2 个字符。不需要字符类,因为只有一个表达式
4赞 pythonian29033 12/3/2022
问题没有python标签
5赞 Ulysse BN 12/6/2019 #4

看完 Byte Commander 的回答后,我想介绍一个微小的改进:

您可以生成一个与任一单词匹配的正则表达式,只要您的是预先确定的。例如,如果我想匹配 1 到 3 个单词,则正则表达式:nn

^([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 中,我会使用类似 或splitstr.split(",")str.scan(/[A-Z]+/)

评论

0赞 pythonian29033 12/3/2022
这个呢:......我刚刚检查了一下^这个非常适合 n 次捕获([A-Z]+)((?:,([A-Z]+))?)+
1赞 Ulysse BN 12/3/2022
@pythonian29033这并不能像 OP 要求的那样捕获每个单词,但它只会捕获第一组和最后一组。至少使用 PCRE
1赞 AhmedMoawad 4/30/2020 #5

我知道我的答案来晚了,但它今天发生在我身上,我用以下方法解决了它:

^(([A-Z]+),)+([A-Z]+)$

因此,第一组 (([A-Z]+),)+ 将匹配所有重复模式,但最后一组 ([A-Z]+) 将与最后一组模式匹配。无论字符串中有多少个重复组,这都是动态的。

评论

7赞 gdwarf 5/21/2020
这不是解决问题的方法。问题不在于匹配字符串,而在于捕获所有组。此正则表达式仍然只捕获第一个重复组(带逗号)的最后一个匹配项,以及最后一个组中的匹配项(不带逗号)。
1赞 Mark Robinson 6/16/2020 #6

您实际上有一个捕获组,该捕获组将多次匹配。不是多个捕获组。

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]

如您所见,这将适用于任意数量的比赛。

0赞 Orwellophile 11/20/2020 #7

对不起,不是 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))

注意:如果你真的要使用它,你会使用正则表达式匹配函数给出的匹配位置,而不是字符串替换。

46赞 ssent1 12/11/2020 #8

主要区别在于重复捕获组,而不是捕获重复的组

正如您已经发现的,区别在于重复捕获的组仅捕获最后一次迭代。捕获重复组将捕获所有小版本。

在 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 中的示例

评论

12赞 Thomas LAURENT 1/22/2021
“捕获重复组会捕获所有迭代。”在你的正则表达式101中,尝试将你的正则表达式替换为,它会给你相同的结果。这里的关键是标志,它重复您的模式以匹配到多个组中。(\w+),?g
1赞 Pierre 12/23/2021
这太不对了。“捕获重复组捕获所有迭代”:是的,但它将仅在一个匹配项中捕获所有迭代(包含所有迭代)。您的示例应该是 .正如 @thomas-laurent 所说,您在这里进行多场比赛只是因为 g 标志。无法从一个捕获组中进行多个匹配。您必须提取并preg_match_all(或等效函数)重复组。((?:\w,?)+)
0赞 ssent1 1/9/2022
@Pierre 感谢您的澄清。根据最初的问题,我们必须对需要什么做出假设。首先,他说,“我想捕捉每一个字,所以第一组是:......第 3 组是......”您的区分很重要,因为在这种情况下,唯一的反向引用组是必要的。上表显示了分配给 的所有匹配项。结果,不起作用。他总结说,“我需要捕捉所有与模式匹配的群体,而不仅仅是最后一个群体。 在启用标志的情况下实现此目的。HELLOWORLDGroup 1((?:\w+)+),?((?:\w+)+),?g
1赞 Pierre 1/10/2022
@ssent1 Your 等同于 。您封闭的匿名组永远不会重复。这种误导,没有什么比“在多场比赛中捕获重复的小组”更重要的了。不幸的是,正则表达式中的任何内容都不能多次匹配同一组。只有 g 标志和 preg_match_all 在剩余的不匹配字符串上迭代执行正则表达式。((?:\w+)+),?(\w+),?
1赞 ssent1 2/18/2022
@Pierre 你是对的。然而,[重复捕获组与捕获重复组]之间似乎仍有区别)(regular-expressions.info/captureall.html)。在实践层面上,它可以成为功能解决方案的一部分。归根结底,如果需要一个“防弹”解决方案,最好以编程方式进行。
0赞 Вова Тихонов 6/15/2021 #9
  1. 设计一个与列表的每个特定元素匹配的正则表达式,而不是与整个列表匹配。使用 /g 应用它
  2. 遍历匹配项,清除任何垃圾,例如混合在一起的列表分隔符。您可能需要另一个正则表达式,或者您可以使用简单的替换子字符串方法。

示例代码是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);
0赞 Golden Lion 10/27/2021 #10

在正则表达式的组中重复 A-Z 模式。

data="HELLO,THERE,WORLD"
pattern=r"([a-zA-Z]+)"
matches=re.findall(pattern,data)
print(matches)

输出

['HELLO', 'THERE', 'WORLD']
4赞 zdim 3/9/2022 #11

如前所述,尝试的代码的问题在于有一个捕获组重复匹配,因此最终只能保留最后一个匹配项。

相反,指示正则表达式匹配(并捕获)字符串中的所有模式实例,这在任何正则表达式实现(语言)中都可以执行。因此,请为此提出正则表达式模式。

所示示例数据的定义属性是感兴趣的模式用逗号分隔,因此我们可以使用否定字符类匹配除逗号以外的任何内容

[^,]+

并全局匹配(捕获),以获取字符串中的所有匹配项。

如果您的模式需要更具限制性,请调整排除列表。例如,捕获由列出的任何标点符号分隔的单词

[^,.!-]+

这将从 中提取所有单词,而不带标点符号。(本身应该在字符类中排在第一位或最后,除非它在类似 或 的范围内使用。hi,there-again!-a-z0-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]+