Anleitung und Dokumentation
|
Pascal Christoph |
|
Hier eine übersichtliche Grafik vom Original, von Gustav Vella, entnommen den spinfo-Seiten
Das Indizieren geschieht entweder über das Gui oder kann durch starten des Programms Main.class mit Übergabe eines Parameters (der als Pfad gewertet wird) erfolgen. Dazu wird in das Verzeichnis gewechselt, in dem sich Main.class befindet. Dort wird z.B. eingegeben : java Main /meinPfad/ wobei darauf zu achten ist, welches Betriebssystem benutzt wird, um sich an die Pfadnotation desselben zu halten. Obiges Beispiel ist die Unix -(und Derivaten-) Notation. Grundsätzlich ist zwischen zwei Modi zu unterscheiden: dem Indizierungsmodus und dem Suchmodus. Im ersten werden alle relevanten Texte indiziert, d.h. es werden alle für die Suche nach Wörtern relevanten Daten in eine Direktzugriffstruktur, den Index, gespeichert (s.u.). Dieser Index wird dann serialisiert (auf die Festplatte gespeichert), gezippt ii und steht der Suchmaschine für die Suche nach Wörtern zur Verfügung. Im Suchmodus wird der serialisierte Index entzippt,rekonstruiert und dient dann dem Auffinden der Dokumente, in denen die gesuchten Wörter enthalten sind, und der Ausgabe dieser Wörter in ihrem jeweiligen Kontext. 3.2.1 Benutzerschnittstelle Die Vorlage verfügt über eine einfache graphische Benutzeroberfläche (Grafical User Interface, GUI). Diese GUI wurde mir im Quellcode 'geschenkt', einerseits damit die lästige Arbeit mit der DOS-Konsole wegfallen kann, andererseits, damit ich ein schönes Beispiel einer einfachen GUI bekomme, das ich je nach Bedarf ( unabhängig von der Aufgabenstellung , d.h. irgendwann mal später) evtl. weiter benutzen kann (nett :-). 3.2.1.1 Ein- und Ausgabe Die Kommunikation mit dem/r Benutzer/in geschieht über das in der Main global definierte Objekt Settings.user. Dieses Objekt verfügt u.a. über eine globale Komponente talk, die die Methoden des Interfaces TalkInterface implementiert. Diese talk-Komponente des Users wird in der Main zunächst auf die Console umgelenkt (vgl. die Klasse ConsoleTalk.java). Bevor die GUI geladen wird, kann sie zum Einlesen und Ausgeben in der Konsole benutzt werden. Bei der Initialisierung der GUI wird die talk-Komponente dann auf die GUI umgelenkt, genauer auf ein Objekt des Typs GuiPrintStream, welches das TalkInterface für die GUI implementiert (vgl. den Konstruktor der Klasse Gui). Von da an wird das globale Objekt Settings.user.talk benutzt, um im Programm-Ausgabe-Feld der GUI (s.u.) Ausgabeoperationen vorzunehmen. 3.2.1.2 GUI Die GUI kommuniziert mit den weiteren Komponenten der Suchmaschine über das Objekt Settings.user, das ihrem Konstruktor übergeben wird (s. 3.2.1.1 o.), und über die beiden Methoden Engine.buildIndex(...) und Engine.findWords(...). Die beiden Modi der GUI und ihre Schnittstellen werden im Folgenden kurz beschrieben. Indizierungsmodus :
|
|
|
|
3.2.3 Die einzelnen Komponenten
|
|
|
|
3.3.3 Suchanfragen
Diese drei Typen von
Abfragen
sind beliebig miteinander kombinierbar, so dass folgende Anfrage von
der
QueryEngine entsprechend bearbeitet wird: |
|
3.3.5 Testbeispiele |
|
Die Verwaltung der Länge des Arrays ist so implementiert worden, dass bei jedem Akkumulieren einer Fundstelle ein Array umkopiert wird. Dies ist zwar langsam spart aber RAM.
3.6.3 Wiederfinden von Fundstelleninformationen im KeyData, Sortierungen Bei der Bearbeitung von Abfragen (processQuery) gibt es zwei Unterschiede, nämlich wenn die Daten im (ungewichteten) Baum oder in der HashMap abgespeichert sind. Es wird also entweder der Baum traversiert, nach dem Inorder Schema (LWR) oder das angefragte Wort in ein Key gewandelt und direkt der HashMap übergeben, um einen Match abzuwarten. Die Methode der HashMap ist die Schnellste. In der Ausgabenmethode QueryEngine.contextAusgabe() werden beim Ranking die PositionDatas in einer sortierten Map gespeichert, und zwar mit Hilfe der Klasse ReverseStringComparator() , so daß diejenigen Dokumente mit dem höchsten Rang als erste angezeigt werden.
3.6.4 Serialisierung Um Datenstrukturen in Java zu serialisieren, d.h. binär auf ein File auszulesen bzw. von einem File einzulesen (was auch externalize und internalize genannt wird), genügte es, die zu serialisierende Datenstruktur und die darin enthaltenen Objekte von Klassen zu wählen, die das Interface Serializable implementieren. Das Interface java.io.Serializable deklariert keine Methoden. Die Angabe implements java.io.Serializable in einer Klassendefinition dient lediglich als Kennzeichnung dafür, dass ein Objekt der betroffenen Klasse serialisierbar ist. Ein solches Objekt kann man dann einfach mit einer Instanz der Klasse ObjectOutputStream, mit Hilfe derer Methode writeObject(...) auf eine Datei ausschreiben. Die somit gespeicherte Datenstruktur (der Index bei uns hier) kann dann entsprechend mit der Methode readObject(...) einer Instanz der Klasse ObjectInputStream wieder eingelesen und somit rekonstruiert werden. Es wird, anders als in der Vorlage, die Instanz der Klasse ObjectOutputStream mit einem BufferedOutputStream initialisiert. Damit wurde das Serialisieren um Faktor 10 schneller. C'est tout!!
3.7 Alternative Direktzugriffsstrukturen und Zeitmessung D ie Direktzugriffsstrukturen, die KeyData-Objekte speichern, den Index also, sind sowohl als HashMap als auch durch einen eigenen Binärbaum implementiert. Der Benutzer sucht sich aus, ob er die Wörter in eine HashMap oder in einen Binärbaum speichern möchte. Die Zeitmessungen ergaben folgendes Bild:
Die Anzahl der Dokumente lag bei 629, ihre Gesamtgröße bei 10MB , und die Anzahl der indizierten Wörter bei über 700.000, der daraus erstellte pureIndex.bin hat eine Größe von 9MB. Es ergibt sich daraus, dass der Binärbaum recht ordentlich arbeitet. Allerdings wird bei größeren Datenmengen der Binärbaum immer langsamer , da er nicht gewichtet ist. Letztendlich ist die HashMap die erste Wahl. 3.10 Anmerkungen zur Funktionalität von SearchEngine,Aussicht SearchEngine ist schon eine ganz brauchbare Suchmaschine. Nett wären noch eine verbesserte graphische Benutzeroberfläche (dynamische Auswahl von Stopwortlisten etwa, wieviele Dokumente gezeigt werden sollen ...) . Eine praxisorientierte Anwendung wird sicherlich einige zusätzliche (bzw. optimierte) Funktionalität haben müssen. Dies wurde bereits im Laufe der Beschreibung, z.B. in Bezug auf den Crawler erwähnt. Das Halten des kompletten Indexes im Speicher ist bei einer reellen, webbasierten Applikation selbstverständlich auch nicht machbar. Hier wird man in der Praxis eine Direktzugriffsstruktur auf Platte benutzen wie z.B. einen B-Tree (Beyer-Baum). Auch sollte eine Suchmaschine nicht nur eine AND-, sondern auch eine ODER und NEAR usw. -Verknüpfung anbieten. Die Benutzung von Wildcard in Suchabfragen müsste auch erlaubt sein (Stichwort 'Stemming'). Fuzzy-Search sowie Synonym-Suche und auch phonetische Suchanfrage vii sind implementierbar und harren ebenso ihrer Realisierung wie das Delisting (dem Herausnehmen aus dem Index) und dem Erweitern des fertigen Index'. Auch die Option der Anzeige des Index' wäre eine schicke Sache. Anders als die Vorlage findet SearchEngine auch eine Wortkette, wenn sie ein oder mehrere Stoppwort/-wörter enthält.
4. Diskussion von Entwurfsalternativen Anders als die Vorlage verwaltet SearchEngine die Fundstellen nicht mit parallel verwalteten Arrays für Positionen und Wortabstände (s.o.). Wortabstände werden gar nicht gespeichert. Der Sinn hat sich mir nicht erschlossen, da die Dokumente bei der Context-Ausgabe ja doch nochmal geöffnet werden und mit einem RAFReader die Wörter davor und danach gelesen werden (ansonsten, würde auf das nachfolgende/vorherige Wort verwiesen, ließe sich tatsächlich nur durch den Index alleine der Kontext wiederherstellen (dadurch würde der Index aber schätzungsweise um den Factor 3 wachsen (und das ohne ohne Stoppwörter(oder es würde mit leerer Stoppwortliste indiziert))). Es ist pro DocumentData bloß der Key (das ist die Documenten ID) und ein int-Array (für Dokumente bis zur Größe von über 2GB) viii gespeichert. 5. Bereitstellung des Materials im Internet SearchEngine wird bald im WWW zu finden sein. Selbsverständlich mit dem Quelltext. 6. Konfiguration Alle gemachten Zeitangaben usw. beziehen sich auf ein (schlecht-konfiguriertes) 400MHz PII Linux-System mit 256MB Ram. Die zum Einsatz gekommene Software ist: Jbuilder4, Jdk1.3.1, StarOffice5.2,Suse7.3, Gimp1.2.2, ArgoUML. Anhang A: Fakultative Zusatzfunktionen |
|
FileName |
DokID |
Wörter |
Fund |
stelle |
|
Rank |
ing |
(+) /2 |
*amount/WordNum/10 |
% Multiplikation des Rankings/50 |
|
|
|
insgesamt |
alte |
Frau |
|
alte |
Frau |
|
|
|
|
Die zwei Brüder.txt |
0 |
4286 |
3 |
7 |
|
2,39 |
3,01 |
2,7 |
0,0075 |
0,0074 |
|
Müllerbursch.txt |
1 |
1122 |
0 |
1 |
|
0 |
0,43 |
---- |
|
|
|
Einäuglein.txt |
2 |
1361 |
1 |
8 |
|
0,79 |
3,44 |
2,115 |
0,0190 |
0,0143 |
|
Der goldene Vogel.txt |
3 |
1583 |
0 |
0 |
|
---- |
---- |
|
|
|
|
Katze und Maus.txt |
4 |
484 |
0 |
0 |
|
---- |
---- |
|
|
|
|
Jorindeund Joringel.txt |
5 |
483 |
5 |
2 |
|
3,99 |
0,86 |
2,425 |
0,0628 |
0,0451 |
|
leere Datei.txt |
6 |
0 |
0 |
0 |
|
---- |
---- |
|
|
|
|
Hänsel und Gretel.txt |
7 |
1520 |
8 |
15 |
|
6,38 |
6,46 |
6,42 |
0,0546 |
0,0545 |
|
Dornröschen.txt |
8 |
640 |
4 |
2 |
|
3,19 |
0,86 |
2,025 |
0,0352 |
0,0287 |
|
Hase und Igel.txt |
9 |
640 |
0 |
15 |
|
0 |
6,46 |
---- |
|
|
|
Der treue Johannes.txt |
10 |
1594 |
2 |
1 |
|
1,59 |
0,43 |
1,01 |
0,0070 |
0,0057 |
|
Froschkönig.html |
11 |
754 |
0 |
0 |
|
---- |
---- |
|
|
|
|
Löweneckerchen.htm |
12 |
1229 |
0 |
0 |
|
---- |
---- |
|
|
|
|
Frau_Holle.html |
13 |
606 |
3 |
14 |
|
2,39 |
6,02 |
4,205 |
0,0851 |
0,0745 |
|
Grimms_Märchen.html |
14 |
11 |
0 |
1 |
|
0 |
0,43 |
---- |
|
|
|
leere Datei.html |
15 |
0 |
0 |
0 |
|
---- |
---- |
|
|
|
|
mann.txt |
16 |
0 |
0 |
0 |
|
---- |
---- |
|
|
|
|
Verschiedene Märchen.txt |
17 |
9069 |
28 |
29 |
|
22,3 |
12,4 |
17,35 |
0,0251 |
0,0240 |
|
Der Eisenhans.txt |
18 |
1695 |
0 |
1 |
|
0 |
0,43 |
---- |
|
|
|
Frau Holle.txt |
19 |
603 |
3 |
13 |
|
2,39 |
5,59 |
3,99 |
0,0816 |
0,0724 |
|
A.1 Ranking |
|
Aus diesem Grunde habe
ich (die ansonsten sehr taugliche) Funktion um einen Teiler wordNum
erweitert (siehe QueryEngine, ranking() ). WordNum
ist die Gesamtanzahl der
indizierten Worte in dem Dokument.Es wird durch
500 geteilt; daraus folgt, daß bei einer Gesamtzahl von 500
indizierten Worten der WordNum gleich 1 ist. Sind es
mehr Worte, wird WordNum
größer, und da es ein Teiler für die Gesamtfunktion
ist, wird das Ergebnis
der Gesamtfunktion kleiner. Dann wird noch ein Extremgrenze abgesteckt:
nämlich wird der Teiler kleiner 1, wird der Teiler auf 1 gesetzt.
Das verhindert ein ungewünschtes Verhalten bei der Bewertung von
Dokumenten, die z.B. nur eine Fundstelle aufweisen, aber auch nur
insgesamt
100 indizierte Wörter groß sind, und also der Teiler WordNum
100/500=1/5 betragen
würde, was einem Faktor von 5 entspricht. So würde das
Dokument ein gleiches
Ranking bekommen wie ein Dokument mit 5 Fundstellen bei insgesamt 500
indizierten Wörtern - dies wäre eine Verzerrung der
Wichtigkeit
von Dokumenten (meiner Meinung nach). Als letztes habe ich noch einen
kleinen Faktor eingebaut: amount ist die Anzahl der
Fundstellen
und hinzu wird 1/5tel der Anzahl von sich selbst addiert. Nach dem
Berechnen
vom Ranking wird dieses mit dem Faktor amount
multipliziert. |
i Von
einigen schrecklichen Bugs mal abgesehn. Zu einem dieser
Bugs gehört der 'SwingOberflächenBug' : Als die Suchmaschine
schon
recht fortgeschritten war sollte sie größere Verzeichnisse
indizieren.
Dazu brauchte ich eine Statusausgabe, die mir mitteilte, an welchem
Dokument
die Maschine gerade arbeitete, um überschauen zu können, ob
SearchEngine
denn nun crawlte/indizierte oder sich in einer Endlosschleife befindet.
Ausgaben jeglicher Art auf eine Swingkomponente zeichnen zu lassen,
während
das Hauptprogramm sehr beschäftigt ist, scheint aber
unmöglich zu sein ... ?! Das Fenster wurde einfach nicht neu
gezeichnet ( ist auch
selber leicht zu testen: einfach ein größeres Verzeichnis
indizieren
lassen, und während dessen ein Fenster über Search Engine
hin-und-her
ziehen ). Die Swingkomponenten in eigenen Threads laufen zu lassen,
diese
zu timen , selbst ein periodisches Anhalten des Hauptprogramms
(-Threads)
half nichts: ich bekam keine aktualisierte Ausgabe zu Gesicht. Drei
Bücher weiter probierte ich einfach eine alte awt
Componente - und es klappte.
Mir ist nicht klar , ob das an der mangelnden Threadsicherheit der
Swingkomponenten
liegt.
ii DieseFunktion
ist z.Z. wenig sinnvoll, da beim Packen die
wichtigen Dateien, wie die docmap.txt, ohne die das Wiederherstellen
des
Indexes nicht funktioniert, nicht mitgepackt werden. Der Sinn des
Packens
liegt darin, dass zukünftig mehrere Indexe von verschiedenen
Partitionen
gemacht werden können, um diese dann in einer platzsparenden
Version
zu archivieren. Der Zip-Algorithmus verkleinert die anfallende
Index-Datei
um den Faktor 3 (!).
iii Es
wäre möglich, wie in der Vorgabe, den/die
Benutzer/in bei der ersten Sucheingabe nach dem Namen der Datei zu
fragen,
in der die Ergebnisse der Suche gespeichert werden sollen.
iv Da
das Suchergebnis als HTML-Dockment in der GUI ausgegeben
ist, läßt sich mit einem Klick auf den Dokumenten-Pfad das
Original-Dokument öffnen. Schön wäre es, wenn in dem
geöffneten Dokument
die gesuchten Terme markiert wären. Eine Standard-Implementation
könnte
in einer purpurenen Markierung der Terme bestehen, wobei nicht
berücksichtigt
wäre, daß wenn das Original-Dokument einen purpurenen
Hintergrund
besäße, nichts von den Markierungen zu sehen ist.
v Dieses
ließe sich automatisieren . Dem Benutzer würde
lediglich eine Mitteilung dieser selbstätigen Veränderung
gemacht.
vi Eine solche Phrase als Eingabe in dem Suchmodus würde nicht gefunden, da "war's" zu "war" tokinisiert wird. Es wäre der RAFReader und MiniReader zu verändern.
vii "Alle
vorgestellten Programme kranken meines Erachtens
an mangelnden Suchfunktionen. So hat keines der vorgestellten Programme
die Möglichkeit der fehlertoleranten Suche oder der phonetischen
Suche.
Insbesondere , wenn die Erinnerung nur noch aus Da hatte ich doch mal
eine
Notiz diesbezüglich, über einen Herrn Zand. Oder hieß
der
Zahrnt ? Oder Zannt ?" besteht." (Mark Henning, in c't 2003 Heft 9, S.
12,
Leserbriefe,Mangelnde Suchfunktionen - Chaos, lass nach,
Windows-Programme
zum Ordnen unstrukturierter Informationen, c't 8/03, S. 134).
viii Beim
Schreiben dieser Dokumentation bemerke ich, daß
es keine 3bytigen Grundwerte in Java zu geben scheint. Bei einem 24
bittigen Wert liessen sich optimalerweise Dokumente bis zur
Größe von
mehr als 16MB indizieren.
ix Das
Beispiel ist nicht ganz stimmig, verdeutlicht aber
worauf es ankäme. Statt aber lediglich das Verhältnis der
Anzahl
der Worte in den gefundenenen Terme in einem Dokument zu bewerten , so
sollte
dies mit dem Ranking nach der Original-Formel geschehen. Das sähe
so aus: Zuerst werden alle Ranking aller Terme eines Dokuments
miteinander
addiert, bei dem Beispiel Hänsel und Gretel also: 6,38 + 6,
46=12,84.
Dies entspräche 100 %. Nun wird der Dreisatz benutzt, um zu dem
anteiligen
Prozentsatz eines Terms an den 100% zu gelangen, also: 6,38
*100/12,84=49,68%.
Beim anderen Term ist es 50,32%. Diese Ergebnisse werden jetzt
miteinander multipliziert => 2499,84. Was nahe am Maximum bei zwei
Termen liegt:
2500. Nun ist auf dieses Ergebnis mit der Wurzel zu operieren, und zwar
der Wurzel des Grades der Anzahl der Terme (bei 2 Termen die 2. Wurzel,
bei 3 Termen die 3.Wurzel ...) . Hier also die Quadratwurzel =>
49,99.
Diese Ergebnis wird durch 50 geteilt (um in Rahmen der sortierbaren
doubles
zu bleiben (wird der Wert vor dem Komma zweistellig,wird falsch in die
Map
einsortiert )) und wird mit dem Ergebnis der erweiterten Formel
multipliziert.
Dies wurde soeben noch implementiert (bei allen weiteren
Modifizierungen
sollte teleologisch argumentiert werden; Kriterien hierzu ändern
sich durch die Form der indizierten Dokumente, also durch das
Einsatzgebiet
der SearchEngine ). Dabei fiel auf, wie unübersichtlich die Dinge
programmiert
sind.Für so eine kleine Funktion hab ich 2 Stunden gebraucht . Die
Ausgabe wird natürlich etwas langsamer, ist verbesserbar.
Immerhin,
es klappt: Hänsel und Gretel sind (mit Abstand) auf den dritten
Platz
gekommen. Doch: SearchEngine ist teilweise (gerade die QueryEngine) sehr
unübersichtlich - werde mal in der Zukunft mehr Wert auf
KlassenBildung/Datenkapselung
legen, um zu einer Entzerrung zu kommen. ...
x Was natürlich auch eine unsinnige Indifferenziertheit ist. Verbesserung wäre zwar angebracht, jedoch erledigt sich das ordentliche Ranking komplexer Wortketten meist dadurch von selbst, da diese nur in einem (oder wenigen) Dokument(en) sind.