Erstellen Sie ein Array von Regex-Übereinstimmungen

160

In Java versuche ich, alle Regex-Übereinstimmungen an ein Array zurückzugeben, aber anscheinend können Sie nur überprüfen, ob das Muster mit etwas übereinstimmt oder nicht (Boolescher Wert).

Wie kann ich eine Regex-Übereinstimmung verwenden, um ein Array aller Zeichenfolgen zu bilden, die mit einem Regex-Ausdruck in einer bestimmten Zeichenfolge übereinstimmen?

Jake Sankey
quelle
2
Gute Frage. Die Informationen, die Sie suchen, sollten Teil der Java-Dokumente zu Regex und Matcher sein. Leider ist es nicht.
Cheeso
3
Eine echte Schande. Diese Funktionalität scheint in fast jeder anderen Sprache (die Unterstützung für reguläre Ausdrücke bietet) sofort verfügbar zu sein.
Ray Toal

Antworten:

278

( Die Antwort von 4castle ist besser als die folgende, wenn Sie Java> = 9 annehmen können.)

Sie müssen einen Matcher erstellen und diesen verwenden, um iterativ Übereinstimmungen zu finden.

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

Danach allMatchesenthält es die Übereinstimmungen, und Sie können verwenden allMatches.toArray(new String[0]), um ein Array zu erhalten, wenn Sie wirklich eines benötigen.


Sie können auch Hilfsfunktionen MatchResultschreiben, um Übereinstimmungen zu durchlaufen, da Matcher.toMatchResult()eine Momentaufnahme des aktuellen Gruppenstatus zurückgegeben wird.

Zum Beispiel können Sie einen faulen Iterator schreiben, damit Sie dies tun können

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

indem Sie so etwas tun:

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

Mit diesem,

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

ergibt

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
quelle
4
Ich würde hier nicht empfehlen, eine ArrayList zu verwenden, da Sie die Größe nicht im Voraus kennen und möglicherweise die Größe des Puffers vermeiden möchten. Stattdessen würde ich eine LinkedList bevorzugen - obwohl dies nur ein Vorschlag ist und Ihre Antwort überhaupt nicht weniger gültig macht.
Liv
13
@Liv, nehmen Sie sich Zeit, um beide zu vergleichen, ArrayListund LinkedListdie Ergebnisse können überraschend sein.
Anthony Accioly
Ich höre, was Sie sagen, und bin mir der Ausführungsgeschwindigkeit und des Speicherbedarfs in beiden Fällen bewusst. Das Problem mit der ArrayList besteht darin, dass der Standardkonstruktor eine Kapazität von 10 erstellt - wenn Sie diese Größe mit hinzuzufügenden Aufrufen überschreiten ( ) müssen Sie mit der Speicherzuordnung und der Array-Kopie fertig werden - und das kann einige Male passieren. Zugegeben, wenn Sie nur wenige Übereinstimmungen erwarten, ist Ihr Ansatz der effizientere. Wenn Sie jedoch feststellen, dass die Größenänderung des Arrays mehr als einmal vorkommt, würde ich eine LinkedList vorschlagen, umso mehr, wenn Sie mit einer App mit geringer Latenz arbeiten.
Liv
12
@Liv, Wenn Ihr Muster dazu neigt, Übereinstimmungen mit einer ziemlich vorhersehbaren Größe zu erzeugen, und abhängig davon, ob das Muster spärlich oder dicht übereinstimmt (basierend auf der Summe der Längen von allMatchesvs yourStringHere.length()), können Sie wahrscheinlich eine gute Größe für berechnen allMatches. Nach meiner Erfahrung LinkedListlohnen sich die Kosten für Speicher und Iterationseffizienz normalerweise LinkedListnicht, ebenso wie meine Standardhaltung. Bei der Optimierung eines Hotspots lohnt es sich jedoch auf jeden Fall, Listenimplementierungen auszutauschen, um festzustellen, ob Sie eine Verbesserung erzielen.
Mike Samuel
1
In Java 9, können Sie jetzt Matcher#resultsbekommen eine , Streamdie ein Array zu erzeugen , verwenden können (siehe meine Antwort ).
4castle
56

In Java 9 können Sie jetzt eine verwenden Matcher#results(), mit Stream<MatchResult>der Sie eine Liste / ein Array von Übereinstimmungen abrufen können.

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
quelle
1
Es gibt keine results () Methode, bitte führen Sie diese zuerst aus
Bravo
14
@ Bravo Verwenden Sie Java 9? Es existiert. Ich habe auf die Dokumentation verlinkt.
4castle
: ((gibt es eine Alternative für Java 8
Logbasex
25

Java macht Regex zu kompliziert und folgt nicht dem Perl-Stil. Schauen Sie sich MentaRegex an, um zu sehen, wie Sie dies in einer einzigen Zeile Java-Code erreichen können:

String[] matches = match("aa11bb22", "/(\\d+)/g" ); // => ["11", "22"]
TraderJoeChicago
quelle
6
Das ist cool. Der doppelte Schrägstrich sieht immer noch hässlich aus, aber ich denke, davon gibt es keine Spur.
JohnPristine
mentaregex-0.9.5.jar, 6 KB, das hat mir den Tag gerettet, Obrigado Sérgio!
CONvid19
2
BEACHTUNG! Die beste Lösung. Benutze es!
Vlad Holubiev
13
Ist die MentaRegex-Site nicht verfügbar? Wenn ich mentaregex.soliveirajr.com besuche, heißt es nur "hi"
user64141
1
@ user64141 sieht aus wie es ist
Amit Gold
11

Hier ist ein einfaches Beispiel:

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

(Wenn Sie mehr Erfassungsgruppen haben, können Sie diese anhand ihres Index als Argument der Gruppenmethode bezeichnen. Wenn Sie ein Array benötigen, verwenden Sie list.toArray())

Bozho
quelle
pattern.matches (Eingabe) funktioniert nicht. Sie müssen Ihr Regex-Muster (erneut!) Übergeben -> WTF Java?! pattern.matches (String-Regex, String-Eingabe); Meinen Sie pattern.matcher (Eingabe)?
El Mac
@ElMac Pattern.matches()ist eine statische Methode, die Sie nicht für eine PatternInstanz aufrufen sollten . Pattern.matches(regex, input)ist einfach eine Abkürzung für Pattern.compile(regex).matcher(input).matches().
dimo414
5

Von dem offiziellen Regex Java Trails :

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

Verwenden Sie finddas Ergebnis und fügen Sie es groupin Ihr Array / Ihre Liste / was auch immer ein.

Anthony Accioly
quelle
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.
quelle