C++-Code plattformunabhängig an .NET anbinden

Eine vernünftige Textanalyse-Software baut aus Texten Netzwerke auf und berechnet viele kürzeste Wege, für die Betweenness Centrality eines Knotens gar alle kürzesten Pfade von jedem anderen Knoten zu jedem anderen Knoten. Mit C++ bin ich da in Bezug auf Speicherverbrauch und Rechengeschindigkeit auf der sicheren Seite. Es gibt z.B. die in C++ geschriebene Bibliothek SNAP (Stanford Network Analysis Platform). Selbstverständlich gibt es auch andere Netzwerke als Textnetzwerke. Es gibt auch soziale Netzwerke, biologische, chemische Netzwerke, die man alle mit dieser Bibliothek hervorragend analysieren kann.

Mit C++/CLI habe ich ja schon tolle Erfahrungen gesammelt. Zuhause hatte ich eine Anbindung von nativem C++-Code über eine gemischte DLL (managed/unmanaged) geschafft. Allerdings habe ich ums Verrecken nicht heraus bekommen, warum das auf dem Webserver beim Webhosting-Provider nicht hinhaut. Außerdem bin ich mit C++/CLI nicht plattformunabhängig. Ich habe SWIG gefunden. Um mit SWIG plattformunabhängigen Zugriffscode zu generieren, erstelle ich eine .i-Datei, die das Interface beschreibt, welches ich in einer Assembly ansteuern will. Dieses Interface muss sich aber sehr einfältig geben. Ich kann z.B. keine shared_ptr übergeben. Was soll .NET damit anfangen? Ich kann auch keine STL-Vektoren übergeben. Ich kann aber das Interface einer Klasse exponieren, die nach außen hin nur einfache Datentypen wie int oder char* austauscht. Nach innen hin kann die exponierte Klasse ja dann damit umgehen, wie sie will und die einfachen Typen wild in abgefahrene Strukturen stecken. Designtechnisch kann ich sogar ein Interface definieren, welches über Vererbung verfügt. Z.B. kann ich die Klasse Shape und die davon abgeleiteten Klassen Rectangle und Circle nach außen geben. Für meine Zwecke reicht erst einmal eine einfache Klasse. Ich habe hier eine sehr gute Anleitung gefunden, die ich unmittelbar anwenden konnte.

SWIG arbeitet mit PInvoke (Platform Invoke). PInvoke ist Bestandteil der ECMA/ISO C#-Spezifikation. Während ich reine managed DLLs direkt unter anderen Platformen verwenden kann, muss ich für die native DLL z.B. unter Linux eine .so-Datei kompilieren und dann der Anwendung mit einer Map mitteilen, welche .so welcher .dll entspricht.

Nicht nur Sozialarbeit

Ich bin gerade auf Arbeitsuche und neulich stieß ich dabei auf einen interessanten Film der Computerwoche. Wenn man die erste Hälfte des Films ansieht, könnte man meinen, es werden in der Industrie Sozialarbeiter als Softwareentwickler gesucht. Aber letzten Endes wird deutlich, dass die ganze Sozialarbeit ohne Programmierfähigkeiten nichts bringt. Softwareentwickler sollten Erfahrung mit agiler Entwicklung z.B. ala Scrum oder Kanban verfügen und ihren Code mit Unit-Tests versehen. Super, so gefällt mir Entwicklung. Letztens habe ich für ein Vorstellungsgespräch einmal mehr einen Abspieler für Go-Partien im SGF-Format geschrieben, da ich älteren Code verloren hatte. Es war eine sehr gute Gelegenheit für eine Neuprogrammierung. Denn ich hatte beim ersten Gespräch den Mund sehr voll genommen, dass ich sehr auf Sauberkeit und Qualität des Codes achte. Als ich das Progrämmchen schrieb und bei der nächsten Vorstelung präsentierte, habe ich auf saubere Objektorientierung geachtet, nämlich Codeabschnitte mit hoher Kohäsion zu einer Klasse zusammen zu fassen. Ich habe auch auf Lesbarkeit geachtet und darauf, doppelten Code zu vermeiden. Natürlich ging es dabei auch um handwerkliche C++-Fähigkeiten und modernes C++.

Weil das Ganze so viel Spaß macht, und weil ich ganz die Tests vergessen hatte, die ja besonders zur Lesbarkeit und zur Qualität, besonders in ernsthaften Projekten, beitragen, habe ich nun die Visual Studio-Solution auf drei Unterprojekte verteilt. Ein Projekt, SGFAnalyzer, berechnet mir den Brettzustand nach setzen eines Steins. Dies ist der Logikteil und wird nachträglich mit Tests versehen. Das andere Projekt ist die reine Oberfläche, für die ich keine Tests schreibe und schließlich gibt es ein Testprojekt. Das Testprojekt lege ich als ein C++-Testprojekt an. Damit ich die Vorteile des .NET-Frameworks nutzen kann, mache ich das Projekt zu einer Assembly, indem ich die .cpp-Datei mit den Unit-Tests mit dem CLR(Common Language Runtime)-Flag versehe. Nun kann ich Tests mit C++/CLI schreiben. Hier ist eine gute Anleitung.

Zusätzlich zur vorhandenen Funktionalität füge ich mir eine Funktion hinzu, die mir nach dem einfachsten Algorithmus bei jedem Zug den Zustand jedes einzelnen Punktes berechnet. Entweder der Punkt ist neutral oder mehr von weiß oder mehr von schwarz beeinflusst. So sieht das Ergebnis aus:

Und hier ist der Quellcode.

Bevor man die Anwendung mit dem Visual Studio startet, muss man noch das Visual Studio Add-in 1.1.11 for Qt4 und die Qt libraries 4.8.4 for Windows herunterladen und installieren.

Android Studienbibel

Gestern hat mich eine Spam-Email dazu ermutigt, nun auch einmal den Erfolg meiner Android Studienbibel zu erwähnen. Wie der Spamroboter so schön bemerkt, ist meine Bibel-App auf Platz 323 der kostenlosen Top-Apps in der Kategorie Bücher und Nachschlagewerke. Sie wurde 3.442 mal heruntergeladen und ist auf 2.197 Handies aktiv in Gebrauch. Immerhin! Die Kurve der aktiven Installationen auf Geräten ist über die Zeit deutlich steiler geworden. Es hat sich gelohnt, SQLite nativ in C++ anzusprechen, denn 50% der Benutzer sind immer noch bei Android <= 4.0. Die Bewertung der App liegt bei 4.54. Ich habe also doch einige Nutzer glücklich gemacht und die App deckt eine kleine aber echte Nische ab. Als Abstürze habe ich keinen einzigen zu verzeichnen, seitdem ich die Möglichkeit zu geringen Speichers beim Entpacken der Datenbank abgefangen habe. Und das, obgleich meine App über einen nativen, in C++ programmierten, Anteil verfügt und sich durch diese erhöhte Komplexität Fehler einschleichen können.

PowerVR Insider SDK

Ich habe noch eine bessere Möglichkeit gefunden, platformunabhängig Text darzustellen. Als erstes stieß ich durch Zufall auf heise.de auf NVIDIA Nsight Tegra, ein Plugin für Visual Studio, welches die native Entwicklung inclusive Debugging für Android auf komfortable Weise erlauben soll. Mir war das komplizierte Gefummel zuwieder, mit Eclipse Android NDK-Anwendungen zu debuggen. Leider funktioniert das Ganze bei mir nicht, aus der Traum. Das Installationsprogramm vermisste eine Benutzergruppe, die auf amerikanischen Windows-Systemen per default vorhanden ist. Die netten Entwickler haben also deutsche Benutzer vergessen. Nachdem ich diese Tatsache in einem Visual Studio-Protokoll herausgefunden und die Benutzergruppe meinem Rechner hinzugefügt hatte, wollte Visual Studio nicht starten. Das Plugin hatte etwas vermurkst. Die Support-Abteilung von NVIDIA war auch ganz freundlich, aber ein Update habe ich bis heute noch nicht gesehen. Dann muss ich also doch unter Windows mit einem Opengl ES-Emulator entwickeln und nach Android portieren.

Kürzlich habe ich aber einmal mehr gesucht und das PowerVR Insider SDK gefunden. Für die Platformen Windows, Linux, Android und iOS und noch mehr entwickelte Imagination Technologies mit diesem SDK ein Framework, welches die platformspezifischen Details heraus abstrahiert und ein grundlegendes API zur platformunabhängigen Grafikprogrammierung unter Opengl ES bereitstellt . Die Beispiele funktionieren tadellos und alles lässt sich anstandslos kompilieren (in meinem Fall mit dem Visual Studio für Windows und dem aktuellsten NDK für Android). Um Text zu dimensionieren, kann man die API fragen, welche Breite und welche Höhe ein Text einer bestimmten Größe im Bildschirmkoordinatensystem hat. Das ist genau, was ich gesucht habe, zumal dieses Framework auch Opengl ES 2.0 unterstützt. Aber insgesamt erkenne ich nun, dass diese grundlegenden Funktionen doch noch ein bisschen wenig ist, so dass man sehr viel selbst programmieren muss. Ich müsste z.B. erst einmal einen Textviewer programmieren. An Interaktionen ist auch nicht gedacht, sondern nur die grafische Darstellung. Vielleicht doch ein Spiele-Framework …

Text mit dem Android NDK malen

Mein nächstes Vorhaben: Der Betweenness-Algorithmus soll mir für jedes Buch der Bibel die Zentralität der Nomen berechnen und dann will ich die Worte in diesem Bibeltext grafisch entsprechend der Zentralität hervorheben. Dies fällt in den Bereich Computergrafik. Da ich mich, wo möglich, aus der reinen Android-Welt befreien möchte und Computergrafik nun wirklich einen Sache ist, die gerne mit C++ angegangen wird, realisiere ich meine Idee mit dem Android NDK. Ich suche nach einem unmittelbar lauffähigen Beispiel. Nachdem ich die einfachsten Beispiele ausprobiert habe und keines davon läuft, frage ich mich, ob ich nicht eine Game-Engine verwenden möchte. Aber ich bin kein Freund von unnötiger Komplexität und möchte so schnell wie möglich zum Ziel kommen. Um ein bisschen Text ansprechend rendern zu können, brauche ich keine Game-Engine. Mit nur ein wenig Suchaufwand, findet sich eine Library, inklusive Beispielprojekt von Julien Rebetez. Man muss auch ein wenig Arbeit investieren, indem man eine freetype-Library hinzu linkt. Hier gibt es ebenfalls vom gleichen Autor auch eine fertige Bibliothek für Android. Man muss die Android.mk des Beispielprojekts ändern, dass sowohl die Bibliothek zum Darstellen von Text als auch die freetype-Bibliothek gefunden wird. Beide Bibliotheken müssen vorher nur noch mit ndk-build gebaut werden.

Android Nativ (C++)

Endlich habe ich einen Grund gefunden, nativ (in C++) unter Android zu programmieren. Eine Datenbankabfrage meiner Studienbibel dauert zu lange. Eine 800.000-zeilige Tabelle wird mit einer 400.000-zeiligen Tabelle per JOIN verknüpft und auf dieser Vereinigung findet eine Abfrage statt. Nicht dass diese Abfrage nicht in Java in akzeptabler Geschwindigkeit zu erledigen wäre. Unter Android 4.1 dauert dieses SELECT ca. 2 Sekunden. Aber Android 4.0 braucht 10 Sekunden. Hier wird eine andere SQLite-Implementierung verwendet, die dieses Statement nicht optimiert ausführt. In C++ programmiert, kann ich mir die SQLite-Implementierung aber selbst kompilieren und die Abfrage dauert, unabhängig von der Version des Android-Betriebssystems, wieder 2 Sekunden.

Eclipse und C++ – Nein danke

Bei der Gelegenheit versuche ich, den C++-Code mit dem Debugger untersuchen zu können. Die Tutorials sind nach mehreren Jahren der Existenz des Android NDK diesbezüglich immer noch rar gesät. Ich finde tatsächlich nur eine Anleitung. Mit Eclipse habe ich aber so meine Schwierigkeiten. Nachdem ich die STL-Portierung eingebunden habe, kann ich wegen hartnäckigen Semantikfehlern das native Projekt nicht mehr starten. Ich kann den Code kompilieren, aber Eclipse meint, es liege ein Fehler vor. Empfohlene Lösung: Ich schalte die Syntaxprüfung ab. Dann kann ich auch das Hauptprojekt wieder starten. Super: Eine IDE ohne Syntaxüberprüfung. Eclipse wird langsam nicht mehr wichtig. Die Prozedur, die Anwendung mit Haltepunkten ausführen zu können, ist recht kompliziert. Zuerst starte ich die Anwendung, bis die native Bibliothek geladen wird. Kurz danach halte ich wieder an. Danach starte ich den Debugger per ndk-gdb in der Console. Jetzt kann ich in Eclipse den nativen Debugger starten, der sich per TCP mit dem GDB verbindet. Das Springen von einem Haltepunkt zum nächsten ist sehr zäh und es dauert einige Sekunden pro Haltepunkt. Nachdem der Debugger plötzlich keine Symbole mehr finden kann, gebe ich genervt auf. Ich habe Urlaub! Ich werde die Entwicklung bezüglich Eclipse und natives Debugging unter Android bestimmt weiter verfolgen, aber viel scheint sich nicht zu tun. Schließlich ist das NDK immer noch nur als Notfalllösung geplant, wenn man bereits funktionierenden Code nach Android portieren will.

Visual Studio und C++

Eine bessere Stragie ist es, das Interface meiner Datanbankabfrage zu definieren und die Implementierung dann mit dem Visual Studio vorzunehmen. Ich erstelle eine Consolenanwendung und kann komfortabel entwickeln, ohne frickeln zu müssen. Wenn alles zufriedenstellend läuft, passe ich den Code mit minimalen Änderungen für den Compiler des Android-NDK an.

Eine Datenbankanwendung für Android

ASP.NET MVC – C++/CLI

Nachdem ich so eine tolle Möglichkeit gefunden habe, mit Boost in C++ so schön und schnell die Betweenness Centrality der Nomen eines Textes zu berechnen, könnte ich doch meine Studienbibel, die ich in ASP.NET MVC verfasst habe, mit dieser Möglichkeit erweitern. Bei meinem letzten Job hatte ich schon einmal die Verknüpfung von C# und C++ über C++/CLI gemeistert und ich will an diesen Erfolg anknüpfen. Auf meinem Rechner klappt das super. Ich erstelle eine gemischte managed/unmanaged DLL, welche über ein managed Interface für die C#-Assembly verfügt und darüber nativen Code ausführen kann. Aber als ich meine Anwendung auf den Server veröffentlichen will, wird beim Start der Ausführung aber eben jene gemischte DLL vermisst. Je mehr ich im Internet zu dem Problem recherchiere, desto mehr Lösungsmöglichkeiten eröffnen sich. Darunter finden sich doch recht viele Meinungen, wie höllisch kompliziert die Mischung C++ und c# mittels C++/CLI doch sei. Nachdem ich einen Sonntag geopfert habe und sich noch mehr Möglichkeiten und Unklarheiten auftun, halte ich inne. Das ist eben doch ein Nachteil an Microsoft. Wäre so ein Server frei, könnte ich ihn mir einfach herunterladen und in einer virtuellen Maschine laufen lassen und den ausgeführten Code auf dem Server per Debugger untersuchen. Die vielen einfachen Lösungsmöglichkeiten hatte ich natürlich ausprobiert, aber um hier weiter zu kommen, brauche ich mehr als spielerisches Interesse an technischen Herausforderungen. Ich bin eben doch kein Technokrat. Was will ich eigentlich? Muss ich unbedingt der Welt zeigen, dass ich ein C++/CLI-Spezialist sein könnte? Sinnlos! Ich will eine Studienbibel programmieren und cool wäre das doch eigentlich für Android! Dann wäre ich nicht vom Internet abhängig und könnte auch im Wald oder in sonstiger Natur die Bibel auf besondere und komfortable Weise lesen. Eben überall, wo ich gerade bin.

Android NDK

Ich probiere ein Hello-World-NDK-Beispiel unter Android aus. Um SQLite nativ zum Laufen zu bringen, muss ich die riesige SQLite C-Datei meinem Makefile hinzufügen und neu kompilieren. Dazu gibt es Beispiele und nach einigem Gefummel habe ich SQLite und die Boost/Graph-Bibliothek nativ unter Android am Laufen. Der Nachteil von nativen Programmen unter Android ist aber die Geschwindigkeit. Während es an meinem Desktop-Rechner eine halbe Sekunde dauert jedes Wort eines Bibelbuches inklusive seiner 10 Parameter aus der Datenbank einzulesen und aus den Worten der Nominalphrasen einen Graphen aufzubauen und die Betweenness-Centrality sowohl der Kanten als auch der Knoten des Graphes auszurechnen, sind es auf meinem schon betagten Nexus S ca. 30 Sekunden. Diese Geschwindigkeit ist nicht akzeptabel. Auch hier wieder: Was soll der Spieltrieb? Tatsächlich sagt auch die Android Developer-Doku, dass man schon einen triftigen Grund haben sollte, um nativ unter Android zu programmieren, da es mit Sicherheit die Komplexität der Entwicklung erhöht, nicht aber unbedingt die Geschwindigkeit der Ausführung. Muss es also unbedingt dynamisch sein? Es reicht doch, wenn ich vorher die Betweennes Centrality für Kanten und Knoten ausrechne und die Ergebnisse in der Datenbank festhalte. Bei der Bibel handelt es sich schließlich um statischen Inhalt und es ist nicht damit zu rechnen, dass noch mehr Inhalt hinzugefügt wird. Was seit tausenden von Jahren penibelst vor Veränderungen geschützt wurde, wird wahrscheinlich nicht plötzlich geändert werden. Wenn ich vorher die Zentralitäten ausrechne, kann ich dies sogar für die gesamte Bibel tun. Ich kann alle ca. 800.000 Worte der Bibel einlesen und einen Graphen über die gesamte Bibel aufbauen und errechne mir damit Worte, die bibelweit zentral sind. Wenn ich diese Zentralitäten für meine Studienbibel benutze, helfe ich dem Benutzer die Bibel im Überblick zu behalten.

Normalisiert vs. Unormalisiert

Soll ich nun meine Datenbank normalisieren oder unnormalisiert lassen. An jeder Informatik-Uni lernt man, dass man Daten in der Datenbank natürlich normalisieren sollte. Aber das gilt nur für Daten, die sich vermehren. Eine unnormalisierte Datenbank verschlingt zuviel Platz, da Einträge vielfach vorkommen können und die Datenbank schlecht gewartet werden kann. Andererseits spart man sich auch komplizierte Join-Anfragen. Wenn ich wirklich meine Datenbank normalisieren würde, würde ich aus der zentralen Tabelle 5 Tabellen machen und müsste damit über ein 5-fach verschlachteltes Join-Statement 5 Tabellen abfragen. Ich habe gehört, dass sich das nicht sonderlich positiv auf die Performance auswirkt. Allerdings werde ich das noch ausprobieren. Denn der Benutzer lädt sich auch nicht gerne 100 MB herunter. Allerdings muss ich meine SQLite-DB indexieren. Wenn ich einen Index über häufig gefragte Spalten erstelle, antwortet SQLite in akzeptabler Geschwindigkeit auf eine Abfrage. Andernfalls dauert es einige Sekunden, bei jeder Anfrage erneut 800.000 Datensätze durchsuchen zu müssen.

Centering Resonance Analysis

Während ich noch auf das Modell für den stanfordschen Dependenzparser warte, welcher mir ermöglichen soll, aus einem Satz möglichst genau und schnell Subjekt, Prädikat und Objekt herausfinden zu können, suche ich anderweiting schon einmal nach anderen Möglichkeiten, Text zu analysieren und zu visualisieren. Ich stoße auf die Bachelorarbeit von Julia Blumental, die sie dem Thema Netzwerk-Textanalyse widmete. Dort geht es hauptsächlich um die Centering Resonance Analyse. Im Rahmen dieser Analyse wird ein Text in eine Kette von Nominalphrasen zerlegt. Die Worte der Nominalphrasen werden mit einander verbunden. Zusätzlich wird das letzte Wort einer Nominalphrase mit dem ersten Wort der nächsten Nominalphrase verbunden. Auf diese Weise entsteht ein ungerichteter Multigraph. Damit nicht zu viele unwichtige Knoten entstehen, werden die Worte, die die Knoten des Graphen bilden, auf das Lemma oder die Grundform abgebildet. Auch das POS (Part of Speech)-Tag eines Wortes wird verwendet, um die Knotenmenge zu reduzieren, indem nur Nomen, Namen und Adjektive verwendet werden. Nun gibt es verschiedene Maße der Zentralität eines Knoten, darunter die sogenannte Betweenness Centrality, die als besonders wichtig erachtet wird. Bei Gelegenheit schaue ich auch nach, was das auf Deutsch bedeutet. Nach diesem Maß ist ein Knoten umso zentraler, je mehr kürzeste Wege von jedem anderen Knoten zu jedem anderen Knoten im Graphen verlaufen.
Ich finde sofort eine passende Boost-Implementierung des Algorithmus, nämlich in der Bibliothek graph. C++ könte hier Speicher- und Geschwindigkeitsvorteile bieten, besonders wenn massenweise Text analysiert und eine gute Übersicht darüber geboten werden soll.
Als ersten muss ich aber meinen Text mit dem Stanford Parser parsen und meine Bibelwörter in der Datenbank als zu einer Nominalphrase zugehörig kennzeichen. Der Stanford Parser beinhaltet eine Vielzahl von Parsern darunter zwei für die deutsche Sprache, nämlich den GermanPCFG.ser.gz und den GermanFactored.ser.gz. Der GermanFactored Parser soll genauer sein, nur braucht er zu viel Speicher und ist darüber hinaus zu langsam. Um meinem kleinen Progrämmchen, welches die Worte der Datenbank mit ihrer Zugehörigkeit zu einer Nominalphrase kennzeichnet, den nötigen Speicher zu gönnen, brauche ich leider ersteinmal einen 64 Bit breiten Datenbanktreiber. Microsoft stellt seinen JDBC Treiber nur als 32 Bit zur Verfügung, der nicht gut mit 64 Bit Java zusammenarbeitet, welches ich brauche, damit ich mehr als zwei GB Arbeitsspeicher adressiert bekomme. Von Microsoft sollte man ja wissen, dass Interoperabilität nur halbherzig geschieht, ein bisschen enttäuscht bin ich aber schon. Schließlich ist es immer eine große Fummelei, den Connectionstring aus einer bestimmten Sprache heraus zu einer bestimmten Datenbank herauszufinden und die Verbindung zu konfigurieren. Der freie JTDS Treiber zur Verbindung von Java mit MSSQL gibt dann aber die gewünschte 64-Bittigkeit und damit hat meine Anwendung genug Speicher. Jedenfalls bin ich nicht bereit, mehr als 4 GB Speicher zu investieren, ich bin Spartaner. Mit dem GermanFactored komme ich aber trotzdem nicht weiter. Irgendwann wird auch dem mit -mx2000m gestarteten Programm der Arbeitsspeicher knapp. Das Parsen von biblischen Sätzen des Paulus aus dem neuen Testament bringt ihn sehr in Verlegenheit und er brütet viele Minuten lang über einen Satz mit 80 Wörtern, bis Java sich schließlich meldet, es sei kein Speicher mehr verfügbar. Genauer wird das Ergebnis mit dem GermanFactored im Vergleich zum GermanPCFG auch nicht. Letzterer tut jedoch seinen Dienst und analysiert mir die Nominalphrasen von 10 Bibelbüchern in einer halben Stunde.
Mit dem Visualisierungsprogramm Gephi kann ich nun meinen Graphen ansehen, welchen mir Boost als .dot Datei schreiben kann. Die Zentralitäten, welche mir Gephi berechnet stimmen mit Boost überein, das ist beruhigend. Das Ergebnis freut mich, denn es bringt wirklich Übersicht in einen Text:

Lückentext

XML vs. SQL

Mein Ziel ist es, einen Lückentext aus einem Text zu generieren. Zufällig sollen Worte aus dem Text herausgepickt werden. Ist das auserwählte Wort ein Nomen, soll eine Auswahl der im Text vorkommenden Nomen angeboten werden. Die für die Lücke angebotene Auswahl an Wörtern sollen die selbe Wortart wie das fehlende Wort haben. Welche Datengrundlage soll ich mir für mein Vorhaben zusammen zimmern? Mir fällt sofort XML ein. Allerdings denke ich sofort danach an Effizienz. Soll meine Lösung auch auf einem Smartphone laufen, muss meine interne Datenstruktur sofort aus den Daten aufgebaut worden sein. Beim Parsen von XML gibt es zwei Möglichkeiten, nämlich DOM oder SAX. Wenn ich XML als DOM einlese, werden die XML Daten vollständig in einer internen Objektstuktur abgebildet. Wenn ich aber meine 40 MB großes XML Datei dermassen vollständig einlese, dauert das mindestens ein paar Sekunden. Per SAX wird XML eingelesen, indem ich benachrichtigt werde, wenn der Parser ein neues Element erreicht hat und wenn das alte Element verlassen wird. Ich habe dann die Möglichkeit, mir meine Datenstruktur selbst aufzubauen. Die Datenstruktur kann dann genau passend erzeugt werden, während ich unter Verwendung von DOM noch einmal hingehen und mir meine benötigten Daten aus dem DOM holen muss. Aber auch hier muss ich Aufwand betreiben, um die vorhanden Daten in eine interne Struktur zu bringen. Richtig flexibel ist man aber mit einer Datenbank. Die Daten können in der Datenbank bleiben und ich kann bequem per SQL darauf zugreifen. Ich entscheide mich für SQLite. Meine Datenbank muss in Java erzeugt werden, da der Stanfort POS Tagger, welcher auch deutsche Texte beherrscht, nur für Java existiert. Ich importiere die Bibliothek SQLJet und es klappt wunderbar. Um die Geschwindigkeit des Datenzugriffs zu erhöhen, erzeuge ich mir einen Index über die am häufigsten nachgefragten Spalten in meiner Tabelle.

Suche nach dem Layout für Qt

Nun, da ich meine Datenbank erzeugt habe geht es wieder in C++ und Qt weiter. Qt bietet selbst eine Zugriffsmöglichkeit für SQLite. Alles läuft problemlos, bis ich meinen Lückentext dann auch auf der Oberfläche sehen will. Die Lücken will ich als QComboBox und den übrigen Text als QLabel Objekte darstellen. Qt bitete mir aber nur eine spärliche Auswahl an Layouts an, nämlich gerade einmal drei. Ich will die Sätze meines Lückentextes in einem QVBoxLayout und die Wörter der Sätze in einem QHBoxLayout unterbringen. Aber diese Layouts scheinen für meine Zwecke nicht gemacht worden zu sein. Mein Vorhaben sprengt die Möglichkeiten dieser Layouts, so dass ich ganz komische Effekte beim generieren meiner Lückentexte erlebe. Qt geht hin und berechnet für jedes Label allerhand und will das Zeug ja irgendwie in Boxen darstellen. Ich bin ratlos und schicke ein Gebet zum Himmel: Oh Gott, wie soll es weitergehen? Lasse mich nicht im Stich! Qt hat ein Interface QLayout vorgesehen, um selbst Layouts implementieren zu können. Doch es scheint mir zu aufwendig für meine einfache Anwendung. Ich entdecke das FlowLayout, ein von Qt als Beispiel gedachtes Layout, welches genau das tut, was ich brauche, nämlich einfach nur Controls hintereinander zu zeichnen. Warum muss man so etwas Einfaches selbst programmieren? In Qt scheint man einiges selbst machen zu müssen. Ich wollte eigentlich den Test so programmieren, dass ich beim Ende per Email benachrichtigt werde. Aber eine Email zu schreiben ist auch kein Einzeiler. Nun gebe ich wie vorher meine Sätze in ein QHBoxLayout, aber die einzelnen Wörter in das FlowLayout. Wenn ein neuer Lückentext erzeugt wird, muss ich zuerst die alten QWidgets aus dem obersten Layout löschen. Da in Qt jedes Widget seine beinhalteten Elemente selbst löscht, brauche in nur die erste Ebene meiner Widgets in dem obersten Layout zu löschen:

    QLayout* hLayout = clozeWidget->layout();

    // Delete all the existing buttons in the layout
    QLayoutItem *wItem = hLayout->takeAt(0);
    while (wItem != 0){
        delete wItem->widget();
        delete wItem;
        wItem = hLayout->takeAt(0);
    }

clozefoto

Groß, größer, am größten

Mein erstes Vorhaben ist abgearbeitet. Ich habe die Wörter der Bibel aufbereitet, indem ich alle Stopwörter herausgefiltert und jedes Wort auf seinen Worstamm mit dem Porter-Stemmer-Algorithmus zurückgeführt habe. Das Ergebnis beeindruckt mich selbst. Die Stopwortliste ist gut. Das häufigste Wort ist nicht und, oder, dass, mein, dein, …, sondern Gott. Folgendes Bild zeigt meine kleine Anwendung:

Bildschirmfoto am 2012-03-24 13:23:49

Das Bild zeigt, dass der Porter-Stemmer ganz gut funktioniert, oder? groß, große, großem, großen, großer, großes, größe, größer, größere, größeren werden auf den Wortstamm gross zurück geführt. Der Algorithmus kennt allerdings nicht den gemeinsamen Stamm von sprach und spricht. Meine Bibelübersetzung ist wohl zu alt, den er kennt auch nicht den gemeinsamen Stamm von herr und herrn. Aber wie soll man einem Programm eine willkürliche Grammatik und Zeitformen beibringen? Es werden immer Fehler bleiben. Die Häufigkeiten der Wortstämme sind auf dem Bild spiralenförmig angeordnet. Gott ist das häufigste Wort in der Bibel, wen wunderts. Nun ja, das mag aber dennoch Menschen in Erstaunen setzen, die meinen, es gehe in der Bibel hauptsächlich um die Liebe oder Verhaltensregeln unter den Menschen. Interessant auch, dass Gott so viel spricht. Nun ja, mindestens wird jetzt deutlich, dass ich heimlicher Bibelfan bin.

An nackte Pointer in den folgenen Zeilen muss ich mich in Qt noch gewöhnen. In Qt übernimmt das Container-Element die Verantwortung für das Löschen der innewohnenden Elemente. Smartpointer brauche ich also nur für meine Datenstrukturen.

QStandardItem *child2 = new QStandardItem( QString::fromUtf8(verse.c_str()));
item->setChild(i,1,child2 );

Ich habe noch einmal gelernt, wie wichtig es in C++ ist, Variablen grundsäzlich zu initialisieren. Es müssen nicht nur alle Klassenelemente in der Initialisierungsliste initialisiert werden, sondern auch lokale Variablen in Methoden. Im Debugmodus hatte ich in Qt den Fall, dass eine uninitialisierte Variable automatisch mit 0 initialisiert wurde, so dass ich davon ausging, das der Code läuft. Im Releasemodus wurde diese Nettigkeit jedoch wegoptimiert, so dass ich den Fehler suchen musste.

Wer sich mit eigenen Augen überzeugen möchte, hier ist das CtCreator-Projekt.