10 Stimmen

Extrahiere alle Vorkommen des Musters K und überprüfe, ob der String in einem Durchgang mit "K*" übereinstimmt.

Für eine gegebene Eingabes Zeichenfolge und ein gegebenes Muster K möchte ich jedes Vorkommen von K (oder eines Teils davon (unter Verwendung von Gruppen)) aus der Zeichenfolge extrahieren und überprüfen, ob die gesamte Zeichenfolge mit K* übereinstimmt (d. h. sie besteht aus 0 oder mehr K's ohne andere Zeichen).

Aber ich möchte dies in einem einzigen Durchlauf mithilfe von regulären Ausdrücken tun. Genauer gesagt, ich finde das Muster derzeit mit Matcher.find, aber dies ist nicht unbedingt erforderlich.

Wie würde ich das machen?

Ich habe bereits eine Lösung gefunden (und eine Antwort veröffentlicht), aber möchte wissen, ob es spezifische Regex- oder Matcher-Funktionen gibt, die dieses Problem angehen können oder einfach bessere / andere Möglichkeiten, dies zu tun. Aber selbst wenn nicht, finde ich es immer noch eine interessante Frage.

Beispiel:

Muster: <[0-9]> (eine einzelne Ziffer in <>)

Gültige Eingabe: <1><2><3>

Ungültige Eingaben:

<1><2>a<3>
<1><2>3
Oh, schau mal, ein fliegender Affe!
<1><2><3

Code, um es in 2 Durchläufen mit matches zu machen:

boolean products(String products)
{
    String regex = "(<[0-9]>)";
    Pattern pAll = Pattern.compile(regex + "*");

    if (!pAll.matcher(products).matches())
        return false;

    Pattern p = Pattern.compile(regex);
    Matcher matcher = p.matcher(products);

    while (matcher.find())
        System.out.println(matcher.group());

    return true;
}

7voto

nhahtdh Punkte 54526

1. Definieren des Problems

Da nicht klar ist, was ausgegeben werden soll, wenn der gesamte String nicht dem Muster K* entspricht, werde ich das Problem neu definieren, um klarzustellen, was in einem solchen Fall ausgegeben werden soll.

Gegeben ein beliebiges Muster K:

  • Überprüfen Sie, ob der String dem Muster K* entspricht.
  • Wenn der String das Muster K* hat, dann wird der String in nicht überlappende Tokens aufgeteilt, die zu K passen.
  • Wenn der String nur ein Präfix hat, das dem Muster K* entspricht, dann wählen Sie das Präfix aus, das von K*+1 gewählt wurde, und teilen Sie das Präfix in Tokens auf, die zu K passen.

1 Ich weiß nicht, ob es eine Möglichkeit gibt, das längste Präfix zu erhalten, das zu K passt. Natürlich können Sie immer das letzte Zeichen nacheinander entfernen und gegen K* testen, bis es passt, aber das ist offensichtlich ineffizient.

Wenn nicht anders angegeben, wird alles, was ich unten schreibe, meiner obigen Problembeschreibung folgen. Beachten Sie, dass der 3. Punkt des Problems die Unklarheit darüber lösen soll, welches Präfix-String genommen werden soll.

2. Wiederholte Erfassungsgruppe in .NET

Das oben beschriebene Problem kann gelöst werden, wenn wir die Lösung für das Problem haben:

Gegeben ein Muster (K)*, das eine wiederholte Erfassungsgruppe ist, erhalten Sie den erfassten Text für alle Wiederholungen, anstatt nur für die letzte Wiederholung.

  • Im Fall, dass der String das Muster K* hat, indem er gegen ^(K)*$ abgeglichen wird, können wir alle Tokens erhalten, die zum Muster K passen.
  • Im Fall, dass der String nur ein Präfix hat, das zum Muster K* passt, durch Abgleichen gegen ^(K)*, können wir alle Tokens erhalten, die zum Muster K passen.

Dies ist der Fall bei .NET Regex, da es den gesamten erfassten Text für eine wiederholte Erfassungsgruppe behält.

Da wir jedoch Java verwenden, haben wir keinen Zugriff auf eine solche Funktion.

3. Lösung in Java

Die Überprüfung, ob der String das Muster K* hat, kann immer mit Matcher.matches()/String.matches() durchgeführt werden, da der Motor das gesamte Eingabemuster vollständig zurückverfolgt, um auf irgendeine Weise K* mit dem Eingabestring zu "vereinheitlichen". Schwierig ist es jedoch, den Eingabestring in Tokens aufzuteilen, die zum Muster K passen.

Wenn K* äquivalent zu K*+ ist

Wenn das Muster K die Eigenschaft hat:

Für alle Zeichenfolgen2 ist K* äquivalent zu K*+, d.h. wie der Eingabestring in Tokens aufgeteilt wird, die zum Muster K passen, ist das Gleiche.

2 Sie können diese Bedingung nur für die Eingabestrings definieren, auf denen Sie arbeiten, aber sicherstellen, dass diese Vorbedingung erfüllt ist, ist nicht einfach. Wenn Sie es für alle Zeichenfolgen definieren, müssen Sie nur Ihren Regex analysieren, um zu überprüfen, ob die Bedingung erfüllt ist oder nicht.

Dann kann eine Lösung konstruiert werden, die das Problem in einem Durchgang löst. Sie können Matcher.find() wiederholt auf das Muster \GK anwenden und überprüfen, ob das letzte gefundene Übereinstimmung direkt am Ende des Strings steht. Dies ähnelt Ihrer aktuellen Lösung, nur dass Sie die Randüberprüfung mit Code durchführen.

Das + nach dem Quantifizierer * in K*+ macht den Quantifizierer possessiv. Ein possessiver Quantifizierer verhindert das Zurückverfolgen des Motors, was bedeutet, dass jede Wiederholung immer die erste mögliche Übereinstimmung für das Muster K ist. Diese Eigenschaft benötigen wir, damit die Lösung \GK äquivalente Bedeutung hat, da sie auch die erste mögliche Übereinstimmung für das Muster K zurückgibt.

Wenn K* NICHT äquivalent zu K*+ ist

Ohne die oben genannte Eigenschaft benötigen wir 2 Durchgänge, um das Problem zu lösen. Erster Durchgang, um Matcher.matches()/String.matches() auf das Muster K* anzuwenden. Im zweiten Durchgang:

  • Wenn der String nicht dem Muster K* entspricht, verwenden wir wiederholt Matcher.find() auf das Muster \GK, bis keine Übereinstimmung mehr gefunden werden kann. Dies kann aufgrund der Definition, welches Präfix-String zu nehmen ist, wenn der Eingabestring nicht zum Muster K* passt, erreicht werden.

  • Wenn der String dem Muster K* entspricht, wiederholen Sie die Verwendung von Matcher.find() auf das Muster \GK(?=K*$). Dies führt jedoch zu redundanter Arbeit beim Abgleichen des Rests des Eingabestrings.

Beachten Sie, dass diese Lösung universell für jedes K anwendbar ist. Mit anderen Worten, sie gilt auch für den Fall, dass K* äquivalent zu K*+ ist (aber für diesen Fall werden wir die bessere Ein-Pass-Lösung verwenden).

6voto

Scott Punkte 11557

Hier ist eine zusätzliche Antwort auf die bereits akzeptierte Antwort. Unten finden Sie ein Beispielcode-Snippet, das nur einmal mit m.find() das Muster durchläuft, was ähnlich zu Ihrer Ein-Pass-Lösung ist, aber nicht übereinstimmende Zeilen verarbeitet.

import java.util.regex.*;

class test{
    public static void main(String args[]){
        String t = "<1><2><3>";
        Pattern pat = Pattern.compile("(<\\d>)(?=(<\\d>)*$)(?<=^(<\\d>)*)");
        Matcher m = pat.matcher(t);
        while (m.find()) {
            System.out.println("Passt!");
            System.out.println(m.group());
        }       

    }
}

Die Regex erklärt:

<\\d> - Dies ist Ihr k-Muster wie oben definiert
?= - positive lookahead (überprüfen, was nach K kommt)
<\\d>* - K 0 oder mehrmals übereinstimmen
$ - Ende der Zeile
?<= - positive lookbehind (überprüfen, was vor K kommt)
^ - Anfang der Zeile
<\\d>* - gefolgt von 0 oder mehr K

Reguläre Ausdrücke sind schöne Dinge.

Bearbeiten: Wie mir von @nhahtdh mitgeteilt wurde, handelt es sich hierbei nur um eine implementierte Version der Antwort. Tatsächlich kann die oben gezeigte Implementierung mit dem Wissen in der Antwort verbessert werden.
(<\\d>)(?=(<\\d>)*$)(?<=^(<\\d>)*) kann zu \\G<\\d>(?=(<\\d>)*$) geändert werden.

3voto

Bernhard Barker Punkte 52773

Unten ist eine Ein-Pass-Lösung unter Verwendung von Matcher.start und Matcher.end.

boolean products(String products)
{
    String regex = "<[0-9]>";

    Pattern p = Pattern.compile(regex);

    Matcher matcher = p.matcher(products);
    int lastEnd = 0;
    while (matcher.find())
    {
        if (lastEnd != matcher.start())
           return false;
        System.out.println(matcher.group());
        lastEnd = matcher.end();
    }
    if (lastEnd != products.length())
        return false;
    return true;
}

Der einzige Nachteil ist, dass alle Werte vor dem Auffinden ungültiger Daten ausgegeben (oder verarbeitet) werden.

Zum Beispiel wird products("<1><2>a<3>"); ausgeben:

<1>
<2>

bevor die Ausnahme ausgelöst wird (weil bis dahin der String gültig ist).

Es scheint unvermeidlich zu sein, entweder dass dies passiert oder alle vorübergehend speichern zu müssen.

1voto

Joop Eggen Punkte 101784
    String t = "<1><2><3>";
    Pattern pat = Pattern.compile("(<\\d>)*");
    Matcher m = pat.matcher(t);
    if (m.matches()) {
        //String[] tt = t.split("(?<=>)"); // Look behind on '>'
        String[] tt = t.split("(?<=(<\\d>))"); // Nach <\\d> lookbehind
    }

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X