Christian Wansart: Wo hakt es bei meinem regulären Ausdruck?

Moin,

ich glaube, ich sehe den Wald vor lauter Bäumen nicht. Ich habe eine Datenstruktur, die etwas uneinheitlich getrennt ist, weswegen ich nicht einmal String.split(…) nutzen kann. Sie sieht etwa so aus:

(A,B,C)(q,w)((A,q,B),(B,w,C)),(A),(C)

Nun brauche möchte ich gerne folgende Tokens erhalten:

  • (A,B,C)
  • (q,w)
  • ((A,q,B),(B,w,C)),
  • (A),
  • (C)

Gerne auch ohne die Klammern drumzu und die zwei Semikolon auf der vorvorletzten und vorletzten Zeile. Ich habe es so versucht:

const fragments = code.match(/^\((.*?)\)\((.*?)\)\((.*?)\),\((.*?)\),\((.*?)\)$/g)

Jedoch habe ich dann nur ein einzelnes Token mit dem gesamten Eingabestring.

Was übersehe ich? Wo liegt mein Fehler?

Freundliche Grüße
Christian

akzeptierte Antworten

  1. Tach!

    Nun brauche möchte ich gerne folgende Tokens erhalten:

    • (A,B,C)
    • (q,w)
    • ((A,q,B),(B,w,C)),
    • (A),
    • (C)

    Ich habe es so versucht:

    const fragments = code.match(/^\((.*?)\)\((.*?)\)\((.*?)\),\((.*?)\),\((.*?)\)$/g)
    

    Was übersehe ich? Wo liegt mein Fehler?

    Bei deinem dritten Teilausdruck kommen schließende Klammern im Wert vor, dein Ausdruck berücksichtigt das aber nicht. Stattdessen findet der aber nur den ersten Teil ((A,q,B). Beim nächsten Teil soll nach der schließenden Klammer ein Komma folgen, es folgt stattdessen aber die zweite schließende Klammer.

    dedlfix.

    1. Hallo dedlfix,

      deshalb habe ich die ja mittels Backslash escaped. Ich benötige doch die Klammern um einen Teilausdruck zu erfassen.

      Sprich bei \((.*?)\)ist der Gedanke (A,B,C) zu matchen, aber eben ohne die Klammern. So wie ich das verstanden habe, nutze ich nicht escapte Klammern um einen Teilausdruck herauszubekommen.

      Irre ich mich hier?

      1. Tach!

        deshalb habe ich die ja mittels Backslash escaped. Ich benötige doch die Klammern um einen Teilausdruck zu erfassen.

        Ja, aber du suchst im dritten Teil nur bis ein ), nachfolgt, doch der geht bis )),.

        dedlfix.

        1. Ich dachte, das kann ich mit dem .*?und dem $ am Ende abdecken. Ich möchte gerne erst einmal prüfen, ob diese fünf Tokens vorhanden sind, denn der dritte kann theoretisch auch leer sein, und keine zusätzlichen Klammern enthalten.

          Zum Beispiel: (A,B,C)(q,w)(),(A),(C) Es ist also nicht sicher, dass dort immer zwei )) sind. Also muss ich hier vermutlich einen etwas genaueren Teilausdruck hier schreiben?

          So etwas wie:

          \((\([A-Za-z0-9_]\),?)*\)

          1. Tach!

            Es ist also nicht sicher, dass dort immer zwei )) sind.

            Dann sind es wohl keine Daten, das mit einem starren Muster "geparst" werden können. Vielleicht brauchst du dann einen Parser.

            Kannst du die Daten in Teilschritten splitten? Erst den hinteren Teil mit den Kommas abtrennen, dann den vorderen an )(

            dedlfix.

            1. Hallo dedflix,

              ich hatte gehofft, dass es in einem Schritt geht, aber deine Idee klingt gar nicht so verkehrt.

              Ja, das sollte machbar sein.. Mhmm.

              Danke für die Idee!

              Freundliche Grüße
              Christian

            2. Da fällt mir gerade noch auf/ein:

              const fragments = code.match(/\(([A-Za-z0-9,]*)\)/g)
              

              Hiermit bekomme ich im übrigen alle Klammern. Ich könnte einfach das erste Token und die beiden letzten nehmen, dann hätte ich alle die ich brauche.

              Der oben genannte String gibt also folgende Tokens: ["(q0,q1,q2,q3)", "(a,b)", "(q0,a,q1)", "(q0,b,q3)", "(q1,a,q3)", "(q1,b,q2)", "(q2,a,q2)", "(q2,b,q2)", "(q3,a,q3)", "(q3,b,q3)", "(q0)", "(q2)"]

              Mich irritiert das ein wenig, da ich die Klammern außerhalb ja escapt habe. Mhmm…

              Freundliche Grüße
              Christian

              1. Tach!

                Mich irritiert das ein wenig, da ich die Klammern außerhalb ja escapt habe. Mhmm…

                Das Escapen war ja richtig, aber du hast nach einer Klammer mit Komma gesucht, und die passte auf den inneren Ausdruck, stattdessen aber das nächste Vorkommen gemeint.

                dedlfix.

                1. Moin,

                  ich muss doch noch mal nachfragen. Ich denke, ich übersehe gerade einfach was, aber ich verstehe es einfach nicht.

                  Mit diesem String: (q0,q1,q2,q3)(a,b)((q0,a,q1),(q0,b,q3),(q1,a,q3),(q1,b,q2),(q2,a,q2),(q2,b,q2),(q3,a,q3),(q3,b,q3)),(q0),(q2)

                  und dem regulären Ausdruck /\(([A-Za-z0-9,]*)\)/g bekomme ich folgende Rückgabe:

                  ["(q0,q1,q2,q3)", "(a,b)", "(q0,a,q1)", "(q0,b,q3)", "(q1,a,q3)", "(q1,b,q2)", "(q2,a,q2)", "(q2,b,q2)", "(q3,a,q3)", "(q3,b,q3)", "(q0)", "(q2)"]

                  Nun sagst du, es hat mit den inneren Klammern zu tun, aber die werden doch gar nicht mit ([A-Za-z0-9,]*) gesucht? Die Klammern hier drumherum sind ja nicht escapt und dienen doch lediglich zur Gruppierung?

                  Ich bin gerade etwas verwirrt…

                  Beste Grüße
                  Christian

                  1. Tach!

                    Nun sagst du, es hat mit den inneren Klammern zu tun, aber die werden doch gar nicht mit ([A-Za-z0-9,]*) gesucht? Die Klammern hier drumherum sind ja nicht escapt und dienen doch lediglich zur Gruppierung?

                    Mit diesem Muster nicht, aber mit dem Muster deines ersten Versuchs, als du mehrere Teilmuster aneinandergehängt hattest, da hatte der dritte Teil was anderes gefunden, als du beabsichtigt hattest.

                    dedlfix.

                  2. Hallo,

                    /\(([A-Za-z0-9,]*)\)/g

                    hier hast du sowohl zu suchende Klammern, als auch gruppierende Klammern.

                    ([A-Za-z0-9,]*)

                    hier nur gruppierende

                    Gruß
                    Kalk

    • Ist die Verteilung von Klammern und Kommas nur ein Beispiel oder immer gleich?
    • Willst Du die Klammern im Text als Teil des Match haben? In dem Fall müsstest Du die inneren Klammern deiner Suchterme escapen, nicht die äußeren.

    Rolf

    1. Ja.

      Das Format ist immer:

      ()()(),(),()

      In der 1., 2., 4. und 5. können Elemente stehen, die mit Kommata getrennt sind. Die interessieren mich im ersten Schritt aber noch nicht. Im dritten kann nichts drinstehen, oder eben im Format:

      • (A,b,C)
      • (A,b,C),(B,c,A)
      • gar nichts
      • oder mehrere wie im zweiten Beispiel mit Kommata getrennt, ohne abschließendes Komma.

      Freundliche Grüße
      Christian

      1. Ok, solange NUR im dritten Teil Subklammern stehen können, kriegt man das hin, wenn man den Suchausdruck für den dritten Teil greedy macht (sprich: .* statt .*?). Unter regex101.com kannst Du das ausprobieren.

        Mein Pattern dort:
        ^(?<A>\(.*?\))(?<B>\(.*?\))(?<C>\(.*\)),(?<D>\(.*?\)),(?<E>\(.*?\))$

        Oder so, wenn Du die äußeren Klammern nicht im Match haben willst:
        ^\((?<A>.*?)\)\((?<B>.*?)\)\((?<C>.*)\),\((?<D>.*?)\),\((?<E>.*?)\)$

        Link zum Beispiel in regex101

        Ich habe die 5 Hauptbereiche man benamst, dann greift es später sich leichter zu.

        Teststring: (A,B,C)(q,w)((A,q,B),(B,w,C)),(A),(C)

        Liefert:

        A=(A,B,C)
        B=(q,w)
        C=((A,q,B),(B,w,C))
        D=(A)
        E=(E)
        

        Schwieriger wird es, wenn auch die anderen Teilausdrücke Unterklammern enthalten dürfen. In dem Fall musst Du wohl oder übel die Grammatik aufschreiben, der diese Teilausdrücke folgen dürfen, und die Regex entsprechend aufblasen.

        Noch schlimmer wird es, wenn beliebig tief geschachtelte Unterklammerungen erlaubt sind; in dem Fall hättest Du rekursive Teile in deiner Regex. Sowas gibt es, aber es ist abzählbar kompliziert und in diesen Fällen baue ICH lieber einen Parser von Hand :)

        Rolf

        1. Hey,

          Noch schlimmer wrd es, wenn beliebig tief geschachtelte Unterklammerungen erlaubt sind; in dem Fall hättest Du rekursive Teile in deiner Regex. Sowas gibt es, aber es ist abzählbar kompliziert und in diesen Fällen baue ICH lieber einen Parser von Hand :)

          An diesem Punkt würde ich gerne auf PEGJS verweisen: Grammatik rein, fertiger Parser raus. Tolles Werkzeug, wenn reguläre Ausdrücke an ihre Grenzen stoßen.

          Reinhard

        2. Moin,

          ich habe mir das gerade mal angeschaut und dort für JavaScript "umgeschrieben": https://regex101.com/r/DIJyde/1

          Wie du siehst, dort findet er alle fünf Gruppen. Im Browser sieht es anders aus. Da bekomme ich mit:

          const fragments = code.match(/^\((.*?)\)\((.*?)\)\((.*)\),\((.*?)\),\((.*?)\)$/g);
          console.log(fragments);
          

          den gesamten String wieder: ["(q0,q1,q2,q3)(a,b)((q0,a,q1),(q0,b,q3),(q1,a,q3),(…q2,a,q2),(q2,b,q2),(q3,a,q3),(q3,b,q3)),(q0),(q2)"]

          Was mache ich hier falsch?

          Freundliche Grüße
          Christian

          1. Falsch ist, dass du string.match verwendest, der liefert die Treffergruppen nicht. Nimm RegExp.exec, also:

            var rx = /pattern/g;
            var match = rx.exec(suchstring);
            

            Aber das ist nicht alles, und ich habe gerade auch keine Ahnung wo das Problem ist. Wenn der Teil im dritten Klammerpaar zu lang wird, scheitert die Regex.

            Das geht kaputt:
            rx.exec("(q0,q1,q2,q3)(a,b)((q0,a,q1),(q0,b,q3),(q1,a,q3),(…q2,a,q2),(q2,b,q2),(q3,a,q3),(q3,b,q3)),(q0),(q2)")

            Das funktioniert:
            rx.exec("(q0,q1,q2,q3)(a,b)((q0,a,q1),(q0,b,q3),(…q2,a,q2)),(q0),(q2)")

            Zuerst dachte ich, das Unicode-Zeichen ... wäre ein Problem, aber das war's nicht. Entweder habe ich Knöpfe auf den Augen und erkenne den Musterfehler nicht, oder das Ding läuft intern auf einen Overflow und gibt deshalb null zurück. Ich muss jetzt weg, sorry, ich gucke morgen noch mal.

            Rolf

            1. Hallo Rolf,

              danke für deine Antwort.

              Ach, ja, exec hatte ich bisher nicht genutzt. Mir war nicht bewusst, dass dort ein Unterschied ist.

              Das hilft mir schon mal weiter, danke!

              Freundliche Grüße
              Christian

            2. Hmpf - war wohl besoffen. Oder Chrome war's. Nach Zu- und Aufmachen funktioniert es nun auf einmal. Vielleicht ein Typo gehabt, keine Ahnung.

              Rolf

  2. Hi,

    (A,B,C)(q,w)((A,q,B),(B,w,C)),(A),(C)
    Nun brauche möchte ich gerne folgende Tokens erhalten:

    • (A,B,C)
    • (q,w)
    • ((A,q,B),(B,w,C)),
    • (A),
    • (C)

    Du solltest Dir einen Lisp-Interpreter besorgen 😉

    cu,
    Andreas a/k/a MudGuard

    1. Wäre sicher auch eine Lösung, nur ist die Syntax da vermutlich zu kurz für. 😀 Es sind ja immer 5 Gruppen.

      Ich bin aber am überlegen, da einen Parser ohne reguläre Ausdrücke zu schreiben, einfach nur um das mal gemacht zu haben.

      1. @@Christian Wansart

        Ich bin aber am überlegen, da einen Parser ohne reguläre Ausdrücke zu schreiben, einfach nur um das mal gemacht zu haben.

        Als Gründe würde ich nicht „einfach nur um das mal gemacht zu haben“ anführen, sondern

        • um Problemen aus dem Weg zu gehen

        • weil reguläre Ausdrücke prinzipiell nicht geeignet sind, nicht-reguläre Sprachen zu erkennen.

        LLAP 🖖

        --
        “When UX doesn’t consider all users, shouldn’t it be known as ‘Some User Experience’ or... SUX? #a11y” —Billy Gregory
          • weil reguläre Ausdrücke prinzipiell nicht geeignet sind, nicht-reguläre Sprachen zu erkennen

          Weswegen die heutigen Regex-Muster ja auch eigentlich Pattex[1] heißen müssten. Sie sind genauso universell, und genauso hart aufzulösen ;-)

          Rolf


          1. Pattern Expression. ↩︎

          1. @@Rolf b

            Weswegen die heutigen Regex-Muster ja auch eigentlich Pattex heißen müssten.

            Dass die Muster nicht „Regex“ heißen sollten, hatte ich hier ja schon öfter gesagt. Oder dass „Regex“ nicht für regular expression steht.[1]

            „Pattex“ (pattern expression) – gefällt mir.

            LLAP 🖖

            --
            “When UX doesn’t consider all users, shouldn’t it be known as ‘Some User Experience’ or... SUX? #a11y” —Billy Gregory

            1. ebensowenig wie „charset“ für character set steht ↩︎

  3. Wenn du nur eine Verschachtelungtiefe hast sollte es eigentlich kein großes Abenteuer sein.

    const fragments = code.match(/\((?:[^)(]+|\([^)(]*\))*\)/g);

    Das Suchmuster findet eine öffnende Klammer, gefolgt von ein oder mehr Zeichen, die keine Klammer sind, oder eine öffnende Klammer gefolgt von beliebig vielen Zeichen, die keine Klammer sind, gefolgt von einer schließenden Klammer, gefolgt von einer schließenden Klammer.

    Mehr dazu auf Regex101