In der Computerei spielt das Auffinden von Teilstrings innerhalb eines Strings eine zentrale Rolle. Ein kleines C-ProgrÀmmchen erledigt diesen Job. Daà es dabei auch die Verwendung von Wildcards erlaubt, versteht sich von selbst...
Fast jede Programmiersprache hĂ€lt irgendeinen Befehl bereit, mit dessen Hilfe man Substrings finden kann. INSTR, POS, SUBSEG sind einige der bekannteren Vertreter: UNIX stellt fĂŒr derlei Abfragen die Prozedur fgrep zur VerfĂŒgung. In C dagegen gibt es standardmĂ€Ăig nur die beiden Funktionen index() und rindex(), die auf das erste bzw. letzte Vorkommen eines Zeichens innerhalb einer Zeichenkette prĂŒfen. Mager.
Wilde Karten -neu gemischt
Die Funktion match() ist nicht nur in der Lage, Substrings ausfindig zu machen, sondern sie erlaubt auch die Verwendung von sogenannten Wildcards. Diese Zeichen ersetzen entweder genau ein beliebiges Zeichen (jedoch nicht '\0â), dann nennt man sie Einzelquantoren. oder aber beliebig viele (auch null) beliebige Zeichen - dann heiĂen sie eben Allquantoren. Sehr sinnvoll erweisen sich diese Wildcards in Verbindung mit der Suche nach bestimmten Dateien, wozu die GEMDOS-Funktion SFIRST (0x4E) die beiden Quantoren '?' und **â bereitstellt. Ok, kennt jeder. Allerdings wird dabei ein wenig âgemogeltâ, weil der Dateiname in zwei Segmente aufgegliedert wird (Name und Extender). Steht nun in einem der Bereiche ein Sternchen, wird er ab dieser Stelle mit Fragezeichen aufgefĂŒllt, egal ob hinter dem Stern noch etwas kommt oder nicht! FĂŒr das ST-Directory-System magâs ja genĂŒgen, in anderen Systemen wie UNIX sind da schon etwas diffizilere Abfragen nötig (Beispiel gefĂ€llig? Bitte: pDoc?_V0??). Abgesehen von Dateisystemen gibt es aber auch noch Textverarbeitung. Die Praxis hat gezeigt, daĂ es hin und wieder recht nĂŒtzlich ist, eine Datei auf das Vorkommen einer Zeichenkette zu untersuchen. Hier lĂ€Ăt sich match() gewinnbringend zum Einsatz bringen. Der Patternstring (also der, in dem drinsteht, was gesucht werden soll) darf eine beliebige Kombination aus normalen Zeichen, All- und Einzelquantoren enthalten und muĂ nach dem Sourcestring (das ist der andere!) als zweiter Pointer ĂŒbergeben werden. Der dritte Parameter ist die Adresse eines Stringpointers. Falls das Muster gefunden werden kann, enthĂ€lt er die Adresse, ab der das letzte Muster gematcht werden konnte. Ein Beispiel: Sourcestring ="holladibollaâ, Patternstring==âoll??â gibt auĂer dem Ergebnis TRUE den Zeiger auf âdibollaâ zurĂŒck. Das hat folgenden Vorteil: möchte man untersuchen, ob die Suchzeichenfolge nochmal vorkommt, muĂ man die Funktion match nur noch einmal mit dem um 1 erhöhten Stringzeiger aufrufen und schon wird richtigerweise FALSE zurĂŒckgegeben, weil durch die beiden Einzelquantoren noch mindestens zwei Zeichen hinter âollâ folgen mĂŒĂten. WĂ€re dagegen beim ersten Aufruf âholladibolla" zurĂŒckgegeben worden, was sicherlich ein richtiges Ergebnis gewesen wĂ€re, hĂ€tte ein zweiter Aufruf mit âolladibollaâ wegen des fĂŒhrenden Allquantors () wieder ein âTRUEâ ergeben, was sicherlich nichts weltbewegend Neues gebracht hĂ€tte. Ăbrigens kann man die beiden Quantoren beliebig umdefinieren (s. Demo), falls sie Bestandteil des Sourcestrings sein sollten!
Kochrezept fĂŒr âMatch mit Quantorâ
ZunĂ€chst einmal mĂŒssen die StringlĂ€ngen ermittelt werden, damit man auch weiĂ, wann man spĂ€testens mit der Suche aufzuhören hat. Sollte mindestens einer der Strings leer sein, erĂŒbrigt sich natĂŒrlich die Suche. In einer Endlosschleife wird dann der String auseinandergenommen. Enthalten beide Strings ein normales Zeichen (also keinen Quantor), wird die Suche erfolglos beendet, wenn sie nicht ĂŒbereinstimmen. EnthĂ€lt der Patternstring einen Einzelquantor, muĂ im Sourcestring noch mindestens ein Zeichen vorhanden sein. Soweit, so hoopy. Lustig wird's erst beim Auftreten eines Allquantors. Angenommen, man sucht im String âOh du zerfrettelter Grunzwanzlingâ das Muster ânzlâ. LĂ€uft man sequentiell durch den String, findet man zuerst das ânzâ von âGrunzâ. Das Problem ist, daĂ kein â1â folgt und also das Muster hier nicht greift. Wohl aber bei âwanzlingâ. Schlimmer wirdâs, wenn Quantoren geschachtelt auftreten (*n?l??*)! Man kann sich beliebig komplexe Suchzeichenfolgen ausdenken (die nur so von Quantoren strotzen), und ebenso fiese Sourcestrings, bei denen es einem vom bloĂen Hinsehen schwindlig wird. Mit sequentiellen Algorithmen gerĂ€t man hier doch sehr schnell an die Grenzen des Abstraktionsvermögens! Viel einfacher ist in solchen FĂ€llen die Verwendung von rekursiven Prozeduren. Betrachten Sie einmal, was passiert, wenn ein Allquantor im Muster auftaucht: ZunĂ€chst wird geprĂŒft, ob es das letzte Zeichen im Muster ist. Falls ja, ist die Suche erfolgreich gewesen. Ansonsten muĂ das nĂ€chste Zeichen im String gesucht werden, das mit dem nĂ€chsten Zeichen des Musters (also dem hinter dem Allquantor) ĂŒbereinstimmt. Ein Sonderfall tritt auf, wenn der String bereits vollstĂ€ndig durchsucht wurde, aber im Muster ein weiterer Allquantor auftaucht. Dieser matcht nĂ€mlich per Definition auch null Zeichen, so daĂ die Suche nicht einfach erfolglos abgebrochen werden darf! Andernfalls darf die Rekursion ihre FĂ€higkeiten demonstrieren: Falls es möglich ist, den String ab der aktuellen Position zu matchen, war die Suche erfolgreich: sonst wird es rekursiv einfach ab der nĂ€chsten Position versucht und zwar solange, bis der String komplett abgeprĂŒft wurde (is>ls). In diesem Falle ist die Suche dann wirklich erfolglos. Das Schöne an der RekursivitĂ€t ist, daĂ sie immer zum richtigen Ergebnis gelangt; man braucht sich ĂŒberhaupt nicht darum zu kĂŒmmern, wieso und weshalb - sie tut's einfach! Herrlich. Mittels dem printf()-Statement zu Beginn der Prozedur kann man die Arbeitsweise des Algorithmus bei Auftreten von Allquantoren nachvollziehen.
MatchQ ist sehr flexibel: Ăbergibt man ihr als Maske eine in Allquantoren eingebettete Zeichenkette (z.B. blub), verhĂ€lt sie sich genau wie INSTR oder POS; lĂ€Ăt man den letzten Stern weg, so werden nur solche Strings gematcht, in denen die Maske am Ende auftaucht (wie bei "Lausblubâ), und insgesamt kann man damit viel filigranere Dateisuchen veranstalten als mit SFIRST.
Fazit: Match! kann zwar nicht als Ersatz fĂŒr die Query Language einer Datenbank dienen, erleichtert aber das Suchen von Dateien und Texten nicht unerheblich.
MS
/******************************************************\
* > > > M A T C H ! < < < *
*======================================================*
* Univers.Suchfkt. z.Auffinden v.Substrings ĂŒ. Muster *
* Sprache: MEGAMAX C Autor: M. Schumacher 15.02.88 *
\******************************************************/
#include <stdio.h>
match(what, how, where, all, one)
char *twhat, *how, **where, all, one;
{
char *s, *p; /* Stringpointer fĂŒr what/how */
int ls=0, lp=0, is=1, ip=1; /* StringlÀngen, Laufvariablen */
*where=what; /* Falls s gematscht werden kann, dann ab hier */
s=what; while (*s++) ls++; s=what; /* StringlÀngen bestimmen */
p=how; while (*p++) lp++; p=how;
if (!(ls*lp)) return(0); /* Leerstring ĂŒbergeben = >FAIL! */
printf("Vergleiche: >%s< (%d) mit >%s< (%d):\n", s, ls, p, lp);
while (1)
{ if (*p == one)
{ if (is>ls) return(0);
++is; ++s; ++ip; ++p;
if ((is>ls) && (ip>lp) ) return(1);
if (ip>lp) return(0);
continue;
}
if (*p == all)
{ if (ip == lp) return(1);
++ip; ++p;
if (is>ls) continue;
do
{ if (match(s, p, where, all, one)) return(1);
++is; ++s;
}while Cis<=ls);
return();
}
if ( (is>ls) || (!(*s++ == *p++ ))) return(0);
++ip; if ((++ is>ls) && (ip>lp) ) return(1);
}
}
main()
{
char str[80], pat[80], *res, all, one;
printf("Teststring eingeben! ");
gets(str);
printf ("Allquantor ! *%c", 8); all=getchar();
if (all == '\n') all = '*' else getchar();
printf("Einzelquantor : ?%c", 8); one=getchar()!
if (one == '\n') one='?'; else getchar();
while (1)
{ printf("\n\nSuche nach; "); gets(pat); res=str;
while (match(res, pat, &res, all, one))
{ printf("TRUE: <%s>\n", res); res++; }
puts("FAIL");
}
}