How to capture multiple repeated groups?

Regex

Regex Problem Overview


I need to capture multiple groups of the same pattern. Suppose, I have the following string:

HELLO,THERE,WORLD

And I've written the following pattern

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

What I want it to do is to capture every single word, so that Group 1 is : "HELLO", Group 2 is "THERE" and Group 3 is "WORLD". What my regex is actually capturing is only the last one, which is "WORLD".

I'm testing my regular expression here and I want to use it with Swift (maybe there's a way in Swift to get intermediate results somehow, so that I can use them?)

UPDATE: I don't want to use split. I just need to now how to capture all the groups that match the pattern, not only the last one.

Regex Solutions


Solution 1 - Regex

With one group in the pattern, you can only get one exact result in that group. If your capture group gets repeated by the pattern (you used the + quantifier on the surrounding non-capturing group), only the last value that matches it gets stored.

You have to use your language's regex implementation functions to find all matches of a pattern, then you would have to remove the anchors and the quantifier of the non-capturing group (and you could omit the non-capturing group itself as well).

Alternatively, expand your regex and let the pattern contain one capturing group per group you want to get in the result:

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

Solution 2 - Regex

The key distinction is repeating a captured group instead of capturing a repeated group.

As you have already found out, the difference is that repeating a captured group captures only the last iteration. Capturing a repeated group captures all iterations.

In 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

Since all captures are in Group 1, you only need $1 for substitution.

I used the following general form of this regular expression:

((?:{{RE}})+)

Example at regex101

Solution 3 - Regex

I think you need something like this....

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

Which in Python3 will return

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

Solution 4 - Regex

After reading Byte Commander's answer, I want to introduce a tiny possible improvement:

You can generate a regexp that will match either n words, as long as your n is predetermined. For instance, if I want to match between 1 and 3 words, the regexp:

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

will match the next sentences, with one, two or three capturing groups.

HELLO,LITTLE,WORLD
HELLO,WORLD
HELLO

You can see a fully detailed explanation about this regular expression on Regex101.

As I said, it is pretty easy to generate this regexp for any groups you want using your favorite language. Since I'm not much of a swift guy, here's a ruby example:

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]+")

That being said, I'd suggest not using regular expression in that case, there are many other great tools from a simple split to some tokenization patterns depending on your needs. IMHO, a regular expression is not one of them. For instance in ruby I'd use something like str.split(",") or str.scan(/[A-Z]+/)

Solution 5 - Regex

Just to provide additional example of paragraph 2 in the answer. I'm not sure how critical it is for you to get three groups in one match rather than three matches using one group. E.g., in 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

Solution 6 - Regex

The problem with the attempted code, as discussed, is that there is one capture group matching repeatedly so in the end only the last match is kept.

What is needed is to instruct the regex to capture all matches, what is available in any regex implementation (language). Then the trick is in writing the pattern so that it indeed matches all instances.

The defining property of the shown sample data is that the patterns of interest are separated by commas so I'd suggest to match anything-but-a-comma, using a negated character class

[^,]+

and match (capture) globally -- get all matches in the string.

If your pattern need be more restrictive adjust the exclusion list. For example, to capture words separated by any of the listed punctuation

[^,.!-]+

This extracts all words from hi,there-again!, without the punctuation. (The - should be given first or last in a character class.)

In Python

import re

string = "HELLO,THERE,WORLD"

pattern = r"([^,]+)"
matches = re.findall(pattern,string)

print(matches)

in Perl (and many other compatible systems)

use warnings;
use strict;
use feature 'say';

my $string = 'HELLO,THERE,WORLD';

my @matches = $string =~ /([^,]+)/g;

say "@matches";

(In this specific example the capturing () in fact aren't needed since we collect everything that is matched. But they don't hurt and in general they are needed.)

Solution 7 - Regex

I know that my answer came late but it happens to me today and I solved it with the following approach:

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

So the first group (([A-Z]+),)+ will match all the repeated patterns except the final one ([A-Z]+) that will match the final one. and this will be dynamic no matter how many repeated groups in the string.

Solution 8 - Regex

You actually have one capture group that will match multiple times. Not multiple capture groups.

javascript (js) solution:

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
}

Syntax:

// matched text: match[0]
// match start: match.index
// capturing group n: match[n]

As you can see, this will work for any number of matches.

Solution 9 - Regex

Sorry, not Swift, just a proof of concept in the closest language at hand.

// 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))

Note: If you were really going to use this, you would use the position of the match as given by the regex match function, not a string replace.

Solution 10 - Regex

  1. Design a regex that matches each particular element of the list rather then a list as a whole. Apply it with /g
  2. Iterate throught the matches, cleaning them from any garbage such as list separators that got mixed in. You may require another regex, or you can get by with simple replace substring method.

The sample code is in JS, sorry :) The idea must be clear enough.

    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);

Solution 11 - Regex

repeat the A-Z pattern in the group for the regular expression.

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

output

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionphbelovView Question on Stackoverflow
Solution 1 - RegexByte CommanderView Answer on Stackoverflow
Solution 2 - Regexssent1View Answer on Stackoverflow
Solution 3 - RegexTim SeedView Answer on Stackoverflow
Solution 4 - RegexUlysse BNView Answer on Stackoverflow
Solution 5 - RegexAndyJView Answer on Stackoverflow
Solution 6 - RegexzdimView Answer on Stackoverflow
Solution 7 - RegexAhmedMoawadView Answer on Stackoverflow
Solution 8 - RegexMark RobinsonView Answer on Stackoverflow
Solution 9 - RegexOrwellophileView Answer on Stackoverflow
Solution 10 - RegexВова ТихоновView Answer on Stackoverflow
Solution 11 - RegexGolden LionView Answer on Stackoverflow