Ick bin ein Berliner

Nachdem ich ausgiebig nach den passenden Bibliotheken geforscht habe und in IKVM eine so elegante Möglichkeit gefunden habe, zwischen Java und .NET hin- und her zu schalten, schreibe ich nun in Java eine Bibliothek, die auf den gefundenen Bibliotheken aufsetzt. Diese Bibliothek ist plattformunabhängig, da ich sie ja dank IKVM auch unter .NET laufen lassen kann. Sie läuft nicht unter mobilen Platformen. Aber wer will schon ein Programm laufen lassen, dass sich 500 MB Speicher genehmigt und dessen Ausführung auf dem Desktop-Rechner schon 20 Sekunden braucht. Diese Software ist also für den Server oder für den Heimrechner gedacht und daher platformunabhängig. Meine Text-Analysesoftware soll einen Text einlesen und die Betweenness-Zentralitäten der Lemmas berechnen und geordnet ausgeben.

Dieses Mal entwickele ich wirklich testgetrieben. Zuerst den Test schreiben, hinterher die Implementierung, die den Test zufrieden stellt. Ich frage mich, ob ich denn jetzt die objektorientierte Entwicklung an den Nagel hängen muss. Denn wenn ich alles teste, muss ja auch so ziemlich jede Methode öffentlich zu gänglich sein. Nun. TDD (Test Driven Development) soll ja auch das Design verbessern. Das Designprinzip SRP (Single Responsibility Principle) besagt, dass eine Klasse genau eine Verantwortlichkeit und damit genau einen Grund haben sollte, um geändert zu werden. Dann mache ich eben meine Klassen noch kleiner. Die öffentlichen Methoden der Klasse kann ich dann testen und die Klasse als privates Element einer größeren Klasse halten, die dann die getestete Klasse benutzt und kapselt. Zuerst komme ich mir komisch vor, die einfachsten Dinge Schritt für Schritt zu entwickeln. Aber wie denn sonst??? Ich merke wirklich, wie TDD ein gutes Design erzwingt und auch unnötige Komplexität aus der Entwicklung heraus nimmt. Wenn man nur kleine Progrämmchen entwickelt, der kann auch mehrere Sachen gleichzeitig im Kopf behalten. Aber Entwicklung wird schließlich nicht einfacher sondern komplexer und irgendwann ist man ohne Testgerüst nur noch am Fehler suchen.

Hier ist nun die Ausgabe meines Programms, nachdem es die berühmte Rede des Präsidenten Kennedy in Berlin eingelesen hat:

stadt 3544.766394063454
berlin 3081.8249711073245
mauer 2383.5455635867397
jahr 2238.033266080326
freiheit 2171.630400645105
welt 2008.0978779390532
west-berlin 1483.8159451659449
europa 1481.9158730158722
frei 1232.8766987914046
frieden 1210.2662804515742
leute 1004.6358585858586
anderer 988.876190476191
tag 960.3910550234081
berliner 851.7508024655083
hoffnung 802.5428571428571
kommunismus 783.5699134199136
mensch 693.1821668527555
krise 626.8333333333334
familie 583.6430752254281
gesellschaft 571.7579365079367
leben 559.0666666666665
teil 544.5222943722944
geschichte 541.5802308802307
name 532.1333333333332
deutschland 493.7532467532466
mann 474.77068307509484
fortschritt 459.78881673881676
amerikanisch 454.3579365079365
bürgermeister 435.9112554112553
bürger 426.8104336839631
morgen 407.83809523809515
schwer 402.8333333333333
hervorragend 401.9614718614719
demokratie 398.68896103896094
kilometer 395.8666666666667
entschlossenheit 389.5857142857143
deutsche 363.2126984126984

Man merkt, dass der Präsident auf seine Zuhörer eingeht, denn die Stadt Berlin steht in seiner Rede im Mittelpunkt.

Die Ausführung dauert unter Java 20 s. Unter .NET dauert es saftige 25% länger. Ich überlege mir noch einmal, ob ich die Bibliothek wirklich unter .NET verwenden will. Andererseits: Wer parst schon ständig Texte?

Hier die Bibliothek und ein Beispielprojekt. Die Modelle für den LinguisticParser befinden sich im Zip-Archiv im Ordner models. Das Eclipse-Projekt ist portabel und die referenzierten .jar-Dateien relativ eingebunden und im API-Projekt dabei.

Aus JUNG mach .NUNG

Warum eigentlich immer der Performance wegen C++ mit herumschleppen? Wenn es nicht sein muss, erhöht es nur die Komplexität. Also erstmal die netzwerktechnischen Analysen in C# versuchen. Wenn die Geschwindigkeit nicht ausreicht, kann ich immer noch C++-Code hinzu addieren. Ich habe eine Java-API namens JUNG (Java Universal Network/Graph Framework) gefunden. Sie berechnet die Betweenness Centrality schon anhand des effizienteren Algorithmus von Brandes. Und auf Effizienz kommt es schließlich an, und nicht auf die Sprache. Wenn C++ schon nicht unbedingt sondern lediglich naturgemäß schneller ist als Java oder .NET-Sprachen, so ist es erst recht nicht schneller, wenn die Implementierung des Algorithmus nicht effizienter ist. Nachdem ich mir in der JUNG-Doku durchgelesen habe, welche Unterprojekte ich in Eclipse einbinden müsste, um die JUNG-API zu nutzen, kompiliere ich alle entsprechenden .jar-Dateien zu einer .NET-DLL:

C:\Users\bileser\Downloads\ikvmbin-7.2.4630.5\ikvm-7.2.4630.5\bin>ikvmc.exe -tar
get:library jung-api-2.0.1.jar jung-algorithms-2.0.1.jar colt-1.2.0.jar collect
ions-generic-4.01.jar jung-graph-impl-2.0.1.jar jung-io-2.0.1.jar jung-samples-2
.0.1.jar jung-visualization-2.0.1.jar concurrent-1.3.4.jar
IKVM.NET Compiler version 7.2.4630.5
Copyright (C) 2002-2012 Jeroen Frijters

http://www.ikvm.net/

note IKVMC0002: Output file is "jung-api-2.0.1.dll"

Welch schlanker Befehl! Ein Glück achtet IKVM nicht auf die Reihenfolge der voneinander abhängigen .jar-Archive bzw. erledigt das für mich. Als Resultat erhalte ich eine DLL namens jung-api-2.0.1.dll. Aus JUNG hätte ich hiermit NUNG (.NET Universal Network/Graph Framework) gemacht.

Den Code aus dem JUNG-Tutorial muss ich mir etwas in C# umschreiben:

// Graph<V, E> where V is the type of the vertices 
// and E is the type of the edges
Graph g = new SparseMultigraph();
// Add some vertices. From above we defined these to be type Integer.
g.addVertex(1);
g.addVertex(2);
g.addVertex(3); 
// Add some edges. From above we defined these to be of type String
// Note that the default is for undirected edges.
g.addEdge("Edge-A", 1, 2); // Note that Java 1.5 auto-boxes primitives
g.addEdge("Edge-B", 2, 3); 
// Let's see what we have. Note the nice output from the
// SparseMultigraph<V,E> toString() method

System.Console.Write("The graph g = " + g.ToString());


// Note that we can use the same nodes and edges in two different graphs.
Graph g2 = new SparseMultigraph();
g2.addVertex(1);
g2.addVertex(2);
g2.addVertex(3); 
g2.addEdge("Edge-A", 1,3);
g2.addEdge("Edge-B", 2,3, EdgeType.DIRECTED);
g2.addEdge("Edge-C", 3, 2, EdgeType.DIRECTED);
g2.addEdge("Edge-P", 2,3); // A parallel edge
System.Console.Write("The graph g2 = " + g2.ToString());

Funktioniert!

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.

Stanford Parser unter .NET

Warum eigentlich .NET? Wie oft habe ich mich gefragt, in welche Richtung ich gehen will. Ich habe mich einmal mehr für .NET entschieden. Von meiner beruflichen Erfahrung her, habe ich die meiste Erfahrung im Desktop-, also im Windows-Bereich gesammelt. Nun will ich eine Textanalyse-Software entwickeln. Diese Software sehe ich auch eher im Desktopbereich. Hier braucht man volle Rechenpower. Es geht um private Texte, die man vielleicht nicht einem fremden Server anvertrauen möchte. Oder man möchte sein eigenes Blog analysieren und will der Cloud keine Zugangsdaten anvertrauen. Im Desktopbereich ist Microsoft bisher bei weitem immer noch unschlagbar. Doch unter .NET bin ich dank Mono gar nicht von Microsoft abhängig. C# ist eine standardisierte Sprache, genauso wie die Common Language Runtime (CLR) ECMA-Standard ist. Gut, mit Mono habe ich nicht den allerneuesten heißesten Scheiß, aber allemal eine stabile, plattformunabhängige Laufzeitumgebung. Mono bietet z.B. kein WPF oder WCF, weil dies keine offenen Standards sind. Will man aber eine Oberfläche entwickeln, macht man das sowieso am besten nativ. Meine Textanalyse wird also plattformunabhängig laufen und ob ich jetzt die darauf laufende Oberfläche nativ in WPF entwickele oder direkt plattformunabhängig in Gtk# oder Winforms, kann ich mir immer noch überlegen. Wahrscheinlich WPF. Für mich ist nur wichtig, dass ich strategisch und langfristig plattformunabhängig sein kann.

Weitestgehend unterstützt Mono auch ASP.NET MVC, allerdings nur Version 2, da die Oberflächensprache Razor, welche HTML- und Javascript-Code sehr komfortabel anhand des Viewmodels erzeugen lässt und der Version 3 angehört, ebenfalls reine Microsoft-Technologie ist. Ich habe also auch hier die Möglichkeit meine Analysesoftware als Service plattformunabhängig ins Web zu bringen und entweder unter Windows oder Linux laufen zu lassen. Ich fühle mich mit der Entscheidung für .NET also in keiner Hinsicht eingeengt und habe viel mehr die Nähe zum Desktop als unter Java. Java Swing ist für den Desktop ja nicht sonderlich beliebt und bis heute fühlen sich Java-Anwendungen auf dem Desktop zäh an.

Bisher war für mich die Natural Language Processing-Ecke eine Java-Domäne gewesen. Viel Code ist in diesem Bereich in Java geschrieben worden, z.B. der Stanford Parser. Als ich nach dessen Portierung für .NET suchte, habe ich IKVM gefunden. Mit IKVM kann man ein Java-Archiv in eine .NET-Assembly verwandeln, die dann auf verschiedenen Betriebssystemen lauffähig ist.

Das geht sehr einfach:

ikvmc.exe stanford-parser.jar

Die resultierende DLL ist damit allerdings nur ungetypt. Unter C# muss man sich also mittels des Schlüsselwortes var bedienen, um Objekte von der DLL im Empfang zu nehmen.

Will man eine getypte DLL, muss man zusätzlich noch dieses ausführen:

ildasm.exe /all /out=stanford-parser.il stanford-parser.dll
ilasm.exe /dll /key=myKey.snk stanford-parser.il

Ein Beispielprogrämmchen in C# sieht so aus:


LexicalizedParser lp = LexicalizedParser.loadModel(@"C:\Users\bileser\Documents\Visual Studio 2010\Projects\ConsoleApplication1\ConsoleApplication1\germanFactored.ser.gz");
string[] sent = { "Das", "ist", "ein", "einfacher", "Satz", "." };
List rawWords = Sentence.toCoreLabelList(sent);
Tree parse = lp.apply(rawWords);
parse.pennPrint();

String sent2 = "Als ich das Progrämmchen schrieb und bei der nächsten Vorstellung präsentierte, habe ich auf saubere Objektorientierung geachtet, nämlich Codeabschnitte mit hoher Kohäsion zu einer Klasse zusammen zu fassen.";

TreebankLanguagePack tlp = new PennTreebankLanguagePack();

Tokenizer toke = tlp.getTokenizerFactory().getTokenizer(new StringReader(sent2));

List rawWords2 = toke.tokenize();
Tree parse2 = lp.apply(rawWords2);

parse2.pennPrint();

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.

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:

Reich mit Amazon Product Advertising API?

Unter der Amazon Product Advertising API wird dem Programmierer eine Untermenge der Funktionalität des Amazon-Shops zur Verfügung gestellt. Amazon-Produktdaten können nahtlos in die eigene Website integriert werden. Produkte können z.B. in einen Warenkorb gelegt werden und der Besucher kann sich umfangreich über die Produkte informieren. Die Bestellabwicklung übernimmt Amazon. Amazon stellt keine Reviews mehr zu Verfügung. Die müssen auf der Eigentümersite gelesen werden. Auf diese Veränderung weist Amazon deutlich hin. Als weitere Änderung muss auf eine bestimmte Anzahl an Anfragen über die API auch ein gewisser Prozentsatz an Bestellungen erfolgen. Ich sehe die Veränderungen als Amazons Reaktion darauf, dass die Produktdaten in der Vergangenheit fleißig von Suchmaschinenoptimierern (SEO) missbraucht wurden, um Inhalte für Websites zu generieren. Wofür braucht das Internet millionenfach reproduzierten Content, der doch ursprünglich einzig und alleine Amazon gehört? So weisen die License Agreements zur Benutzung dieser API denn auch darauf hin, dass die API nur dazu benutzt werden soll, um Besucher zu Amazon umzuleiten.

Hier studiere ich ein C#-Codebeispiel, welches ich in der Amazonschen Dokumentation gefunden habe. Es handelt von einer REST-Anfrage an den Amazon-Server. Die Antwort kommt als XML zurück. Um den Eigentümer der Anfrage eindeutig zu identifizieren bietet Amazon verschiedene Verfahren an. Das Beispiel setzt ein Verfahren um, bei dem Amazon und der Programmierer den gleichen geheimen Schlüssen haben. Dieser Schlüssel kann auf Knopfdruck neu genieriert werden.

Das Beispiel benutzt die Klasse HMACSHA256. Instanzen dieser Klasse können einen Hash-based Message Authentication Code (HMAC) mit dem SHA256-Algorithmus erzeugen.

Zuerst wird eine Instanz von HMACSHA256 mit dem geheimen Schlüssel als Argument erzeugt:


string secretKey = "XblaY"
HMAC signer = new HMACSHA256(secretKey);

Danach wird der gesamte Query-String signiert:


string queryString = "Fertig formatierter String mit allen gewünschten Parametern, für Amazon präpariert";
byte[] sigBytes = signer.ComputeHash(queryString );
string signature = Convert.ToBase64String(sigBytes);

Diese Signatur wird dann als letzter Parameter der Anfrage angehängt.

Kann ich jetzt endlich durch die Benutzung der API reich werden? Meine Nachforschungen haben ergeben, dass weniger Programmieren mehr ist. Besucher einer Website wurden noch nie durch Programmierkunst beeindruckt, sondern durch handgemachten Inhalt. So habe ich denn auch unter denjenigen, die einen nennenswerten Verdienst durch das Partnerprogramm erzielten, nur solche gefunden, die vereinzelte Links auf Amazonprodukte gesetzt haben. Diese Links haben direkt etwas mit dem zu tun, was sie in mühsamer Arbeit gepostet haben. Hier wird es eine vollautomatische Generierung von Produktdaten schwer haben, zu Verdienst gemacht zu werden. Da besucht man doch lieber direkt den Amazon-Shop.

Also, keine Scham, das Partnerprogramm ohne API zu nutzen! Die einfachste Möglichkeit besteht darin, einen Link auf die Amazon-Homepage zu setzen. Damit Amazon nicht zu viele für Suchmaschinen sichtbare Links bekommt, können Links umgeleitet werden. In ASP.NET MVC habe ich das so gemacht:

    public class AmazonLinkController : Controller
    {
        //
        // GET: /AmazonLink/

        public void Index()
        {
            Response.Redirect("http://www.amazon.de/exec/obidos/redirect-home?tag=meinTag-21&site=home");
        }

    }

die Action Index meines Controllers ist eine void-Methode und leitet zu Amazon um.

URL-Rewriting mit ASP.NET MVC

Möchte man ASP.NET MVC einsetzen, um Internetanwendungen zu programmieren, kann man guten Inhalt direkt mit Suchmaschinenoptimierung verknüpfen. Öffentlich zugänglicher Inhalt kann auch Google möglichst gut zugänglich gemacht werden. Die Google-Roboter, die Websites abgrasen und Webseiten in den Index aufnehmen, scheuen vor URL mit Parametern zurück (z.B. www.wetter.de?PLZ=53127) und nehmen statisch erscheinende URL in den Index auf (z.B. www.wetter/53127.html). Das ist ein triftiger Grund, URL statisch aussehen zu lassen. Zusätzlich sehen statische URL freundlicher aus, und können besser gemerkt werden.

Als Erstes definiere ich eine Zuordnung zwischen einem Konfigurationsabschnittshandler und einem Konfigurationsabschnitt. Aus diesem Grund füge ich dem configSections-Knoten in Web.config folgende sectionGroup hinzu:

<configSections>
  <sectionGroup name="myModuleConfig">
    <section name="rewriteModule" type="MyAssembly.RewriteModuleSectionHandler, MyAssembly"/>
  </sectionGroup>
</configSections>

Dementsprechend muss ich dann im configuration-Knoten den Konfigurationsabschnitt definieren:

<myModuleConfig>
  <rewriteModule>
    <rewriteOn>true</rewriteOn>
    <rewriteRules>
      <rule source="Keyword/(.+).aspx" destination="Keyword?keyword=$1"/>
    </rewriteRules>
  </rewriteModule>
 </myModuleConfig>

In diesem Abschnitt definiere ich, ob das URL-Rewriting stattfinden soll und ich definiere Rewrite-Rules, so wie man das unter PHP in einer .htaccess-Datei tun würde.

Nun muss ich den Konfigurationsabschnittshandler RewriteModuleSectionHandler denn auch in Code gießen:

public class RewriteModuleSectionHandler : IConfigurationSectionHandler
{

    private XmlNode _XmlSection;
    private string _RewriteBase;
    private bool _RewriteOn;

    public XmlNode XmlSection
    {
        get { return _XmlSection; }
    }

    public string RewriteBase
    {
        get { return _RewriteBase; }
    }

    public bool RewriteOn
    {
        get { return _RewriteOn; }
    }
    public object Create(object parent, object configContext, System.Xml.XmlNode section)
    {
        _RewriteBase = HttpContext.Current.Request.ApplicationPath + "/";

        try
        {
            _XmlSection = section;
            _RewriteOn = Convert.ToBoolean(section.SelectSingleNode("rewriteOn").InnerText);
        }
        catch (Exception ex)
        {
            throw (new Exception("Error while processing RewriteModule configuration section.", ex));
        }
        return this;
    }
}

Im Wesentlichen nimmt mein Handler bei Erzeugung seinen XML-Knoten in Empfang und dient mir dazu, den XML-Zugriff zu kapseln.

Zusätzlich definiere ich in der Web.config ein HTTP-Modul:

<system.web>
  <httpModules>
    <add name="RewriteModule" type="MyAssembly.RewriteModule, MyAssembly"/>
  </httpModules>
</system.web>

In C#-Code gegossen:

class RewriteModule : IHttpModule
{

    public void Dispose() { }

    public void Init(HttpApplication context)
    {
        context.BeginRequest += new EventHandler(RewriteModule_BeginRequest);
    }

    void RewriteModule_BeginRequest(object sender, EventArgs e)
    {
        RewriteModuleSectionHandler cfg = (RewriteModuleSectionHandler)ConfigurationManager.GetSection("myModuleConfig/rewriteModule");

        if (!cfg.RewriteOn) return;

        string path = HttpContext.Current.Request.Path;

        if (path.Length == 0) return;

        XmlNode rules = cfg.XmlSection.SelectSingleNode("rewriteRules");
        foreach (XmlNode xml in rules.SelectNodes("rule"))
        {
            try
            {
                string attrsource = "/" + xml.Attributes["source"].InnerText;

                Regex re = new Regex(attrsource, RegexOptions.IgnoreCase);

                Match match = re.Match(path);
                if (match.Success)
                {
                    path = re.Replace(path, xml.Attributes["destination"].InnerText);
                    // new path to rewrite to
                    string rew = cfg.RewriteBase + path;

                    // rewrite
                    HttpContext.Current.RewritePath(rew);

                    return;
                }
            }
            catch (Exception ex)
            {
                throw (new Exception("Incorrect rule.", ex));
            }
        }
        return;
    }

}

in der Init-Methode, die mir das IHttpModule-Interface vorschreibt, bekomme ich eine Referenz auf die Anwendung übergeben. Ich füge dem Anwendungsevent BeginRequest als EventHandler einen Delegaten hinzu, der mir schließlich meine URL-Rewritefunktion aufruft:
void RewriteModule_BeginRequest(object sender, EventArgs e)

Nachdem ich meinen RewriteModuleSectionHandler instanziiert habe (über RewriteModuleSectionHandler cfg = (RewriteModuleSectionHandler)ConfigurationManager.GetSection("myModuleConfig/rewriteModule") findet die Instanziierung nur einmal statt), gehe ich alle Rewrite-Regeln durch und prüfe mit Regex und Match, ob der reguläre Ausdruck der Anfrage-URL mit dem der Regel übereinstimmt. Mit Regex.Replace(…) wird die URL endlich umgeschrieben.

SGF-Player in Silverlight (Teil 2) – 99,1% Plattformunabhängigkeit

Nachdem ich die Machbarkeit eines Webservice erkundet habe, mache ich mich nun an die Implementierung eines Teils der Oberfläche und der Portierung der Logik von meiner letzten Entwicklung eines SGF-Players mit Java.
Als Erstes verpacke ich meine Oberfläche in ein Grid-Layout. Eine Zelle beherbergt eine ViewBox-Element. Wie in SVG werden Inhalte dieser Box skaliert, je nachdem, wieviel Platz sie innerhalb des Gitters im Browserfester zu Verfügung bekommt. Trotzdem kann die Box ein Rectangle in einem Canvas beinhalten, welches 1000 x 1000 Pixel groß ist. Auch wenn die tatsächliche Größe des Rechtecks abweicht, kann ich im Code mit dieser Größe rechnen. Auf diese Weise kann ich ganz einfach meinen Java Code portieren, der ebenfalls mit dieser Größe arbeitet.
Obwohl XAML keinen Standard für Vektorgrafik bietet, kann ich die Konzepte meiner Grafikgenerierung, die ich mit der SVG basierten Java-Bibliothek Batik entwickelt habe, übernehmen. Das Muster des Go-Bretts kommt durch eine große Textur zustande, ein Foto meiner Schreibtischplatte. Das bringt mir 500 KB Übertragungsoverhead ein. Aber in Zeiten der schnellen Leitungen kann ich eine Optimierung verzögern.
Die Portierung der Logik verlief am poblemlosesten. Ich kopierte meinen Java Code mit sehr wenigen Änderungen nach C#. Ich musste etwa die Syntax für mehrdimensionale Arrays oder von Stringoperationen ändern. Ein vollständiges Austauschen der for- in die besser lesbaren und eleganteren foreach-Anweisungen konnte ich mir nicht verkneifen.
Unter Windows flutscht die Anwendung. Rückmeldungen eines Mac-nutzenden Kumpels sind ebenfalls sehr positiv. Die Steine werden fast genauso schnell gesetzt, wie ich klicken kann. Als böse Überraschung finde ich unter Linux Unebenheiten vor. Auf meinem Rechner stellt Moonlight die Steine nicht korrekt dar. Auf dem Rechner meine Frau tut Moonlight es, dafür fehlen bei beiden Rechnern die Schatten. Installiere ich Moonlight 3, wird zwar alles korrekt dargestellt, aber die CPU-Auslastung ist sehr hoch, auch wenn die Anwendung ruhen sollte. Die Frickelei, um herauszufinden, woran es hapert, spare ich mir im Urlaub. Im Forum von go-mono.com habe ich mein Problem geschildert, wieder ohne Antwort. Das erfüllt mich nicht mit Vertrauen und ich hadere einen Moment.
Warum andererseits trauern? Meine Zielgruppe sind Clientnutzer und die benutzen zu 99,1 % Windows oder Macintosh. Es gibt laut Net Market Share nur 0,9% Linuxinstallationen auf dem Desktop. Und hier geht es ja nicht um Jesus, der seine 99 Schafe zurücklässt, um ein verlorenes Schaf zu retten. Und vielleicht macht sich Moonlight ja noch. Und wer interessiert sich schon für Go?
Hier die Anwendung. Die inhaltliche Benutzbarkeit muss natürlich erst noch hergestellt werden.