home *** CD-ROM | disk | FTP | other *** search
/ Turbo Toolbox / Turbo_Toolbox.iso / spezial / 01 / pattern / pattern.doc < prev   
Encoding:
Text File  |  1988-11-03  |  13.2 KB  |  338 lines

  1.  
  2. Eine Pattern-Matching-Toolbox für Turbo Prolog
  3.  
  4. Im Englischen sind die Begriffe pattern und match vieldeutig und
  5. flexibel: ein pattern ist ein Muster, ein Modell, eine Schablone;
  6. match bedeutet vergleichen, gegenüberstellen oder passen. Pattern
  7. Matching ist das Vergleichen eines Gegenstandes mit einem Muster
  8. -zum Beispiel der Vergleich eines Stückes farbigen Stoffes mit
  9. einem Muster der Farbe Kobaltblau. Im EDV-Kontext ist der mit
  10. einem bestimmten zu vergleichende Gegenstand meist ein String,
  11. also der Datentyp, der der menschlichen Schnittstelle am nähesten
  12. steht. Eine typische Fragestellung ist: entspricht ein gegebener
  13. String dem Muster einer Zahl? eines Bezeichners? eines Filenames?
  14. einer Anweisung? eines logischen Ausdrucks? usw. Man begegnet
  15. solchen Fragestelllungen meist bei der Verarbeitung von
  16. Eingabestrings, natürlichsprachigen Texten sowie im Rahmen einer
  17. Scanner- oder Parserumgebung.
  18.  
  19. Man halte sich vor Augen, wie ähnlich gelagerte Aufgaben durch
  20. eine konventionelle prozedurale Programmiersprache, etwa Pascal,
  21. gelöst werden. Wie kann beispielsweise ein String als
  22. DOS-Filename identifiziert werden? Vermutlich schreibt man sich
  23. hierzu eine kleine Funktion oder Prozedur, in der eine Schleife
  24. jedes Zeichen des Strings abtastet und überprüft, ob es zum
  25. zulässigen Zeichenbereich gehört. Das kann solange gehen, bis man
  26. über acht Zeichen hinausgerät, oder an das Ende des Strings oder
  27. auf einen Punkt stößt. Passiert letzteres, läßt man eine neue
  28. Schleife laufen, die die jetzt mutmaßlich folgende Extension
  29. Zeichen für Zeichen in ähnlicher Weise durchkontrolliert. Einige
  30. Umsicht ist erforderlich, damit alle Fehlschlagsbedingungen auch
  31. richtig erfaßt werden.
  32.  
  33. Der Mensch selbst denkt nicht so prozedural und atomistisch. Wir
  34. haben wahrscheinlich ein allgemeines Muster eines DOS-Filenames
  35. in unserem Kopf, das etwa folgendermaßen umschrieben werden
  36. könnte:
  37.  
  38.     ein eins bis acht Zeichen langer String aus einem bestimmmten
  39.     Zeichenbereich, dem ein Punkt und eine bis zu drei Zeichen
  40.     lange Extension folgen kann.
  41.  
  42. Der Wunsch, solche Muster als besondere Datenstrukturen zu
  43. formulieren und damit im Sinne einer einfachen und natürlicheren
  44. Programmierung zu nutzen, liegt nahe. Das erste zusammenhängende
  45. Konzept integrierter Pattern-Matching-Werkzeuge wurde Ende der
  46. sechziger Jahre durch die Programmiersprache SNOBOL vorgestellt.
  47. Wer einen kurzen Einblick in die Musterstrategien von SNOBOL
  48. vermittelt haben will, kann James F: Gimpels Artikel "Text
  49. Prozessing in SNOBOL4", in Byte 11.2(1986) nachlesen. Hier soll
  50. im weiteren gezeigt werden, in welcher Weise einige der
  51. SNOBOLschen Konzepte für die im ganzen leichter handhabbare und
  52. übersichtlichere Sprache PROLOG praktisch genutzt werden.
  53.  
  54.  
  55.  
  56.  
  57.  
  58. Das match-Prädikat
  59.  
  60. Der TURBO-PROLOG-Code, der hier vorgestellt wird, bildet eine
  61. Toolbox, die im wesentlichen nur ein Werkzeug enthält: das
  62. Prädikat match. Aber dieses Prädikat ist ein Vielzweckwerkzeug: in
  63. seinem zweiten Argument kann eine ganze Reihe der in SNOBOL
  64. bekannten Muster-Grundbausteine("pattern primitives") für die
  65. Erstellung komplexer Muster kombiniert werden.
  66. match ist wie folgt definiert:
  67.  
  68.   match(SubjectString,PatternList,Matchedlist) (i,i,o)
  69.  
  70. Es wird weitgehend die Termonologie von SNOBOL beibehalten; daher
  71. die Bezeichnung des zu vergleichenden Gegenstandes als
  72. "SubjectString". Das Muster ist eine "PatternList", die sich aus
  73. ein oder mehreren "pattern Primitives" zusammensetzt - dazu
  74. gleich mehr. Die "MatchedList" enthält die Teile des
  75. "SubjectStrings", die den in der "PatternList" genannten
  76. Musterbausteinen entsprechen. Match vergleicht die
  77. Subjekt-Zeichenkette mit einem als Liste formulierten Muster;
  78. wenn es zu einer Entsprechung kommt, dann werden die passenden
  79. Teile in Aufgabenliste eingereiht; ansonsten schlägt das Prädikat
  80. fehl.
  81. Hier ein Beispiel:
  82.  
  83.    match("if x<10 then stop := true;",[break([":="])],X).
  84.  
  85. Der Subjekt-String ist eine Zeile aus einem Pascal-Programm. das
  86. Muster besteht aus dem einzelnen Grundbaustein "break", einem
  87. Funktor, der eine Stringliste als Argument mit - in diesem Fall -
  88. der einzelnen Konstante ":=" enthält. Dieses Muster paßt auf
  89. beliebige Subjekt-Strings, die ein ":=" aufweisen. Paßt das
  90. Muster (wie hier), so wird der vor dem Schlüsselstring ":="
  91. liegende Teilstring in die "MatchedList" eingereiht. Läßt man
  92. KIT.PRO laufen und gibt obige Goal, so erscheint die Lösung
  93.  
  94.    X = ["if x<0 then stop"]
  95.  
  96. Man kann break also benutzen, ein Muster zu konstruieren, zu dem
  97. jeder Teilstring bis zu einer bestimmten Schlüsselsequenz paßt.
  98. Werfen wir einen Blick auf ein paar weitere solcher Bausteine.
  99.  
  100.  
  101. "any(Stringlist)" klärt die Frage, ob der Subjekt-String an der
  102. gegenwärtigen Stelle einen der in "Stringlist" aufgeführten
  103. Teilstrings enthält. Beispiel:
  104.  
  105.    match("if x<0 then stop := true;", [any(["<=",":="])], X)
  106.  
  107. (enthält der Subjekt-String am Anfang einen Substring der Form
  108. "<=" oder ":=" ?) Die Antwort lautet nein, "No Solution", weder
  109. := noch <= steht am Anfang des Subjekt-Strings ( der
  110. gegenwärtigen Stelle). Jetzt versucht man die Kombination von
  111. break and any:
  112.  
  113.   match("if x<0 then stop := true;",
  114.       [break([":=","<="]); any (["<=",":="])], [_,Operator]).
  115.  
  116. Operator = ":=" wird PROLOG auf dieses Goal hin melden und damit
  117. eine Antwort auf die Fragen "Kommt einer der genannten Operatoren
  118. vor? Welcher erscheint zuerst?" geben.
  119.  
  120.  
  121. "notany(chartlist)" überprüft, ob bestimmte Zeichen an einer
  122. bestimmten Stellle im Subjekt-String nicht vorkommen. Im folgenden
  123. Beispiel wird eine Stelle identifiziert, an der kein Spatium
  124. hinter einem Komma steht:
  125.  
  126.    match("alles rennet,rettet";[break([","], notany([' '])], X).
  127.  
  128.  
  129. "span(Stringlist)" akzeptiert jede Zeichenkette, die aus den in
  130. "Stringlist" enthaltenen Zeichen besteht. Die Musterüberprüfung
  131. wird beim Erreichen eines Zeichens, das nicht in "Stringlist"
  132. vertreten ist, oder beim erreichen des Endes vom Subjekt-String
  133. erfolgreich abgeschlossen. Auszuprobierendes Beispiel:
  134.  
  135.    match("0110 xyz", [span(["0", "1"])], [Binärzahl]).
  136.  
  137.  
  138. "len(N)" ist ein Grundmuster, das jeden Teilstring der Länge N
  139. abdeckt:
  140.  
  141.     match("512.Anton"; [len(3)], [laufende Nummer]).
  142.  
  143.  
  144. "rtab(0)"  deckt alles bis zum Ende des Subjekt-Strings ab:
  145.  
  146.     match(512.Anton", [len(5), rtab(0)], [Anfang,Rest]).
  147.  
  148.  
  149. "rpos(0)" überprüft, ob das Muster den gesamten Subjekt-String
  150. erfaßt:
  151.  
  152.     match("512.Anton", [len(10), rpos<(0)], _).
  153.  
  154.  
  155. "lit(String)" überprüft, ob der Subjekt-String einen bestimmten
  156. Teilstring an der gegenwärtigen Stelle enthält:
  157.  
  158.     match("Anton", [len(2), lit("ton")], _).
  159.  
  160.  
  161. "bal" überprüft, ob ein ausgeglichenes Klammerverhältnis bis zu
  162. einer bestimmten Stelle vorliegt. Das folgende Beispiel bricht
  163. einen Klammerausdruck auf:
  164.  
  165.     match("f(g(h))", [break(["("]), lit("("), bal, lit(")"],
  166.                    [Funktor, _, Argument, _]).
  167.  
  168.  
  169.  
  170.  
  171.  
  172.  
  173. Arbeiten mit der Toolbox
  174.  
  175. Man beginnt gewöhnlich damit, daß man sich die Datei KIT.PRO in
  176. den Work-File kopiert, umbennet und nn je nach Problemstellung
  177. modifiziert. Man kann auch einfach KIT-PRO am Anfang des eigenen
  178. Programms inkludieren (dies tun die Beispielprogramme BP1 und
  179. BP2). KIT-PRO selbst inkludiert die drei eigentlichen
  180. Toolbox-Dateien Match.1, Match.2 und Match.3 und wird damit zu
  181. einem lauffertigen Programm, das ohne weitere Deklaration über
  182. das match-Prädikat verfügen kann. Ein einzelnes
  183. Dienstleistungsprädikat - literal(symbol,stringlist) - wird
  184. explizit aufgeführt: es enthält in seinen Klauseln die üblichen
  185. Unterteilungen des Zeichensatzes. Dies kann erhebliche Arbeit
  186. sparen. Wenn es etwa darum geht, ein Muster für eine
  187. alphanumerische Sequenz zu erstellen, dann könnte man sich zwar
  188. die Mühe machen zu schreiben:
  189.  
  190.     match(string, span(["A", "B" <...etc., alle Buchstaben und
  191.       Zahlen einzeln aufgeführt...>"8", "9"]), X)
  192.  
  193.  
  194. Aber mit Hilfe von "literal" geht's auch so:
  195.  
  196.     literal(alfanums, A),
  197.     match(String, [span(A)], X)
  198.  
  199.  
  200. Um das Erscheinungsbild des match-Prädikats zu entlasten,
  201. empfiehlt es sich besonders für komplexe Mustwer, ein eigens
  202. Musterprädikat wie
  203.  
  204.     pattern(symbol, patternlist)
  205.  
  206. zu definieren. Eine mögliche pattern-Klausel mag dann so
  207.  aussehen:
  208.  
  209.      pattern(integer, [span(Digits)]) :-
  210.        literal(digits, Digits).
  211.  
  212. Dies ermöglicht überschaubare Subgoals wie
  213.  
  214.      pattern(integer, IntegerPattern),
  215.      match(Eingabe, IntegerPattern, [Betrag]).
  216.  
  217. Gegebenenfalls lassen sich aufeinander aufbauende, komplexere
  218. Muster erstellen:
  219.  
  220.      pattern(integer, [span(Digits)]):-
  221.         literal(digits, Digits).
  222.      pattern(real, [Int| X]) :-
  223.         pattern(integer, [Int]) append([lit(".")], [Int], X).
  224.      pattern(exponential, X):-
  225.         pattern(real, R),
  226.         pattern(integer, Exponent),
  227.         append(R, [lit("E")| Exponent], X).
  228.  
  229.  
  230. Die zusammmengesetzten Muster vom Typ real und exponential
  231. greifen hier auf das/die vorher definierte(n) Muster zurück.
  232.  
  233.  
  234.  
  235. Anwendung: Ein simpler Scanner (BP1.PRO)
  236.  
  237. In seinem Buch Algorithmen und Datenstrukturen(Stuttgart, 1983,
  238. S. 45-47) skizziert Niklaus Wirth einen in Pascal formulierten
  239. Scanner, der aus einem Zeichenstrom Sprachsymbole wie Bezeichner,
  240. Zahl, und die Operatoren :=, <= und >= identifiziert. BP1.PRO
  241. löst die gleiche Aufgabe unter Zuhilfenahme der Pattern-Matching-
  242. Toolbox.
  243.  
  244.  
  245. Der Mini-Scanner verlangt eine beliebige Zeileneingabe des
  246. Benutzers und identifiziert dann alle Sprachsymbole, die einem
  247. der in den pattern-Klauseln definierten Muster entsprechen.
  248. Anders als in Wirth's PASCAL-Code, in dem sich die Lösung mit
  249. Hilfe des üblichen Geflechts von Bedinguns- und
  250. Kontrollstrukturen gestaltet liegt im Prolog-Code der Schwerpunkt
  251. eher statisch auf der Definition von vier Grundmustern. Das
  252. komplexeste der vier Muster, das Bezeichner-Muster, soll zur
  253. Erläuterung herausgegriffen werden:
  254.  
  255.    pattern(identifier,
  256.       [span([""," "]) any(letter), span([""|Alfa]), rtab(0)]):-
  257.       literal(letters,letter), literal(alfanums,Alfa).
  258.  
  259.  
  260. Das Muster umfaßt nicht den eigentlichen Bezeichner, sondern auch
  261. einen etwaigen linken und rechten Kontext. span([""," "]"
  262. überbrückt alle etwaigen führenden Leerzeichen. Der Einsatz des
  263. Nullstrings (der auch in any und break vorkommen kann) liefert
  264. die Möglichkeit, ein Element als optional zu kennzeichnen. Wegen
  265. des Nullstrings schlägt das obige span auch dann nicht fehl, wenn
  266. die Zeile mit Leerzeichen beginnt. any(letter) erfaßt einen
  267. einzelnen Buchstaben, der als erstes Zeichen eines Bezeichners
  268. notwendig ist. span([""|alfa]) läßt sich lesen als "Null oder
  269. alfa", also eine optionale alphnumerische Sequenz. rtab(0) wird
  270. abschließend benutzt, um den Rest der Zeile für eine spätere
  271. Variable zu erfassen. Der Bezeichner selbst läßt sich aus den
  272. Gegenwerten für das zweite und dritte Element einer
  273. "MatchedList" wieder zusammensetzen, und so wird das auch in den
  274. entsprechenden classify-Klausel prakziziert.
  275.  
  276.  
  277. Mehrdeutige Muster(BP2.PRO)
  278.  
  279. Die eigentliche Stärke der Toolbox wird aber erst deutlich, wenn
  280. mit mehr oder komplexeren Mustern zu rechnen ist. Dann schwillt
  281. ein vergleichbarer Pascal-Code überproportional an, während im
  282. Prolog-Code eventuell lediglich die Zahl der pattern-Klauseln
  283. zunimmt.
  284.  
  285. BP2.PRO soll demonstrieren, mit welcher Leichtigkeit
  286. unterschiedlichste Muster zusammengestellt werden können. Statt
  287. einer ganzen Zeile wird hier lediglich ein einzelnes mutmaßliches
  288. Sprachsymbol angefordert, und die Idendifikation des
  289. Eingabestrings erfolgt einfach über den Identifikator des
  290. passenden pattern-Prädikats. Wird ein mehrdeutiges Sprachelement
  291. eingegeben, so soll auch eine Mehrfachklassifikation erfolgen -
  292. 20 soll z.b. sowohl als Integer- wie als mögliche Hexzahl
  293. identifiziert werden. Die folgende Übersicht zeigt ein
  294. repräsentatives Korpus mit der jeweils gewünschten
  295. Identifikation:
  296.  
  297. Sprachelement          verlangte Identifikation
  298. -----------------------------------------------
  299. var12                  identifier
  300. +24                    integer
  301. 1F, 20H. $20           hex
  302. 1001b                  binaer
  303. -100.45, 6.7E-6        real
  304. 'Jim Knopf'            literal
  305. :=, <=                 operator
  306. John                   name, identifier
  307. 20                     integer, hex
  308. FF                     hex, identifier
  309. 1001                   integer, hex, binaer
  310. !                      -
  311.  
  312.  
  313. Diese Variationsvielfalt nähert sich Verhältnissen in
  314. natürlichsprachigen Texten an und ist sicher nur mit äußerster
  315. Geduld prozedural zu bewältigen. BP2.PRO zeigt demgegenüber, wie
  316. die entsprechenden Muster mit Hilfe der Toolbox vergleichsweise
  317. mühelos zu realisieren sind.
  318.  
  319.  
  320.  
  321.  
  322. BP3.PRO (6 Prädikate/50 Zeilen) zeigt am Beispiel von
  323. Dos-Zugriffspfaden und -Filenamen die Behandlung von Mustern mit
  324. iterativen Bestandteilen. BP4.PRO (5 Prädikate/60 Zeilen) offeriert
  325. eine Lösung des Standarsproblem "römische Zahlen" (beide
  326. Richtungen) BP5.PRO (12 Prädikate/260 Zeilen) ist ein
  327. Pretty-Printer, mit dem man versuchen kann, Pascal-Listings in
  328. die richtige Form - einschließlich großgeschriebenen
  329. Schlüsselwörtern und korrekt eingerückten Kontrollstrukturen - zu
  330. bringen. BP6.PRO (12 Prädikate/180 Zeilen) schließlich
  331. demonstriert einen Algorithmus zur Validierung propossitionaler
  332. Formeln vom Typ "or(to be, not(to be))", ohne dabei auf eine
  333. Wahrheitstabelle zurückzugreifen.
  334.  
  335.  
  336.  
  337. (Manfred Jahn)
  338.