Multiples grupos, todos opcionales, diferenciando cada uno en el resultado
Todo lo que mencionaste en la pregunta tiene sentido, y es un buen análisis del problema. Pero se puede encarar de una forma más fácil. En vez de buscar coincidir con los dos números en 1 única coincidencia, conviene pensar en coincidencias independientes:
telefono\D*(\d+)|numero favorito\D*(\d+)
De esta forma, en cada coincidencia busca a uno o al otro, y te devolvería una coincidencia para el grupo 1 o el grupo 2 de acuerdo a cual corresponda. Llamamos a Matcher#find() mientras siga coincidiendo:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
final String regex = "telefono\\D*(\\d+)|numero favorito\\D*(\\d+)";
final String texto = "hola mi telefono es 12345678 y mi numero favorito es el 13";
final Matcher matcher = Pattern.compile(regex).matcher(texto);
while (matcher.find()) {
if (matcher.group(1) != null) {
System.out.println("Tel: " + matcher.group(1));
} else {
System.out.println("Num: " + matcher.group(2));
}
}
De todas formas, sé que tu pregunta apunta más a la teoría que la práctica. Si los grupos tienen que aparecer en ese orden en el texto, siendo igualmente opcionales, entonces, la forma de capturarlos sería agregando al texto intermedio (.*
) dentro de la parte opcional. Es decir:
^(?:.*telefono\D*(\d+))?(?:.*numero favorito\D*(\d+))?
Recordemos que el motor de regex es goloso (greedy), por lo que para cada cuantificador, siempre intenta coincidir con lo más posible. En este caso significa que el (?:
…)?
intenta con 1 antes que 0... Con eso nos garantizamos recorrer todo el string hasta encontrar una coincidencia (por ejemplo en .*telefono\D*(\d+)
), y recién tomarlo como opcional si esa parte no coincide.
import java.util.regex.Matcher;
import java.util.regex.Pattern;
final String regex = "^(?:.*telefono\\D*(\\d+))?(?:.*numero favorito\\D*(\\d+))?";
final String texto = "hola mi telefono es 12345678 y mi numero favorito es el 13";
final Matcher matcher = Pattern.compile(regex).matcher(texto);
if (matcher.find()) { // ← if redundante (siempre coincide)
if (matcher.group(1) != null) {
System.out.println("Tel: " + matcher.group(1));
}
if (matcher.group(2) != null) {
System.out.println("Num: " + matcher.group(2));
}
}
- Hay que usar
Pattern.DOTALL
si se puede extender más allá de un salto de línea.
Si se pueden presentar en cualquier orden, solamente es necesario reemplazar los grupos sin captura (?:
…)?
por inspecciones positivas (lookaheads) (?=
…)?
.
Otra forma de tener múltiples grupos opcionales en orden es:
(?:telefono\D*(\d+).*?)?(?:numero favorito\D*(\d+).*?)?$
Lo que hace es que si coincide con uno de los grupos, sigue consumiendo lo menos posible (no goloso, lazy) con .*?
hasta el próximo grupo, pero al mismo tiempo lo estoy obligando a recorrer todo el string hasta coincidir con el final $
.