Laden...

[erledigt] Suche in einem String: nur ganze Wörter finden

Erstellt von Cuin vor 13 Jahren Letzter Beitrag vor 13 Jahren 17.071 Views
C
Cuin Themenstarter:in
92 Beiträge seit 2010
vor 13 Jahren
[erledigt] Suche in einem String: nur ganze Wörter finden

Hallo alle Zusammen,

wie die Überschrift schon sagt, versuche ich in einem vorhandenen String ein ganzes Wort zu finden. Mit dem normallen IndexOf würde ich z.b. im folgenden Beispiel nicht das richtige Wort finden.


string TempString = "Hans wollte 40 km laufen, aber ab 20 km hatte er keine Puste mehr";
string s = "ab";
int Position = TempStringt.IndexOf(s);

Als Position erhalte ich hier das "ab" in "aber" und nicht das alleinstehende "ab".

Ich könnte zwar z.B. mit


if (!char.IsLetter(TempString[Position - 1]) && !char.IsLetter(TempString[Position + s.Length]))
{

}

selber prüfen, ob es sich um ein alleinstehendes Wort handelt, aber da ich die IndexOf-Methode ziemlich oft benutze, ist mir dieser Weg ein wenig zu umständlich.

Hat jemand einen Vorschlag wie ich das Problem lösen kann?

3.511 Beiträge seit 2005
vor 13 Jahren

Hallo,

sowas lässt sich über Reguläre Ausdrücke sehr gut lösen. ([Artikel] Regex-Tutorial)

"Jedes Ding hat drei Seiten, eine positive, eine negative und eine komische." (Karl Valentin)

C
Cuin Themenstarter:in
92 Beiträge seit 2010
vor 13 Jahren

hmm... das Tutorial kenne ich schon.. weiß allerdings nicht wie ich das mit der IndexOf-Methode verbinden soll...

muss ich mir mal näher angucken... danke für den tipp^^

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

ohne Regex zu verwenden (wäre sicherlich besser) könntest du auch im "Suchstring" die Leerzeichen (welche ja ein Wort umgeben) verwenden.


string s = " ab ";

verwenden und dann den gefunden Index um 1 korrigieren. Allerdings musst du den Sonderfall wenn das Wort am Anfang steht behandeln.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo gfoidl,

ich wüsste nicht, warum es besser sein sollte, hier kein Regex zu verwenden. Im Gegenteil, dein Workround hat diverse Nachteile:

Worte am Anfang und am Ende des Strings werden nicht gefunden.
Worte, die durch andere Whitespaces als Leerzeichen getrennt sind (z.B. Tab/CR/NL), also z.B. am Ende und am Anfang einer Zeile stehen, werden nicht gefunden.
Worte, hinter (oder vor) denen direkt Satzzeichen stehen, werden nicht gefunden.

All die Probleme hat man bei Regex und \b nicht.

herbivore

J
111 Beiträge seit 2006
vor 13 Jahren
string.split()

Hallo Cuin,

wenn du wirklich kein RegEx benutzen möchtest, könntest du auch string.split(" ") verwenden. Da bekommst du ein string[] mit je einem Wort an einer Array-Adresse zurück. Dann könntest du alle Arrayelemente auf dein Suchwort hin überprüfen.

Vielleicht ein weing umständlich aber sollte funktionieren.

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo Jochen1980,

das verursacht allerdings nahezu alle Probleme, die auch bei gfoidl Vorschlag entstehen. Ok, man könnte an allen White-Spaces splitten, dass würde wieder ein Problem beseitigen. Das mit den Satzzeichen würde allerdings bleiben. Und wenn man versucht, alle möglichen Sonderzeichen als Trennzeichen zu definieren, wird es spätestens abwegig.

Also doch lieber Regex nehmen.

herbivore

C
Cuin Themenstarter:in
92 Beiträge seit 2010
vor 13 Jahren

Erstmal danke für eure Tipps^^

@gfoidl: Wie herbivore schon erwähnte, ist das nicht wirklich eine passende methode, da ich ja dann alle möglichen Fälle abfangen müsste, was sicher mehr Aufwand wäre als meine eigene umständliche Lösung. Aber trotzdem Danke für den Tipp.

@Jochen1980: Genau das ist mein Problem^^

Ich habe nämlich einen Text bzw. Liedtext. Als erstes überprüfe ich ob sich das Schlüsselwort überhaupt in dem Lied befindet, das funktioniert mit meiner Methode auch super. Danach wird NUR der Vers in dem sich das Wort befindet an eine Methode weitergegben. Diese Methode splittet dann nochmal den Vers mit string.splitt(). Mein Problem ist jetzt, das ich mein Schlüsselwort wieder in dem string[] finden muss.

mit IndexOf("ab") würde ich ja nur das Wort "aber" findet.

Theoretisch hättte ich auch jedes Element im Array per String.compare mit meinem Schlüsselwort vergleichen können, da die Satzeichen aber noch vorhanden sind, funktioniert der vergleich nicht mehr.

Wörter wie "aber", "Haben" oder "Rabe" dürfen nicht gefunden werden"
Wörter mit Satzzeichen wie "ab," oder "ab!" hingegen schon.

Hiermal ein Codeausschnitt:


string[] splitter = Text.Split(new char[] { ' ', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);

Bis jetzt habe ich noch keine Zeit gehabt, aber RegEx hört sich gut an. Ich versuche mal mein Problem damit zu lösen.

mfg Cuin

F
60 Beiträge seit 2010
vor 13 Jahren

Ich hätte gfoidls post ja so verstanden, dass RegEx hier die bessere Lösung ist..

Wie herbivore schon sagte, das klappt ganz gut:


            string input = "Hans wollte 40 km laufen, aber ab 20 km hatte er keine Puste mehr";
            string wordToFind = "ab";
            string regExWordDelimiter = "\\b";
            Regex regEx = new Regex(wordToFind + regExWordDelimiter, RegexOptions.IgnoreCase | RegexOptions.Multiline);
            IEnumerable matches = regEx.Matches(input);
            foreach (Match match in matches)
            {
                System.Console.Write(match.Value + " ");
            }

            System.Console.ReadKey();

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

Ich hätte gfoidls post ja so verstanden, dass RegEx hier die bessere Lösung ist..

wenigsten einer hats vestanden, aber ich gebe zu dass ich das heute beim Lesen auch anders gesehen habe. Also hab ich das da sehr unglücklich formuliert. Gemeint war schon:

ohne Regex zu verwenden (Regex wäre sicherlich besser)

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

C
Cuin Themenstarter:in
92 Beiträge seit 2010
vor 13 Jahren

OK, das ist schon mal eine gute Basis, an dem Regex(...) muss man zwar noch basteln aber vom grundgerüst schonmal ein guter Ansatz.

Wenn man als input das hier nimmt:


string input = "Hans blickte runter ins Tal";
string wordToFind = "er";

dann findet er nämlich auch das "er" in "runter"....

1.361 Beiträge seit 2007
vor 13 Jahren

Hi Cuin,

du hast das WordBoundary-Zeichen \b ja auch nur hinten angehängt.
Damit du das komplette Wort beschreibst, musst du schon an beiden Enden die WordBoundary setzen. Also häng vorn _und _ hinten das \b an.

beste Grüße
zommi

C
Cuin Themenstarter:in
92 Beiträge seit 2010
vor 13 Jahren

@zommi: Ich bin ja so doof^^ Ich habe das "\b" als backspace interpretiert^^
Ich habe mich schon gefragt, wieso man das wort "ab" in "Test ab," findet, da nach "ab" ja kein leerzeichen steht sondern ein komma...

ok, ich finde jetzt wirklich nur die substrings, die ich auch finden möchte! 😃

Vielen Dank für eure Hilfe.

Hier nochmal ein link, der mir erklärt hat, was das "\b" Zeichen bedeutet:

http://www.regular-expressions.info/wordboundaries.html

Zum Schluss hätte ich da aber noch eine Frage: Wäre es sinnvoll bzw. ist es möglich mit verhältnismäßigem Aufwand, die String.IndexOf methode mit hilfe der hier gefundenen Lösung so zu überschreiben, dass sie nur ganze Wörter sucht?

1.361 Beiträge seit 2007
vor 13 Jahren

Zum Schluss hätte ich da aber noch eine Frage: Wäre es sinnvoll bzw. ist es möglich mit verhältnismäßigem Aufwand, die String.IndexOf methode mit hilfe der hier gefundenen Lösung so zu überschreiben, dass sie nur ganze Wörter sucht?

Du kannst natürlich eine neue Methode "IndexOfWord(...)" anbieten. Eventuell lohnt es sich für dich daraus eine Extension Method für die String-Klasse zu bauen.

Den Index (die Position) des gefundenen Wortes bekommst du über den deinen Match. Genauer gesagt mit Match.Index.

beste Grüße
zommi

C
Cuin Themenstarter:in
92 Beiträge seit 2010
vor 13 Jahren

@zommi: Vielen Dank. Mit diesem Tipp habe ich jetzt endlich mein Problem lösen können^^

Hier die Lösung:


public static class HelperClass
    {
        public static int IndexOfWord(this string myString, string myWord)
        {
            int myIndex;
            Regex myRegex = new Regex("\\b" + myWord + "\\b");

            if ((myIndex = myRegex.Match(myString).Index) == 0)
            {
                if (myString.IndexOf(myWord) == 0)
                {
                    return 0;
                }
                else
                {
                    return -1;
                }
            }
            else
            {
                return myIndex;
            }
        }

        public static int IndexOfWord(this string myString, string myWord, int myPosition)
        {
            int myIndex;
            Regex myRegex = new Regex("\\b" + myWord + "\\b");

            if ((myIndex = myRegex.Match(myString, myPosition).Index) == 0)
            {
                if (myString.IndexOf(myWord, myPosition) == 0)
                {
                    return 0;
                }
                else
                {
                    return -1;
                }
            }
            else
            {
                return myIndex;
            }
        }
}

Leider liefert Match.Index den Wert 0 anstatt -1 zurück, falls es das gesuchte Wort im String NICHT findet. Es kann natürlich auch sein, dass sich das Schlüsselwort an Position 0 befindet, deshalb muss man, falls Match.Index 0 liefert nochmal überprüfen ob IndexOf -1 liefert oder nicht.
Falls ja, dann befindet sich das gesuchte Wort definitiv nicht im String, anderfalls muss IndexOf 0 liefern, was bedeutet, dass sich das Wort an Position 0 befindet.

Vielen Dank an alle für eure schnelle und gute Hilfe!

mfg Cuin

1.378 Beiträge seit 2006
vor 13 Jahren

Die Klasse Match hat auch eine Eigenschaft, die dir sagt ob auch wirklich gematcht wurde oder nicht und diese heißt "Success" oder ähnlich.

Lg XXX

C
Cuin Themenstarter:in
92 Beiträge seit 2010
vor 13 Jahren

Gute Idee^^ Ich sollte mal den Code bei gelegenheit umschreiben. thx

mfg Cuin

3.971 Beiträge seit 2006
vor 13 Jahren
Suche in einem String: nur ganze Wörter finden

Regex rgx = new Regex("\b([a-zA-Z]+)\b");
var mtch = rgx.Match(myString);
if (mtch.Success) {
  return mtch.Groups(1).Value;
}

Gibt das erste Wort zurück. Gebräuchlicher wäre aber wohl Regex.Matches.

@Cuin
Wenn du dein Regex dynamisch zusammenbaust, solltest du auf 2 Dinge achten:

  • Regex.Escape verwenden (in deinem Fall für myWord). Kritisch wird es beispielsweise wenn in myWort ein Punkt oder ein anderer Regex-Token enthalten ist.
  • Die Erstellung des Regex-Objektes selbst kostet auch Zeit. Besonders in deinem Fall kann es aus Performancegründen sinnvoll sein, kein dynamisches Regex zu verwenden sondern die zurückgegebenen Matches entsprechend nochmal zu vergleichen. Das Regex-Objekt am besten als static-Member Variable definieren oder aber RegexOptions.Compiled verwenden.

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

C
Cuin Themenstarter:in
92 Beiträge seit 2010
vor 13 Jahren

@kleines_eichhoernchen: Daran habe ich garnicht gedacht, dass die Satzzeichen in meinem übergebenen Wort als Regex-Token interpretiert werden. Das hätte zu unschönen Effekten geführt.

So, habe die Klasse nochmal umgeschrieben:


public static class HelperClass
    {
        private static Regex myRegex;

        public static int IndexOfWord(this string myString, string myWord)
        {
            myRegex = new Regex("\\b" + Regex.Escape(myWord) + "\\b", RegexOptions.Compiled);
            
            Match mtch = myRegex.Match(myString);

            if (mtch.Success)
                return mtch.Index;
            else
                return -1;
        }

        public static int IndexOfWord(this string myString, string myWord, int myPosition)
        {
            myRegex = new Regex("\\b" + Regex.Escape(myWord) + "\\b", RegexOptions.Compiled);

            Match mtch = myRegex.Match(myString, myPosition);

            if (mtch.Success)
                return mtch.Index;
            else
                return -1;
        }

Funktioniert so auch ganz gut und schaut auch besser aus^^

Den Satz verstehe ich allerdings noch nicht so ganz:

sondern die zurückgegebenen Matches entsprechend nochmal zu vergleichen.

Danke nochmals.^^

mfg Cuin

5.742 Beiträge seit 2007
vor 13 Jahren
  
private static Regex myRegex;  
  
public static int IndexOfWord(this string myString, string myWord)  
{  
   myRegex = new Regex("\\b" + Regex.Escape(myWord) + "\\b", RegexOptions.Compiled);  
  

Ist nicht wirklich sehr sinnvoll.
Du erzeugst das Regex bei jedem Methodenaufruf neu - dann brauchst du es auch nicht in einem Feld speichern.

Also besser:


private static readonly Regex myRegex = new Regex("\\b" + Regex.Escape(myWord) + "\\b", RegexOptions.Compiled);
//Oder
Match match = Rgex.Match(/*pattern*/, myString);

C
Cuin Themenstarter:in
92 Beiträge seit 2010
vor 13 Jahren

private static readonly Regex myRegex = new Regex("\b" + Regex.Escape(myWord) + "\b", RegexOptions.Compiled);
//Oder
Match match = Rgex.Match(/pattern/, myString);

Bei mir kommt da eine Fehlermeldung:

Einem statischen, schreibgeschützten Feld kann nichts zugewiesen werden (außer in einem statischen Konstruktor oder einem Variableninitialisierer).

Dein zweites Beispiel funktioniert leider nicht, da es keine Regex.Match-Überladung gibt, in der man die Anfangsposition mit angeben kann. (Für meine zweite Methode).

mfg Cuin

5.742 Beiträge seit 2007
vor 13 Jahren

Bei mir kommt da eine Fehlermeldung:

Das Zuweisen in den Methoden selbst musst du dann natürlich weglassen.

C
Cuin Themenstarter:in
92 Beiträge seit 2010
vor 13 Jahren

Achso, jetzt versteh ich^^

naja, funktioniert aber leider trotzdem nicht, da es ja keine match-überladung gibt, bei der man die anfangsposition mitangeben kann...