¿Cómo capturar múltiples grupos repetidos?

87

Necesito capturar varios grupos del mismo patrón. Supongamos que tengo la siguiente cadena:

HELLO,THERE,WORLD

Y he escrito un siguiente patrón

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

Lo que quiero que haga es capturar cada palabra, de modo que el Grupo 1 sea: "HOLA", el Grupo 2 sea "ALLÍ" y el Grupo 3 sea "MUNDO" Lo que mi expresión regular está capturando solo la última, que es " MUNDO".

Estoy probando mi expresión regular aquí y quiero usarla con Swift (¿tal vez hay una forma en Swift de obtener resultados intermedios de alguna manera, para poder usarlos?)

ACTUALIZACIÓN: No quiero usar split. Ahora solo necesito saber cómo capturar todos los grupos que han coincidido con el patrón, no solo el último.

phbelov
fuente
5
¿Por qué no dividirse ,?
rock321987
¿Por qué no usar [A-Z]+o [^,]+capturar los resultados?
rock321987
rock321987, he actualizado la cadena de entrada. Necesito extraer exactamente la cadena que sigue el patrón anterior. Y necesito que todos los grupos coincidan con el patrón, no solo el último. Quiero saber cómo hacerlo con expresiones regulares.
phbelov
2
rock321987, ¿qué no está claro? Necesito que cada palabra de la cadena sea un grupo coincidente, pero mi patrón solo captura la última ("MUNDO").
phbelov
1
use esta respuesta para encontrar todas las coincidencias
rock321987

Respuestas:

64

Con un grupo en el patrón, solo puede obtener un resultado exacto en ese grupo. Si su grupo de captura se repite por el patrón (usó el +cuantificador en el grupo de no captura circundante), solo se almacena el último valor que coincide con él.

Debe usar las funciones de implementación de expresiones regulares de su lenguaje para encontrar todas las coincidencias de un patrón, luego tendría que eliminar los anclajes y el cuantificador del grupo que no captura (y también podría omitir el grupo que no captura).

Alternativamente, expanda su expresión regular y deje que el patrón contenga un grupo de captura por grupo que desea obtener en el resultado:

^([A-Z]+),([A-Z]+),([A-Z]+)$
Comandante de bytes
fuente
17
¿Cómo se ajustaría esto para tener en cuenta un número variable de cadenas? por ejemplo, HOLA, MUNDO y HOLA, ALLÍ, MI, MUNDO. Estoy buscando solo una expresión para manejar ambos ejemplos y con flexibilidad incorporada para matrices de cadenas aún más largas
Chris
12
@Chris No se puede generalizar. Como dice la respuesta, un grupo de captura solo puede capturar una cosa y no hay forma de crear un número dinámico de grupos de captura.
Barmar
7

Creo que necesitas algo como esto ...

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

Que en Python3 volverá

['HELLO', 'THERE', 'WORLD']
Tim Seed
fuente
re.findall('\w+',b)es 2 caracteres más corto. No es necesaria una clase de personaje, ya que solo tiene una expresión
Jean-François Fabre
3

Solo para proporcionar un ejemplo adicional del párrafo 2 en la respuesta. No estoy seguro de cuán crítico es para ti tener tres grupos en un partido en lugar de tres partidos usando un grupo. Por ejemplo, en 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
AndyJ
fuente
3

Después de leer la respuesta de Byte Commander , quiero presentar una pequeña mejora posible:

Puede generar una nexpresión regular que coincidirá con cualquiera de las palabras, siempre que nesté predeterminada. Por ejemplo, si quiero hacer coincidir entre 1 y 3 palabras, la expresión regular:

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

coincidirá con las siguientes oraciones, con uno, dos o tres grupos de captura.

HELLO,LITTLE,WORLD
HELLO,WORLD
HELLO

Puede ver una explicación completamente detallada sobre esta expresión regular en Regex101 .

Como dije, es bastante fácil generar esta expresión regular para cualquier grupo que desee utilizando su idioma favorito. Como no soy muy rápido, aquí hay un ejemplo de rubí:

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

Dicho esto, sugeriría no usar expresiones regulares en ese caso, hay muchas otras herramientas excelentes, desde splitpatrones simples hasta algunos patrones de tokenización, según sus necesidades. En mi humilde opinión, una expresión regular no es uno de ellos. Por ejemplo, en ruby ​​usaría algo como str.split(",")ostr.scan(/[A-Z]+/)

Ulysse BN
fuente
2

La distinción clave es repetir un grupo capturado en lugar de capturar un grupo repetido .

Como ya ha descubierto, la diferencia es que la repetición de un grupo capturado captura solo la última iteración. La captura de un grupo repetido captura todas las iteraciones.

En 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

Dado que todas las capturas están en el Grupo 1, solo es necesario $1realizar una sustitución.

Usé la siguiente forma general de esta expresión regular:

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

Ejemplo en regex101

ssent1
fuente
1

De hecho, tiene un grupo de captura que coincidirá varias veces. No múltiples grupos de captura.

solución 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(bob);     //find next match
}

Salida:

HI
THERE
TOM

Sintaxis:

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

Como puede ver, esto funcionará para cualquier número de coincidencias.

Mark Robinson
fuente
0

Sé que mi respuesta llegó tarde pero hoy me pasa y lo resolví con el siguiente enfoque:

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

Entonces, el primer grupo (([A-Z]+),)+coincidirá con todos los patrones repetidos excepto el último ([A-Z]+)que coincidirá con el final. y esto será dinámico sin importar cuántos grupos repetidos en la cadena.

AhmedMoawad
fuente
3
Ésta no es una solución al problema. No se trata de hacer coincidir la cadena, sino de capturar todos los grupos. Esta expresión regular todavía solo captura la última coincidencia para el primer grupo repetido (con coma), más la coincidencia en el grupo final (sin coma).
gdwarf
0

Lo siento, no Swift, solo una prueba de concepto en el idioma más cercano.

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

Nota: Si realmente fuera a usar esto, usaría la posición de la coincidencia dada por la función de coincidencia de expresiones regulares, no un reemplazo de cadena.

Orwellophile
fuente