Logo
Section 1 / 3

分组元字符

学习如何在正则表达式中创建分组。

导入

分组元字符

学习如何在正则表达式中创建分组。

分组元字符的要求

在之前的许多挑战中,你曾通过转义的方式将括号作为字面字符使用。但现在你将了解它为何是一个特殊字符,以及如何正确使用它。

在课程的入门章节中,场景2,我们假设你是一名从事DNA研究的科学家。你的主管给你一段DNA文本:“AAGCCTAAGCCTAGATGCGT”,并要求你替换其中的模式“AAGCCTAAGCCT”。由于你现在掌握了重复字符的知识,可以想到一个解决方案:正则表达式 /A{2}GC{2}TA{2}GC{2}T/。这个正则表达式可以在给定文本中匹配目标模式。

但等等!如果一段DNA字符串包含成千上万个字符,而要匹配的模式比现在长一百倍呢?你不可能手动重复写一百次。当你观察刚才编写的正则表达式时,会发现你实际上复制粘贴了两部分:“A{2}GC{2}T”。如果你能将重复字符应用于整个分组,代码会简洁得多。然而,你目前熟悉的重复字符只能作用于前一个元素。为了解决重复或捕获一组字符的问题,必须将它们放在括号内。

括号内的字符或表达式被视为一个组。括号是表示分组的元字符。任何写在括号 ( ) 内的内容都被视为一个组。

1var str = 'abcdef abcabcdef' 2console.log( str.match(/(abc)+def/g) )

重复字符的用途

  • 对一组字符应用重复字符。
  • 捕获并替换文本内容。

使用重复字符对分组进行操作的示例

让我们通过另一个文本集来学习。你收到以下文本:“123123456”、“123456”、“456”、“123123123456”。观察这些文本,你会发现“456”部分是固定的,而“123”似乎被重复出现。如果你能把“123”当作一个单一字符,就可以轻松地对其应用重复字符。分组字符会将“123”构造成一个整体,然后你可以轻易地对这个组应用重复字符。使用分组字符还可以创建嵌套分组,即组中包含组。例如:/(\d{3})-((\d{3})-(\d{4}))/。你将在本课后续部分学习嵌套分组。

注意:正则表达式不会匹配括号本身。例如,若写成 /(\d{3})/,它不会匹配文本中的“(123)”里的括号,只匹配“123”。若要匹配括号,需对其进行转义,正则表达式应为 /(\(\d{3}\))/。

使用分组字符的挑战

请编写一个正则表达式,用于匹配以下文本:“abcxdef1237”、“abcabcdef123456”、“abc\1239”、“abc12312378456”。无需使用标志位。请在边界内用双引号写出正则表达式。

  • 在“abc”组之后可以出现任意字符。
  • 在“123”组之后仅允许使用数字7到9。
思考时间
思考时间
1var RegEx = function(){ 2 // 例如返回 "/xyz/"; 3 return ""; 4}

上述挑战的解答

我们采用常规的拆解方法:

  • 第一部分:所有文本都至少包含一次“abc”。因此需要使用分组和 + 字符。正则表达式为:/(abc)+/。
  • 第二部分:某些文本中可能有一个随机字符。由于该字符可出现零次或一次,选用 ?。匹配任意字符的最佳选择是通配符 .。因此正则表达式变为:/(abc)+.?/。
  • 第三部分:“def”这一组字符出现零次或一次,因此再次使用 ?。正则表达式变为:/(abc)+.?(def)?。
  • 第四部分:“123”这一组字符出现多次,因此使用 +。正则表达式变为:/(abc)+.?(def)?(123)+。
  • 第五部分:仅允许使用数字7到9,因此将其放入字符类 [7-9] 中。该部分可出现零次或多次,使用 *。正则表达式变为:/(abc)+.?(def)?(123)+[7-9]*。
  • 最后一部分:“456”这一组字符出现零次或多次,因此使用 *。最终正则表达式为:/(abc)+.?(def)?(123)+[7-9]*(456)*。

替换并捕获分组的示例

假设你有另一段DNA文本:“AAGCTCTAAATCGC”。你需要将“AAGCT”和“ATCGC”互换。由于你已熟悉分组字符,这个问题很容易解决。

注意:每种编程语言访问捕获组的方式不同。通常,匹配结果存储在变量中,然后以数组形式使用。

1var str = "abcabc123"; 2var array = str.match(/(abc)+/) 3console.log( array[0] ); 4console.log( array[1] );

请务必检查你所使用语言的语法。核心原理保持不变。

要替换分组,你可以直接写正则表达式:/(AAGCT)(ATCGC)/,问题就解决了。只需写 array[2] + array[1],或按所需格式进行组合。

再看一个例子。你编写正则表达式 /(\d{3})-(\d{4})-(\d{3})/ 来处理电话号码记录。假设电话号码是“342-5723-123”。现在你的任务是将四位数字组合放在最前面,其余两个三位数字组合随后排列。这本质上是一次文本重排。为了实现重排,你需要先捕获原始结构,而这正是分组字符的作用。

此时,若写成 array[2] + "-" + array[3] + "-" + array[1],所有文本都会被正确重排。

匹配结果在变量中存储如下:

  • 索引0:完整匹配结果,即“342-5723-123”。
  • 索引1:第一个分组的结果,即“342”。
  • 索引2:第二个分组的结果,即“5723”。
  • 索引3:第三个分组的结果,即“123”。

现在,如果将正则表达式稍作修改为 /((\d{3})-(\d{3}))/,文本为“342-572”,同样会捕获分组。

  • 索引0:完整匹配结果,即“342-572”。
  • 索引1:第一个分组的结果,即“342-572”。
  • 索引2:第二个分组的结果,即“342”。
  • 索引3:第三个分组的结果,即“572”。

你可能会疑惑:为什么索引0和索引1的结果相同?仔细观察分组结构就会明白:第一个三位数由一个分组捕获,第二个三位数由另一个分组捕获,还有一个分组包含了这两个数字及连字符。由于这个分组捕获了完整的模式,其结果与整体匹配一致。因此索引0和索引1的值相同。

另一个容易混淆的问题是:分组编号是如何确定的?你可能会问:为什么第二组代表第一个三位数,而不是第三组?正则表达式从左到右依次分配编号,分组编号由左括号打开的顺序决定。

如果不使用分组字符进行标准搜索,结果只有一个值,因此不需要使用索引遍历。

一旦成功捕获分组,就可以轻松地进行替换或修改。

...
分组顺序

还有一点需要注意:假设你编写正则表达式 /(abc)+/,文本为“abcabc”,那么整个文本会被完全匹配。

  • 索引0:存储“abcabc”,即匹配结果。
  • 索引1:存储“abc”,而非完整文本。分组只保存重复单元本身,不会保存全部重复内容。在此例中,重复单元是“abc”。
1var str = "342-572-5638" 2var arr = str.match(/(\d{3})/) 3console.log( " value in arr[0] = " + arr[0]); 4console.log( " value in arr[1] = " + arr[1]); 5 6var str = "342-572-5638" 7var arr = str.match(/(\d{3})-(\d{3})/) 8console.log( " value in arr[0] = " + arr[0]); 9console.log( " value in arr[1] = " + arr[1]); 10console.log( " value in arr[2] = " + arr[2]); 11 12// var str = "342-572-5638" 13// console.log( " value in arr[0] = " + str.match(/\d{3}/)[0]); 14// console.log( " value in arr[1] = " + str.match(/\d{3}/)[1]); 15 16// var arr = str.match(/((\d{3})-(\d{3}))/) 17// console.log( " length = " + arr.length ); 18 19// 取消注释并自行尝试以加深理解

在上述代码实验环境中,请取消注释相关代码行,自行尝试以更深入理解捕获机制。

分组字符在文本格式化和捕获方面非常有用。

重复字符

选择元字符