Crear una matriz de coincidencias de expresiones regulares

160

En Java, intento devolver todas las coincidencias de expresiones regulares a una matriz, pero parece que solo puede verificar si el patrón coincide con algo o no (booleano).

¿Cómo puedo usar una coincidencia de expresiones regulares para formar una matriz de todas las cadenas que coinciden con una expresión de expresiones regulares en una cadena dada?

Jake Sankey
fuente
2
Buena pregunta. La información que busca debe formar parte de los documentos de Java en Regex y Matcher. Lamentablemente, no lo es.
Cheeso
3
Una verdadera pena. Esta funcionalidad parece existir fuera de la caja en casi cualquier otro idioma (que tiene soporte para expresiones regulares).
Ray Toal

Respuestas:

278

( La respuesta de 4castle es mejor que la siguiente si puede suponer Java> = 9)

Debe crear un emparejador y usarlo para encontrar coincidencias de forma iterativa.

 import java.util.regex.Matcher;
 import java.util.regex.Pattern;

 ...

 List<String> allMatches = new ArrayList<String>();
 Matcher m = Pattern.compile("your regular expression here")
     .matcher(yourStringHere);
 while (m.find()) {
   allMatches.add(m.group());
 }

Después de esto, allMatchescontiene las coincidencias y puede usar allMatches.toArray(new String[0])para obtener una matriz si realmente necesita una.


También puede usar MatchResultpara escribir funciones auxiliares para recorrer las coincidencias ya que Matcher.toMatchResult()devuelve una instantánea del estado actual del grupo.

Por ejemplo, puedes escribir un iterador perezoso que te permita hacer

for (MatchResult match : allMatches(pattern, input)) {
  // Use match, and maybe break without doing the work to find all possible matches.
}

haciendo algo como esto:

public static Iterable<MatchResult> allMatches(
      final Pattern p, final CharSequence input) {
  return new Iterable<MatchResult>() {
    public Iterator<MatchResult> iterator() {
      return new Iterator<MatchResult>() {
        // Use a matcher internally.
        final Matcher matcher = p.matcher(input);
        // Keep a match around that supports any interleaving of hasNext/next calls.
        MatchResult pending;

        public boolean hasNext() {
          // Lazily fill pending, and avoid calling find() multiple times if the
          // clients call hasNext() repeatedly before sampling via next().
          if (pending == null && matcher.find()) {
            pending = matcher.toMatchResult();
          }
          return pending != null;
        }

        public MatchResult next() {
          // Fill pending if necessary (as when clients call next() without
          // checking hasNext()), throw if not possible.
          if (!hasNext()) { throw new NoSuchElementException(); }
          // Consume pending so next call to hasNext() does a find().
          MatchResult next = pending;
          pending = null;
          return next;
        }

        /** Required to satisfy the interface, but unsupported. */
        public void remove() { throw new UnsupportedOperationException(); }
      };
    }
  };
}

Con este,

for (MatchResult match : allMatches(Pattern.compile("[abc]"), "abracadabra")) {
  System.out.println(match.group() + " at " + match.start());
}

rendimientos

a at 0
b at 1
a at 3
c at 4
a at 5
a at 7
b at 8
a at 10
Mike Samuel
fuente
44
No sugeriría usar una ArrayList aquí ya que no conoce el tamaño por adelantado y es posible que desee evitar el cambio de tamaño del búfer. En cambio, preferiría una LinkedList, aunque es solo una sugerencia y no hace que su respuesta sea menos válida.
Liv
13
@Liv, tómese el tiempo para comparar ambos ArrayListy LinkedListlos resultados pueden ser sorprendentes.
Anthony Accioly
Escucho lo que estás diciendo y soy consciente de la velocidad de ejecución y la huella de memoria en ambos casos; el problema con ArrayList es que el constructor predeterminado crea una capacidad de 10, si superas ese tamaño con llamadas para agregar ( ) tendrá que soportar la asignación de memoria y la copia de la matriz, y eso puede suceder varias veces. De acuerdo, si espera solo unas pocas coincidencias, entonces su enfoque es el más eficiente; sin embargo, si encuentra que el "cambio de tamaño" de la matriz ocurre más de una vez, sugeriría una LinkedList, aún más si se trata de una aplicación de baja latencia.
Liv
12
@Liv, si su patrón tiende a producir coincidencias con un tamaño bastante predecible, y dependiendo de si el patrón coincide de manera escasa o densa (en función de la suma de las longitudes de allMatchesvs yourStringHere.length()), probablemente pueda calcular previamente un buen tamaño allMatches. En mi experiencia, el costo de la LinkedListmemoria y la eficiencia de iteración en términos de eficiencia no suele valer la pena, por LinkedListlo que no es mi postura predeterminada. Pero al optimizar un punto caliente, definitivamente vale la pena intercambiar las implementaciones de la lista para ver si obtiene una mejora.
Mike Samuel
1
En Java 9, ahora puede usar Matcher#resultspara obtener uno Streamque puede usar para generar una matriz (vea mi respuesta ).
4castle
56

En Java 9, ahora puede usar Matcher#results()para obtener uno Stream<MatchResult>que puede usar para obtener una lista / matriz de coincidencias.

import java.util.regex.Pattern;
import java.util.regex.MatchResult;
String[] matches = Pattern.compile("your regex here")
                          .matcher("string to search from here")
                          .results()
                          .map(MatchResult::group)
                          .toArray(String[]::new);
                    // or .collect(Collectors.toList())
4castle
fuente
1
no hay resultados () método, ejecute esto primero
Bravo
14
@Bravo ¿Estás utilizando Java 9? Sí existe Me vinculé a la documentación.
4castle
: ((¿hay alguna alternativa para Java 8
Logbasex
25

Java hace que la expresión regular sea demasiado complicada y no sigue el estilo perl. Eche un vistazo a MentaRegex para ver cómo puede lograrlo en una sola línea de código Java:

String[] matches = match("aa11bb22", "/(\\d+)/g" ); // => ["11", "22"]
TraderJoeChicago
fuente
66
Eso es genial. La doble barra todavía se ve fea, pero supongo que no hay escapatoria de eso.
JohnPristine
mentaregex-0.9.5.jar, 6Kb que me salvó el día, Obrigado Sérgio!
CONvid19
2
¡ATENCIÓN! La mejor solucion. Úsalo!
Vlad Holubiev
14
¿Está caído el sitio MentaRegex? Cuando visito mentaregex.soliveirajr.com solo dice "hola"
user64141
1
@ user64141 parece que es
Amit Gold
11

Aquí hay un ejemplo simple:

Pattern pattern = Pattern.compile(regexPattern);
List<String> list = new ArrayList<String>();
Matcher m = pattern.matcher(input);
while (m.find()) {
    list.add(m.group());
}

(si tiene más grupos de captura, puede referirse a ellos por su índice como argumento del método de grupo. Si necesita una matriz, use list.toArray())

Bozho
fuente
pattern.matches (input) no funciona. ¡Tienes que pasar tu patrón de expresiones regulares (de nuevo!) -> WTF Java ?! pattern.matches (String regex, String input); ¿Te refieres a pattern.matcher (input)?
El Mac
@ElMac Pattern.matches()es un método estático, no debe llamarlo en una Patterninstancia. Pattern.matches(regex, input)es simplemente una abreviatura de Pattern.compile(regex).matcher(input).matches().
dimo414
5

De los senderos oficiales de Java Regex :

        Pattern pattern = 
        Pattern.compile(console.readLine("%nEnter your regex: "));

        Matcher matcher = 
        pattern.matcher(console.readLine("Enter input string to search: "));

        boolean found = false;
        while (matcher.find()) {
            console.format("I found the text \"%s\" starting at " +
               "index %d and ending at index %d.%n",
                matcher.group(), matcher.start(), matcher.end());
            found = true;
        }

Use finde inserte el resultado groupen su matriz / Lista / lo que sea.

Anthony Accioly
fuente
0
        Set<String> keyList = new HashSet();
        Pattern regex = Pattern.compile("#\\{(.*?)\\}");
        Matcher matcher = regex.matcher("Content goes here");
        while(matcher.find()) {
            keyList.add(matcher.group(1)); 
        }
        return keyList;
Nikhil Kumar K
fuente