insensitive Suche in Textstring

  • VB.NET

Es gibt 15 Antworten in diesem Thema. Der letzte Beitrag () ist von Fakiz.

    insensitive Suche in Textstring

    Hi

    ich habe einen kleinen Texteditor als Ersatz für den Notepad geschrieben. Der funktioniert auch prima ... aber ...

    Eine der Sonderfunktionen, die ich benötige, ist die "insensitive Suche" ... d.h. die Groß-Kleinschreibung wird ignoriert, aber auch Akzente, also e = é, è, ê ... und v.a.m. Mit anderen Worten, ich kann nicht einfach die Standard Methoden aus .NET zur Suche verwenden.

    Ich habe eine FunktionRemoveAccents geschrieben, die einen vereinheitlichten String zurückliefert.
    Meine Daten stehen in txtData. Der vereinheitlichte Suchstring steht inSearchText
    Und dann lasse ich i von 0 bis TxtData.Length laufen (vorwärts oder rückwärts) und hole mir die jeweiligen Daten:

    VB.NET-Quellcode

    1. work = txtData.Text.Substring(i, SearchText.Length)


    Dann teste ich

    VB.NET-Quellcode

    1. if SearchString = RemoveAccents(work) then ...


    Das ist natürlich sehr "hausbacken". Bei kleinen Strings klappt das prima - aber bei großen Datenmengen dauert es Minuten, vor allem wenn der String nicht gefunden wird ! txtData.Text kann mehrere MB groß sein.

    Gibt es denn performantere Methoden einer "insensitiven Suche" ? Natürlich muss ich vorwärts und rückwärts suchen können.

    Vielleicht hat jemand eine schlaue Idee ...

    LG
    Peter

    Peter329 schrieb:

    ich habe einen kleinen Texteditor als Ersatz für den Notepad geschrieben.
    Ich arbeite mit Notepad++. Der lässt nichts zu wünschen übrig.
    ====
    Drehe die Suche um:
    Durchsuche das Char-Array Deines Strings nach Deinen Accent-Chars.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Du könntest eine Fuzzy-Suche einbauen.
    Damit erreichst du, dass auch Strings matchen, die nicht 100% übereinstimmen.

    Crêpe könnte also auch mit "Crepe" gesucht werden.

    github.com/JakeBayer/FuzzySharp
    Das könnte dich in die richtige Richtung leiten.
    Quellcode lizensiert unter CC by SA 2.0 (Creative Commons Share-Alike)

    Meine Firma: Procyon Systems
    Meine Privatwebseite: SimonC.eu

    Bitte nicht wundern, wenn meine Aktivitäten im Forum etwas langsamer sind, ich baue gerade mein Nebengewerbe zum Vollgewerbe aus.
    Ich versuche auf euch zurückzukommen :)
    Erst mal vielen Dank, dass ihr euch mit meinem Problem beschäftig habt.

    Der Notepad++ ist ein nettes Teil. Sieht super aus und ist schnell - leider gibt es da auch keine Suche ohne Berücksichtigung der Akkzente (soweit ich das sehen kann).

    Der Fuzzy-Search ist auch eine tolle Sache ... aber da werden eben nicht nur die Akkzente ausgeblendet .. und das hilft mir dann leider auch nicht.

    [off topic]

    Für alle die wissen wollen, warum um alles in der Welt ich hier so sehr viel Wert auf das Ausblenden der Akkzente lege:

    Ich habe ein "Tagalog" Wörterbuch - Tagalog ist die (austronesische) Sprache der Filippinen (ja, da wo die sonnigen Strände und die netten Mädels sind :) ) und in dieser Sprache kommt es sehr auf die Betonung an:

    bása = to read (verb)
    basá = wet (adjective)

    Zwischen beiden Varianten liegen Welten. Aber das ist nur ein Beispiel von sehr vielen ... Die Akkzente der Betonung werden alllerdings nicht geschrieben (anders als etwa Im Spanisch oder Französisch), die Bedeutung und damit die Akkzente werden nur aus dem Kontext ersichtlich. Im Text lese ich also nur "basa" ... und wenn ich das in die Suche eingebe,dann wird weder "bása" noch "basá" gefunden. Und die Akzente können á, à oder â sein ... damit wird der sog. "glottal stop" angezeigt - ohne das jetzt näher erklären zu wollen. Und das betrifft alle Vokale (a, e, i, o, u).

    Jetzt sollte klar sein, warum ich soviel Wert darauf lege, dass meine Suche die Akkzente ignoriert ... damit ich alle passenden Wörter finde! Ansonsten sind die Suchergebnisse für mich ziemlich wertlos.

    [off topic end]

    Na ja, ich hoffe, dass ich mein Anliegen verständlich machen konnte.

    Im dümmsten Fall, muss ich halt vor jeder Suche den Textstring erst mal mit meinem "RemoveAccents" in eine "insensitive Version" konvertieren ... blöde halt nur, dass diese Version dann nach jeder Text Änderung invalidiert werden muss. D.h. jede neue Suche verlangt dann erst mal einen ziemlichen Aufwand ...

    Ich bin gespannt, ob jemand eine zündende Idee hat.

    LG
    Peter

    Peter329 schrieb:

    Na ja, ich hoffe, dass ich mein Anliegen verständlich machen konnte.
    Wo kommen denn die Texte mit den Akzenten her?
    Wäre Regex eine Alternative (langsam aber mächtig)?
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Wenn ich mir die Unicode-Tabelle anschaue, dann sind die Buchstaben (auch mit Accents) sinnig angelegt.
    symbl.cc/en/unicode/table/#latin-1-supplement

    Vielleicht würde StringComparison.OrdinalCultureIgnoreCase deine Suche ja schon beschleunigen.
    Quellcode lizensiert unter CC by SA 2.0 (Creative Commons Share-Alike)

    Meine Firma: Procyon Systems
    Meine Privatwebseite: SimonC.eu

    Bitte nicht wundern, wenn meine Aktivitäten im Forum etwas langsamer sind, ich baue gerade mein Nebengewerbe zum Vollgewerbe aus.
    Ich versuche auf euch zurückzukommen :)
    Danke für die Anregungen!

    RodFromGermany schrieb:

    Wo kommen denn die Texte mit den Akzenten her?


    Na, das ist natürlich das Dictionary - da sind die Vokablen MIT Akkzenten abgespeichert (sonst wäre das ja auch sinnlos).

    Mit RegEx ... da müsste man einen entsprechenden String generieren ... wäre vielleicht eine Möglichkeit ... da werde ich mal drüber nachdenken.

    siycah schrieb:


    Vielleicht würde StringComparison.OrdinalCultureIgnoreCase deine Suche ja schon beschleunigen.


    Auch das scheint mir vielversprechend ... mal sehen, ob ich das hinbekomme.

    LG
    Peter

    [edit]
    Also von der Sache mit RegEx bin ich jetzt doch nicht mehr so überzeugt:

    Nakákapagpabagábag = causing trouble

    Wenn man das ohne Eingabe der Akkzente mit RegEx finden möchte, braucht es dann schon einen ziemlich langen String. :)

    Das scheint mir dann doch nicht so ganz zielführend zu sein (auch wenn es sich bei dem o.a. String um einen "tongue twister" handelt).

    [edit end]



    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Peter329“ ()

    Könntest du bitte deinen RegEx hier posten. Eigentlich sind reguläre Ausdrücke keine ganz so verkehrte Idee und können das auch sehr gut.

    Die sind nur wirklich schwer zu verstehen. Seit 16 Jahren programmiere ich nun und immer wieder bin ich erstaunt, was die Profis alle für Werke zaubern
    Quellcode lizensiert unter CC by SA 2.0 (Creative Commons Share-Alike)

    Meine Firma: Procyon Systems
    Meine Privatwebseite: SimonC.eu

    Bitte nicht wundern, wenn meine Aktivitäten im Forum etwas langsamer sind, ich baue gerade mein Nebengewerbe zum Vollgewerbe aus.
    Ich versuche auf euch zurückzukommen :)
    Ein selbstgebastelter Editor wird natürlich nicht ansatzweise die Qualität und Leistungsfähigkeit erreichen, die np++ bietet.
    Etwa sowas selbstverständliches wie die UnDo-Funktionalität - das wirste schlicht nicht hinkriegen.
    Daher wäre schon am besten, wenn du einfach np++ nutzen könntest.
    Aber diese Spezial-Funktion: Accent-Insensitive Search - das hatter eben nicht, bzw. das kann er nur, indem man der Suche einen u.U. recht komplexen Regex eingibt.
    Vielleicht kannst du dir ein tool bauen, was alle bösen Zeichen erstmal ausmerzt, bevor du den Text in np++ öffnest?

    Jedenfalls, am Ende mag herauskommen, dass du in einem grossen Text viele Zeichen gleich behandeln musst wie andere viele Zeichen (É-E, é-e, È-E, è-e, ...).
    Dazu mein Tipp: Vergiss String, und arbeite mit Char-Array.
    Bei multiplen Austausch-Vorgängen sind lange Strings extrem resourcenfressend, weil jeder Austausch bedeutet ein komplettes Umkopieren.

    Mehr kann noch nicht gesagt werden, weil du bislang keine wirkliche Spezifikation angegeben hast, welche Funktionen du brauchst (Suche? Filtern? Ersetzen? Markieren?).
    Und jeweils braucht man mehrere Beispiele, mit Input-Text, Such/Ersetz-Pattern, Output-Text.
    Wenn man das ohne Eingabe der Akkzente mit RegEx finden möchte, braucht es dann schon einen ziemlich langen String.

    Da muss man etwas um die Ecke denken. In dem Wort das du mit Regex prüfen willst ersetzt du alle Akzente durch \w. Dadurch wird der Input string dann zum Pattern und umgekehrt.

    Spoiler anzeigen

    C#-Quellcode

    1. static void Main(string[] args)
    2. {
    3. string source = "bása, basá, Nakákapagpabagábag";
    4. foreach (string s in FindMatches(source, "basa"))
    5. Console.WriteLine(s);
    6. foreach (string s in FindMatches(source, "Nakakapagpabagabag"))
    7. Console.WriteLine(s);
    8. Console.WriteLine("\nDone...");
    9. Console.ReadLine();
    10. //bása, matches basa
    11. //basá, matches basa
    12. //Nakákapagpabagábag matches Nakakapagpabagabag
    13. //Done...
    14. }
    15. static IEnumerable<string> FindMatches(string source, string pattern)
    16. {
    17. foreach (string s in source.Split(' '))
    18. {
    19. string word = Regex.Replace(s.Trim(','), "[áàâ]", "\\w");
    20. if (Regex.IsMatch(pattern, word, RegexOptions.IgnoreCase))
    21. yield return $"{s} matches {pattern}";
    22. }
    23. }

    Vielen Dank - wegen des Jahreswechsels komme ich erst jetzt dazu mich damit zu befassen.

    1. @EDR: Das mit dem Char-Vektor verstehe ich. Aber dann müsste ich ja, jedesmal wenn der String sich ändert, diesen Vektor neu aufbauen (In einem Editor werden die Daten i.a. häufig geändert). Da die Dimension des Vektors einige MB beträgt, müsste ich den wohl nach jedem Update vor einer neuen Suche "entsorgen" und neu aufbauen. Wie mache ich das denn ...? ich meine mich zu entsinnen, dass es mit "REDIM" einige Probleme gab, wenn man das in einer Funktion ausführt ...

    2. @Fakiz: Die Sache mit dem Ignorieren der Akzente im RegEx ist genial. Da werde ich ausprobieren, um zu sehen, wie performant das ist. Aber wie mache ich das denn denn, wenn der zu suchende String mehrfach auftritt ? (Das ist der Normalfall ...). Dann will ich ja die Suche mit einem "Repeat" Button ab dem letzten Treffen fortsetzen, bzw. mit "wrap-around" am Anfang bzw. Ende neu aufsetzen. Müsste ich da erst mal die Adressen aller Treffer in einem Vektor speichern und dann abarbeiten ? M.W. gibt es im RegEx keine Möglichkeit ab einer bestimmten Stelle zu suchen, bzw. rückwärts zu suchen .. oder ist mir das entgangen?

    Ich hab da einfach ein paar grundlegende Probleme, wie man so eine Funktion wirklich sinnvoll gestaltet.

    LG und ein Gutes Neues Jahr
    Peter

    Peter329 schrieb:

    Da die Dimension des Vektors einige MB beträgt...
    Tja, da hast du ein Problem, ein grosses womöglich.
    Mit Char-Array ists ein Problem, aber String-Operationen bzw. Regexe haben dasselbe Problem, evtl. sogar noch viel krasser.

    Wirst halt probieren müssen.



    Du bleibst also dabei, einen Editor bauen zu wollen? Für MB-grosse Texte?
    Welches Control willst du dafür verwenden?



    Peter329 schrieb:

    Müsste ich da erst mal die Adressen aller Treffer in einem Vektor speichern und dann abarbeiten ? M.W. gibt es im RegEx keine Möglichkeit ab einer bestimmten Stelle zu suchen, bzw. rückwärts zu suchen .. oder ist mir das entgangen?
    ja, müsstest du - ist auch kein Problem. Und rückwärts suchen, ab Position suchen - ja Regex kann sehr sehr viel.

    Aber natürlich nicht alles - wie gesagt musste rumprobieren.
    aber Fakiz's Code - ich hab den probiert - funzt net - kriegst du was damit hin?

    Und da sehe ich ein Regex.Replace - also wenn sich das auf einen 2MB-String bezieht... - da darf man gespannt sein.
    aber ob sich das auf 2MB bezieht weiss ich nicht genau, wie gesagt: ich blick da nicht durch

    ErfinderDesRades schrieb:

    Du bleibst also dabei, einen Editor bauen zu wollen? Für MB-grosse Texte?
    Welches Control willst du dafür verwenden?


    Du hast ja Recht ... einen "richtigen" Editor schreibt man nicht selbst, sondern verwendet etwa Notepad++.

    Meinen "Editor" verwende ich vorwiegend für sehr spezielle Aufgaben .... etwa als IDE für HTML, Skripte (BAT, RegEx), zum Extrahieren von Infos aus zweifelhaften Internet Files oder eMails, die ich nicht per Anwendung, also etwa in einem Mail Client oder Browser öffnen möchte, etc. etc. (die versuchen nämlich oft ihren Code nach Möglichkeit zu "verstecken").

    Einen Screenshot habe ich zur Info und zur Erbauung angehängt.

    Danke, für deine Einschätzung. Im dümmsten Fall muss ich eben damit leben, dass die Suche nur eingeschränkt verwendbar ist.


    LG
    Peter
    Bilder
    • s 2024-01-03 11-20-092.jpg

      82,49 kB, 895×505, 55 mal angesehen

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Peter329“ ()

    Na gut ... 2 MB ist vielleicht ein bissl viel für eine TextBox ... aber hier habe ich z.B. eine Datei mit 1 MB (1.053.016 Bytes) ... die läuft flüssig und ohne Probleme ... Das ist so die Größe, mit der ich es gelegentlich zu tun habe. (s. Anhang)

    Na ja, mal sehen, ob ich das mit der Suche zum Laufen kriege.
    Bilder
    • s 2024-01-03 15-38-198.jpg

      94,52 kB, 887×497, 40 mal angesehen
    Aber wie mache ich das denn denn, wenn der zu suchende String mehrfach auftritt ?

    In dem du dir den Enumerator zwischen speicherst. Dann kannst du mit Enumerator.MoveNext() durch die Funde interrieren.

    Spoiler anzeigen

    C#-Quellcode

    1. var enumerator = FindMatches(source, "basa").GetEnumerator();
    2. if (enumerator.MoveNext())
    3. Console.WriteLine(enumerator.Current); // bása, matches basa
    4. Console.WriteLine("Wait for user input...");
    5. if (enumerator.MoveNext())
    6. Console.WriteLine(enumerator.Current); // basá, matches basa
    7. if (!enumerator.MoveNext())
    8. Console.WriteLine("No more matches");



    Aber wenn du mit Sonderzeichen rechnen musst würde ich tatsächlich im text alle akzente ersetzen und dann alle vorkommen suchen. Hab das grad mal mit einer 4MB Datei versucht, hat bei mir 11 Sekunden mit Konsolenausgabe benötigt.