Der Regex Megathread

Begonnen von RichardCZ, 06 Mai 2020, 12:18:14

Vorheriges Thema - Nächstes Thema

RichardCZ

Zitat von: amenomade am 07 Mai 2020, 02:28:40
/^.*HUMB\|([0-9]+(\.[0-9])?)\|[0-9]{4}?.*/
187 steps. Backtracking ist mit "^.*" vom Anfang an angesagt. Je länger der Text nach dem gesuchten Wert, desto mehr steps.

Das "187 steps" da oben macht mich ziemlich glücklich. Das zeig mir, dass offensichtlich Kenntnis über

use re 'debug'; # bzw.
use re 'Debug';


vorhanden ist. Siehe: https://perldoc.perl.org/re.html
Damit kann man dann nämlich "Performance-Albtraum" quantifizieren und - sobald man gelernt hat diese
Debugausgaben zu interpretieren - auch herausfinden kann warum die Regex manchmal nicht tut.

Zitat
Eigentlich reicht:
/HUMB\|([0-9.]+)/
9 steps, egal was danach steht. (OK, man kontrolliert damit nicht, dass nach der Zahl ein Pipe und 4 Ziffern kommen, und man kontrolliert auch nicht, dass in dem Wert der dezimale Punkt nicht mehrmals auftaucht. Zu locker? Abhängig vom Bedarf?)

Man sollte immer auf das matchen was man erwartet. Erwarte ich Zahlen vom Typ

0.123
1.23
123.111

dann [0-9]+\.[0-9]+ bzw. jeweils [0-9]{1,3}, weil an etlichen (blah)+ Stellen (und konkret kann ich mich an eine Diskussion mit Rudolf hier erinnern)
dem matching "nur Gott und der Hauptspeicher" ein Ende bereiten können.

Und wenn man sieht, wie quantitativ von Dir dargelegt, dass man sich eine Regex "mal eben" um den Faktor 20 schneller machen kann,
dann ist so eine straffere Spezifikation sicher drin im Budget.




Zitat von: Wzut am 07 Mai 2020, 07:16:09
a. meine Liebling Saubermach   =~ s/ //g; müsste ich zukünftig besser als =~ s/\s//g; schreiben ?

Also willst Du immer aus diesem Satz -> AlsowillstDuimmerausdiesemSatz

machen?

Dann wäre wohl tr/ //; die bessere Wahl.


Zitat
b. in 14_CUL_MAX steckt eine Regex die ich gerne loswerden möchte, aber natürlich keinen Plan habe wie
Es geht darum eine Nachricht in ihre Bestandteile zu zerlegen, bekannte Vorbedingung : Sie muß mit dem Buchstaben Z beginnen und mindestens 21 Zeichen lang sein. Prüfsumme gibt es keine, ausser das die richtige Gesammtlänge in den erste zwei Byte nach dem Z steckt. Die anderen Variablen haben jeweils eine fixe Länge bis auf die letzte $payload, daher wohl auch das "böse" .* am Ende :)

m/Z(..)(..)(..)(..)(......)(......)(..)(.*)/x
...
    my ($len,$msgcnt,$msgFlag,$msgTypeRaw,$src,$dst,$groupid,$payload) = ($1,$2,$3,$4,$5,$6,$7,$8);


* wenn Z wirklich am Anfang steht, dann ^Z als Anker
* wenn ich recht mitgezählt habe, sind da schon 22 Zeichen bevor .* kommt, also ist die Bedingung > 21 Zeichen immer erfüllt
* definitiv named captures -> siehe "code von Beta-user" ;-) ... ne? @Beta-User?

m{\A Z(?<len> [0-9]{2})
      (?<msgcnt> xxxxx)
...
  }xms


* kommen da echt beliebige Zeichen? Also auch "Z" zum Beispiel, oder sind das nur bestimmte Zeichenklassen?

(. durch \w zu ersetzen - Beispiel - ist ebenso sinnvoll wie .* loszuwerden.)

* Einerseits redest Du von Zeichen, dann wieder von Byte. Vorsicht! Das ist gefährlich die zwei Konzepte zu mixen.
* Falls Du da wirklich bytes parst, mach ein

use bytes;

vor der Regex. Ansonsten natürlich nicht.

Witty House Infrastructure Processor (WHIP) is a modern and
comprehensive full-stack smart home framework for the 21st century.

RichardCZ

Zitat von: Icinger am 07 Mai 2020, 08:22:35
Dann würde mich auch mal die Meinung zu meinen OBIS-Konstrukten interessieren:

Prinzipiell auf einem guten Level. An einigen Stellen glaube ich hast Du vergessen . zu escapen - oder?

Zitat

...
        qr{1.0.96.5.5|0.0.96.240.\d+|129.129.199.130.5}x,
...
            my $a = shift; # bitte kein $a,$b verwenden - unabhängig von unserer Rx diskussion
...
        qr{1-0:0\.0\.[0-9]|129.129.199.130.3}x, ""   # hier ist's mal escaped mal nicht?
    ],
);


Ansonsten gilt das Gesagte zu \d -> [0-9]
Witty House Infrastructure Processor (WHIP) is a modern and
comprehensive full-stack smart home framework for the 21st century.

RichardCZ

Zitat von: Beta-User am 07 Mai 2020, 08:37:23
Eine der "beliebtesten" Stellen, an denen regexe@FHEM stehen ist notify. Das macht immer "\A" und "\z" dazu, was bedeutet, dass man häufig "auffüllen" muß, also der Einfachheit den "bösen" ".*" hinten anfügt.

Dann sollte man mal über dieses Verhalten von notify nachdenken.

Zitat
Habe jetzt  verstanden, dass es schneller ist, ein "(.*)?" ranzupappen bzw. entsprechende Abwandlungen davon, falls man weiß, dass da ein Zeichen sein muß.

(.*)? ist noch schlimmer.  ;) Falls Du ein "non-greedy" match meintest, dann ist der (.*?) - ist was anderes. UNd falls man weiß dass da ein Zeichen sein muss, dann
(.+?)


Zitat
Aus
defmod n_FK_TK_notify notify ((Dachf|F)enster|.*tuer)_.*:contact:.(open|tilted|closed)..to.VCCU. { myWinContactNotify ($NAME, $EVENT, 30) }

wurde jetztdefmod n_FK_TK_notify notify ((Dachf|F)enster|(.+)?tuer)_(.+)?:contact:.(open|tilted|closed)..to.VCCU. { myWinContactNotify ($NAME, $EVENT, 30) }
.
Hat jemand für einen DAM wie mich eine Größenordnung, um wie viel schneller das ist? Oder übersehe ich mal wieder was wesentliches?

Siehe Kommentar von amenomade - das kann mal eben das 20-fache an Laufzeit ausmachen. Aber auch locker mehr. Backtracking hat exponentielle Laufzeit.
Je nach Stringlänge.

Du machst immer (.+)? statt (.+?) - vorsicht. Das Fragezeichen muss direkt an den Quantor.
Witty House Infrastructure Processor (WHIP) is a modern and
comprehensive full-stack smart home framework for the 21st century.

Beta-User

Vorab zur Klarstellung:

Der "hübsche" Code ist nicht von mir, den hat mir RichardCZ zugespielt. Wie man an meiner anderen Frage sieht, bin ich eher noch dabei, die Untiefen zu verstehen und meine Gedanken zu ordnen ;D .

Das (.+)? war etwas sehr auf die Schnelle zusammengeschustert, ich mache das nicht "immer", sondern lasse die Finger von Dingen, die ich gar nicht verstehe, bzw. teste das ggf. erst mal aus. Da das in einem halbwegs nachvollziehbaren Beispiel funktioniert hat, hab's vielleicht etwas voreilig hier gepostet.

Ob notify "an sich" das "richtig" löst, sei mal dahingestellt, dem Bauchgefühl finde ich es ok. Vermute, dass das eher "false positives" verhindert, die kein "normaler user" verstehen würde/rechtzeitig erkennen.

Dann werden das jetzt wohl "non-greedy" matches, das war es, nach dem ich eigentlich suchte ::) . Dass die (erhoffte) "Ersparnis" von der Stringlänge abhängt ist klar, aber hier wäre auch schon eine geschätzte Größenordnung hinreichend und interessant :) . (Die zu füllenden Leerstellen sind "lesbare Namensbestandteile", also tendenziell unter 10 Zeichen.)
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

rudolfkoenig

ZitatHat jemand für einen DAM wie mich eine Größenordnung, um wie viel schneller das ist?
Wenn das tiefergelegte Regexp dazu fuehrt, dass notifyRegexpChanged kein Device erkennt, dann wird das eher Groessenordnungen langsamer.

Beta-User

Zitat von: rudolfkoenig am 07 Mai 2020, 10:18:37
Wenn das tiefergelegte Regexp dazu fuehrt, dass notifyRegexpChanged kein Device erkennt, dann wird das eher Groessenordnungen langsamer.
Na ja, das "tiefergelegte Regexp" hatte zwar Balast an Board, aber so schlau, das wenigstens vor dem Posten auf Funktionsfähigkeit zu prüfen war ich schon ::) ... (Und ich bin mir auch ziemlich sicher, dass es auch keinen zweiten Eventhandler gibt, der das "Fenster-auf"-Signal verzögert an meine Thermostaten sendet ;D .)
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

rudolfkoenig

notifyRegexpChanged() in fhem.pl versucht zu erkennen, fuer welche Geraete das Regexp zutrifft.
Wenn was erkannt wird, dann wird NotifyFn (in notify/FileLog/etc) nur fuer Events, die von diesen Geraeten stammen, aufgerufen, ansonsten fuer alle Events.

Ein tiefergelegtes Regexp hat die Ehre, sein Performance gegen alle FHEM-Events zu beweisen, wogegen ein "Dummes" deutlich entspannter sein kann.
Nix gegen Regexp-Optimierung, aber (wie oft), ist man nicht ganz allein auf der Welt.

Beta-User

...dann bekommt mein notify jetzt wieder seine Urspungsgestalt und ich lasse an der Stelle "andere werkeln"...

Danke für die Aufklärung (falls ich den Hinweis jetzt richtig interpretiert habe ::) ).
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

RichardCZ

Kurzes Kochrezept:

Regex sollte straff sein. Es stimmt schon: Es hängt vom Kontext ab - man muss wissen/ahnen was man da an Daten erwartet.
Folglich sollte man sich mit allzu generischen Regexen zurückhalten. Ich mache hier mal ne Hitparade der "schlimmsten" Zeichen:

1) .  <- matche jedes Zeichen - wirklich jedes - Whitespaces, Interpunktion, UTF8 Schlonz, Normale Buchstaben, Zahlen

2) * <- matche was davorsteht (Zeichen, Gruppe), null oder mehrmals, also null bis unendlich mal. Also nix bis Universum.

In Kombination: .* gehört das eigentlich auf Platz eins oder 0).  :)

3) + <- matche eins bis unendlich mal. Viel besser als * (weil der Unterschied zwischen 0 und 1 sehr groß ist), aber immer noch sehr ... locker.

Man überlege sich bitte, ob man wirklich immer mit "potentiell unendlich" arbeiten will. Oder weiß, dass z.B. "3 bis 7 Zeichen" also \w{3,7}

4) \d <- matche eine Ziffer. Wie schon ausgeführt, kann eine Ziffer durchaus was anderes sein als was wir Mitteleuropäer uns darunter vorstellen.

Wir stellen uns eben [0-9] darunter vor, also sollten wir auch das schreiben.

5) \w <- matche ein "Wort-Zeichen", grob vereinfacht "Buchstaben und Zahlen und so". Wesentlich besser als . aber häufig immer noch zu locker.

Wenn ich weiß ich habe es z.B. mit hexadezimal Zahlen zu tun (0-9A-Fa-f), dann siehe https://perldoc.perl.org/perlreref.html
Entweder ich schnitze mir die Klasse selbst aus dem Knie: [0-9A-Za-z] oder ich verwende [[:xdigit:]], oder \p{XPosixDigit}




Praktisch immer kann ich eine Regex

.*:

verbessern und beschleunigen, indem ich "negative Zeichenklassen" verwende. Anstatt also zu glauben gesagt zu haben:

"Matche mir alles vor einem Doppelpunkt" (was nicht stimmt, denn alles beinhaltet eben auch einen Doppelpunkt)

wollte ich eigentlich sagen: "Matche mir alles - außer einem Doppelpunkt - vor einem Doppelpunkt" und dann sollte ich das auch so schreiben:

[^:]*:

Aber es gilt weiterhin das oben Gesagte. Muss ich wirklich "Null bis Universum matchen"? Erwarte ich vor dem Doppelpunkt mindestens ein Zeichen, oder tatsächlich auch mal "Nichts"? Es macht sehr viel aus stattdessen

[^:]+:

zu schreiben. Oder noch besser ich weiß, dass da "2 bis 20" Zeichen kommen können, dann eben

[^:]{2,20}:

Und noch besser ist natürlich die Zeichen explizit zu benennen. Negative Zeichenklassen sind ja auch ein "Alles bis auf..."
Versucht einfach mal spaßeshalber Regexen in natürlichsprachliche Aussagen zu übersetzen. Jedes Mal, wenn ihr zur Beschreibung die Worte "Alles", "beliebig" heranziehen müsst, besteht Optimierungspotential.
Witty House Infrastructure Processor (WHIP) is a modern and
comprehensive full-stack smart home framework for the 21st century.

RichardCZ

Zitat von: rudolfkoenig am 07 Mai 2020, 10:18:37
Wenn das tiefergelegte Regexp dazu fuehrt, dass notifyRegexpChanged kein Device erkennt, dann wird das eher Groessenordnungen langsamer.

Und dann wurde es nicht tiefergelegt, sondern einfach kaputtgemacht.

Wobei ja meistens nur Youngsters ihre Rostkarren tieferlegen. Wir Erfahrenen mit genug Zeit und Ausdauer restaurieren lieber rostige Oldtimer.
Witty House Infrastructure Processor (WHIP) is a modern and
comprehensive full-stack smart home framework for the 21st century.

Wzut

Zitat von: RichardCZ am 07 Mai 2020, 10:41:37
Praktisch immer kann ich eine Regex
.*:

verbessern und beschleunigen
@Richard, IMHO wird noch ein Extra Thread nötig sein, denn bisher reden wir doch zu 99% über Regex die wir in unseren Modulen verwenden ?
Aber eine ganze andee Baustelle ist doch : was ist mit den ganzen Regexen die alle User irgendwo als DEF oder Attribut eintragen ?
Als Beispiel nur mal das geliebte event-on-change-reading .* oder halt der banale FileLog Device.*:.*
Maintainer der Module: MAX, MPD, UbiquitiMP, UbiquitiOut, SIP, BEOK, readingsWatcher

Beta-User

Zitat von: Wzut am 07 Mai 2020, 11:37:15
@Richard, IMHO wird noch ein Extra Thread nötig sein, denn bisher reden wir doch zu 99% über Regex die wir in unseren Modulen verwenden ?
Aber eine ganze andee Baustelle ist doch : was ist mit den ganzen Regexen die alle User irgendwo als DEF oder Attribut eintragen ?
Als Beispiel nur mal das geliebte event-on-change-reading .* oder halt der banale FileLog Device.*:.*
Wenn ich Rudi's Antwort richtig interpretiere, sind das eigentlich ein paar mehr Varianten:

1. Was direkt im Modul (oder "echtem Perl-Code", insbesondere in myUtils) steht => hier zu diskutieren;
2. Was der User an das jeweilige Modul geben sollte, _sofern der jeweilige Modulautor nicht bereits Vorkehrungen getroffen hat_: (mit Einschränkungen) hier zu diskutieren (im Sinne von: Was mache ich als Modulautor ggf. draus, wie mache ich die Doku für den Endanwender dazu);
3. "Andere" Module: Hatten wir in Bezug auf notify schon, denke, das gilt vermutlich für den "kleinen Bruder" FileLog analog: Liegt in der Verantwortung des Modulautors; für notify hat Rudi das so optimiert, dass der User einfach ".*" verwenden kann und soll (wie es in der Doku steht...).

4. (MMn.) Separates Thema: Wie klinkt sich mein Modul optimalerweise das Event-Dispatching von fhem.pl ein? Das ist eigentlich irgendwo bei https://wiki.fhem.de/wiki/DevelopmentModuleIntro#X_Notify zu verorten, oder? Falls da was verbesserungswürdiges steht, wäre das mMn. gesondert zu diskutieren.
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

rudolfkoenig

Zitatwas ist mit den ganzen Regexen die alle User irgendwo als DEF oder Attribut eintragen ?
Wir beschliessen hiermit, dass FHEM nur von gebildeten Leuten, mit abgeschlossenem Master in Informatik und Doktorarbeit in Regexp-Optimierung verwendet werden darf.
Alle anderen werden wuest beschimpft und auf dem Pranger gestellt.

RichardCZ

#28
Zitat von: Wzut am 07 Mai 2020, 11:37:15
@Richard, IMHO wird noch ein Extra Thread nötig sein, denn bisher reden wir doch zu 99% über Regex die wir in unseren Modulen verwenden ?

Ja klar. Hier dürfen User ja auch gar nicht mitschreiben. Die Ambitionierten können ja mitlesen. Ansonsten sollen die User erstmal machen wozu sie fähig und willens sind.

Zitat
Aber eine ganze andee Baustelle ist doch : was ist mit den ganzen Regexen die alle User irgendwo als DEF oder Attribut eintragen ?
Als Beispiel nur mal das geliebte event-on-change-reading .* oder halt der banale FileLog Device.*:.*

Eben. Andere Baustelle. Also die ganzen User-Eval-Verquickungen (und Eigentlich-Grund-Warum-Man-bei-FHEM-fast-nix-anfassen-kann-Rückwärtskompatibilität-wissensschon)
... das sehe ich mir so in 6 Monaten an - frühestens. Bevor man ne Testsuite hat ist das "futile".

Mittelfristig stelle ich mir das so vor, dass da eine hübsche Unit-Tests Suite vorhanden ist, und dann baut man eine Integrations-Testsuite.
... naja - man - ich vermutlich, wer sonst?  ...
und diese Integrationssuite bekommt eben diese User-Snippets vor den Latz geknallt (braucht dann auch Heavy-Duty Mock::Object u.ä.)

Also da ist noch weit hin.
Witty House Infrastructure Processor (WHIP) is a modern and
comprehensive full-stack smart home framework for the 21st century.

Sidey

Ich hab jetzt überlegt ob ich eine meiner Werke präsentiere,
da ist es:

m/^MS;(?:P\d=-?\d+;){3,8}D=\d+;CP=\d;SP=\d;/

Eigentlich extrahieren ich alle Bestandteile dann später noch mal in einen Hash, damit ich auf die Daten zugreifen kann.

Mit named capture groups habe ich mich noch nicht so sehr versucht, wüsste aktuell auch nicht, wie man es mit mehrfach vorkommenden capture groups arbeitet :)

Gruß Sidey
Signalduino, Homematic, Raspberry Pi, Mysensors, MQTT, Alexa, Docker, AlexaFhem

Maintainer von: SIGNALduino, fhem-docker, alexa-fhem-docker, fhempy-docker