Donald Knuth, der Meister der Lyrik

vom 3. August 2009

Donald E. Knuth ist die lebende Legende im Softwareumfeld. Er widmet schon über Jahrzehnte hinweg sein Leben der Forschung und der formalen Beschreibung von Algorithmen. Sein "still in progress" Band Art of Computer Programming ist wahrlich ein Meisterwerk. Interessant ist aber, dass sich die wenigsten Informatiker an dieses monströse Werk wagen. Er ist auch der Auffassung, dass Programmcode mit der selben Sorgfalt verfasst werden sollten wie literarische Texte. Dokumentation und Code sollten vereint sein.

"Let us change our traditional attitude to the construction of programs. Instead of imagining that our main task is to instruct a computer what to do, let us concentrate rather on explaining to human beings what we want a computer to do." - Donald E. Knuth

Da ich auch einen gewissen Standard an Dokumentation und Programmcode habe und meinen Code ästhetisch ansprechend formatiere und schreibe (bzw. den Code so gestalte, dass er mich anspricht :)), bin ich natürlich immer wieder auf der Suche nach Vorbildern.

Deshalb dachte ich mir, wieso nicht mal den Quellcode von TeX (einem Textsatzsystem von Herrn Ph. D. Knuth persönlich) anschauen. Und tatsächlich: Im ersten Moment dachte ich, der Code sei nur die Dokumentation zu TeX. Er liest sich wie eine Definition oder ein Informatikbuch. Hier mal ein kleiner Auszug:

@ Now comes the harder part: At this point in the program, |cur_val| is a
nonnegative integer and $f/2^{16}$ is a nonnegative fraction less than 1;
we want to multiply the sum of these two quantities by the appropriate
factor, based on the specified units, in order to produce a |scaled|
result, and we want to do the calculation with fixed point arithmetic that
does not overflow.

@<Scan units and set |cur_val| to $x\cdot(|cur_val|+f/2^{16})$...@>=
if inf then @<Scan for \(f)\.{fil} units; |goto attach_fraction| if found@>;
@<Scan for \(u)units that are internal dimensions;
  |goto attach_sign| with |cur_val| set if found@>;
if mu then @<Scan for \(m)\.{mu} units and |goto attach_fraction|@>;
if scan_keyword("true") then @<Adjust \(f)for the magnification ratio@>;
@.true@>
if scan_keyword("pt") then goto attach_fraction; {the easy case}
@.pt@>
@<Scan for \(a)all other units and adjust |cur_val| and |f| accordingly;
  |goto done| in the case of scaled points@>;
attach_fraction: if cur_val>=@'40000 then arith_error:=true
else cur_val:=cur_val*unity+f;
done:

Abgesehen von der gewöhnungsbedürftigen Syntax ist das fast schon lyrik, was er da an den Tag legt. Leider sind nicht alle Programmiersprachen so biegsam, aber Ruby geht hier schon sehr in die richtige Richtung. Also hier ein Aufruf an alle: Doku + Code = Awesome! :)

6 Kommentare, delicious bookmark del.icio.us


Active Browsing, Teil 2

vom 23. July 2009

Im letzten Beitrag habe ich über Active Browsing geschrieben und der Mächtigkeit, die jeder User im Grunde hat, diese aber nicht nutzt. Wir wollen uns nun das Leben mit ein paar Zeilen Code vereinfachen.

Installation der Komponenten

Um ruby-libnotify und mechanize nutzen zu können, müssen sie zuerst installiert werden. Bei Mechanize ist das kein Problem, da es schon als rubygem vorliegt. Ein simples gem install mechanize installiert das Paket, inklusive nokogiri für das Parsen der Webseiten. Bei ruby-libnotify muss man selbst Hand anlegen und mit dem Dreischritt ruby extconf.rb && make && make install das Paket zusammenbauen und installieren. Wer das Paket libopenssl-ruby noch nicht installiert hat, sollte das zuvor erledigen.

Login und Parsing

Der Erste Schritt ist nun, sich auf der Webseite anzumelden und die Titel aller Jobaufträge aus der HTML Datei herauszusuchen. Wir binden zuerst die Pakete mechanize und nokogiri ein, um damit arbeiten zu können.

require 'mechanize'
require 'nokogiri'

Als nächstes kümmern wir uns um den Login. Hier kommt uns mechanize sehr entgegen, denn es bietet ein simples Interface zur HTML-Seite. Machen wir zuerst Gebrauch von den Methoden get und submit:

class TextBroker
    def initialize(mail, pass)
        @agent = WWW::Mechanize.new
        @mail, @pass = mail, pass
        login
    end
       
    private
    def login
        page = @agent.get('http://www.textbroker.de/')
        loginForm = page.form('login-autoren')
        loginForm.author_mail = @mail
        loginForm.pass = @pass
        @agent.submit(loginForm, loginForm.buttons.first)
    end
end

Unter @agent ist die mechanize-Instanz abgelegt, die wir im Konstruktor initialisieren. Die private Methode login läd zuerst die Startseite mit get herunter und greift sich das Login Formular der Autoren anhand der ID. Die Attribute author_mail und pass sind Formularfelder, die sich so befüllen und auslesen lassen. mit submit wird das Login-Formular abgeschickt. In der mechanize Instanz wird nun der Cookie gesetzt, um dauerhaft eingeloggt zu sein. Ab jetzt können wir GET/POST-Anfragen innerhalb des passwortgeschützten Bereichs starten ohne sich erneut einzuloggen.

Sammeln wir nun die Jobs ein, die auf der Seite verfügbar sind. Ich habe die Methode fetch genannt, und in die TextBroker Klasse integriert. (In Ruby sind alle Klassen offen und können so erweitert werden, hier wird also keine neue Klasse angelegt, sondern die bestehende erweitert)

class TextBroker
    def fetch
        page = @agent.get('http://www.textbroker.de/a/search.php')
        links = page.links.select {|l| !l.href.nil? and l.href.include?("search.php?hdl_id=") }
        links.map {|l| l.text }
    end
end

Wir stellen einen neuen GET-Request, um uns die HTML-Seite mit den Jobangeboten zu besorgen. über das Attribut links erhalten wir ein Array von allen Links, die auf der Seite verfügbar sind. Daraus selektieren wir uns alle, die im href Attribut den String "search.php?hdl_id=" enthalten haben. Da wir nur am Linktext interessiert sind, erstellen wir mit map ein Array der Linktexte und geben diese zurück.

Damit sind wir schon mit dem ersten Teil fertig und können uns eine Liste der Aufträge holen.

wwwAccess = TextBroker.new('mail@aaron-mueller.de', 'xxx')
pp wwwAccess.fetch #=> ["...", "...", ...]

Im nächsten Schritt werden wir diese Information auswerten und grafisch aufbereiten, so dass der User nur das sieht, was er wirklich sehen will: Die neu hinzugekommenen Jobaufträge.

Darstellung

Da ich unter GNOME arbeite, habe ich mich für das GTK Binding entschieden. Zusammen mit ruby-libnotify ist das Zusammenstecken einer GUI ganz einfach. Zuerst einmal binden wir wieder die benötigten Bibliotheken ein:

require 'gtk2'
require 'RNotify'

Da wir unser Programm in der Notification Area positionieren wollen, brauchen wir grundlegend nur ein Gtk::StatusIcon und die Notification selbst.

statusIcon = Gtk::StatusIcon.new
statusIcon.stock = Gtk::Stock::DND_MULTIPLE
statusIcon.tooltip = "Textbroker.de Benachrichtigung"
notify = Notify::Notification.new("Header", "Content", nil, statusIcon)
notify.timeout = 5000

Um nun in regelmäßigen Abständen nach neuen Aufträgen zu suchen, und um diesen Vorgang starten und stoppen zu können, verbinden wir das Signal "activate" (was für einen Linksklick steht) mit einem Stück Code:

wwwAccess = TextBroker.new('mail@aaron-mueller.de', 'xxx')
fetchThread = nil

statusIcon.signal_connect("activate") {|widget, event|
    if fetchThread.instance_of? Thread
        fetchThread.terminate
        fetchThread = nil
        statusIcon.stock = Gtk::Stock::DND_MULTIPLE
    else
        statusIcon.stock = Gtk::Stock::JUMP_TO
        fetchThread = Thread.new do
            oldJobs = []
            loop do
                newJobs = (jobs = wwwAccess.fetch) - oldJobs
                oldJobs = jobs
                if !newJobs.empty?
                    content = "\n\n"
                    newJobs.each {|job| content += job + "\n" }
                    notify.update("Neue Schreibaufträge vorhanden", content, nil)
                    notify.show
                end
                sleep 60
            end
        end
    end
}

Dies bedarf einer Erklärung: Jedes mal, wenn das Icon gedrückt wird, soll es entweder den fetchThread starten und in regelmäßigen Abständen nach neuen Jobs suchen, oder den aktuellen Thread beenden. Um dies zu visualisieren, ändern wir das Icon bei jeden Klick. Wird der Thread gestartet, läuft er in eine Endlosschleife, die alle 60 Sekunden unsere fetch Methode aus der TextBroker Klasse aufruft und dann mit dem letzten Stand überprüft. Gibt es Veränderungen (ist also das newJobs Array nicht leer), rufen wir die Methode update vom Notification Objekt auf und zeigen diese an.

Damit man das Programm auch anständig beenden kann, geben wir dem Icon noch ein Kontextmenü mit einem Beenden Eintrag.

menu = Gtk::Menu.new
menuQuit = Gtk::ImageMenuItem.new(Gtk::Stock::QUIT)
menuQuit.signal_connect("activate") {|widget, event| Gtk.main_quit }
menu.append(menuQuit)

statusIcon.signal_connect("popup-menu") {|widget, button, time|
    if button == 3
        menu.show_all
        menu.popup(nil, nil, button, time)        
    end
}

Am Ende müssen wir nur noch die Notification initialisieren und in den Event-Loop von GTK hineinspringen. Danach übernimmt GTK die Ablaufkontrolle.

Notify.init("textbroker-notification")
Gtk.main

Zusammengezählt sind das nun ca. 80 Zeilen Code, nicht viel für eine solche Aufgabe. Dieses Vorgehen lässt sich auf beliebige Resourcen anwenden und erleichtert den Alltag ungemein. Allerdings kann man nicht oft genug darauf hinweisen, dass man hier Funktionalität erstellt, die vom Autor der Webseite (oder einer anderen Resource) so nicht vorgesehen wurden, entweder aus Faulheit oder aber aus gutem Grund.

Habt ihr Ideen, wo man eine solche Technik einsetzen kann? Oder habt ihr selbst schon das ein oder andere Tool geschrieben? Soll ich öfters solche "Beispielprojekte" vorstellen? Lasst es mich in den Kommentaren wissen. :)

2 Kommentare, delicious bookmark del.icio.us


Active Browsing, Teil 1

vom 23. July 2009

Wer bei meinem Vortrag mit dem Titel "Fix the web with Greasemonkey" dabei war, weiß was gemeint ist. Für alle anderen ein kleiner Abriss: Das Web liefert uns aufbereitete Daten in einer Form, die der Ersteller festlegt. Oft ist es aber so, dass man nur an einer bestimmten Information interessiert ist und alles andere am Liebsten ausblenden würde oder auf eine andere Art und Weise dargestellt bekommen möchte. Mechanismen, die dazu beitragen, die Informationen so darzustellen, wie es einem selbst am besten gefällt, nennt man Active Browsing. Mittlerweile gibt es einige Tools, um sich das Surfen etwas zu personalisieren und nach den eigenen Wünschen zu formen.

Mit dem schon erwähnten Greasemonkey - einem Firefox-Addon - kann man durch einfache Javascript-Dateien die Darstellung umgestalten, in dem man in den DOM-Baum eingreift. Ein anderes Beispiel ist Readability, welches durch ein Bookmarklet nur den eigentlichen Content einer Seite anzeigt.

Diese Tools vereinfachen das Surfen ungemein, aber es gibt oft genug Situationen, wo man regelmäßig eine bestimmte Information auf einer Webseite abfragen will, bspw. den Flottenstatus bei einem Onlinespiel oder ob jemand neue Artikel bei eBay eingestellt hat. Für gewöhnlich geht man hier alle paar Stunden auf die Seite, loggt sich ein, hangelt sich durch ein paar Menüs und sucht die Information, die meistens nur aus einer Zahl oder einer Liste bestehen, heraus. Wie praktisch wäre es doch, wenn solche Seiten einen RSS-Feed oder eine andere API anbieten würden, damit man immer zur richtigen Zeit mit den entsprechenden Informationen versorgt werden würde. Dies ist allerdings in den seltensten Fällen so. Eine bessere Möglichkeit muss also her!

Hinweis: Da ich nach der Springerlink Aktion (eingeweihte wissen Bescheid) etwas vorsichtig geworden bin was "Web-Automatismus" angeht, soll das folgende Beispiel nur das Konzept verdeutlichen und dient NICHT zur eins zu eins Nachahmung. Deshalb stelle ich auch nicht den kompletten Code online, sondern nur die einzelnen Stücke separat. Zudem wird das Verfahren oft missbräuchlich verwendet. Grundsätzlich ist jeder selbst für sein Handeln verantwortlich und sollte mit gesundem Menschenverstand und Rücksicht auf den Webseitenbetreiber vorgehen, denn dieser stellt schließlich den Service bereit und trägt den Traffic.

Um mit dem Nachfolgenden etwas anfangen zu können, muss man kein Ruby können und auch kein Linux verwenden, es soll nur das Konzept beschrieben werden. Ähnliche Tools und Bibliotheken gibt es in fast jeder Sprache.

So, nun also zum Thema! Weiter oben habe ich die Problematik mit den immer wiederkehrenden Schritten, um an eine bestimmte Information zu kommen, schon angesprochen. Diesen Umstand wollen wir vereinfachen. Als Beispiel nehme ich das Portal textbroker.com, auf dem Autoren Texte für Geld schreiben können und Kunden Anfragen für Texte stellen können. Leider gibt es mehr Autoren als Kunden, so sind die meisten Aufträge schon nach ein paar Minuten nach der Einstellung vergeben. Sprich, man muss entweder Glück haben und im richtigen Moment die Stellenbeschreibung anschauen oder eben alle paar Minuten das folgende Szenario abspulen:

Auf die Startseite gehen und sich anmelden. Zum Glück speichert Firefox nach dem ersten Mal ein Cookie und dieser Schritt muss nur einmal am Tag gemacht werden. Dann auf "Aufträge Zeigen" und anschließend auf den Button "Suche starten" klicken. Nun bekommt man alle verfügbaren Aufträge angezeigt. Da man meist nur an bestimmten Aufträgen interessiert ist, und die Liste alle verfügbaren Aufträge anzeigt, muss man diese nach Änderungen durchsuchen und kann sich dann entscheiden, ob man den Auftrag annehmen will oder nicht.

Diesen Vorgang wollen wir automatisieren, aber nicht im Browser, sondern mit einem externen Programm. Die Idee ist, dass sich das Programm selbständig in regelmäßigen Abständen auf der Webseite einloggt, die Liste der verfügbaren Aufträge holt und diese dem User über die Notification Area der Schnellstartleiste (unter Windows wird das meines Wissens SysTray genannt), als Notification (Sprechblase) anzeigt. Das Ergebniss soll so aussehen:

Notification bei neuen Jobangeboten

Um dies ohne händisches Parsen von XML Dateien oder umständliche wget/curl Aufrufe zu lösen, gibt es im Ruby Umfeld einige interessante Projekte. Eines davon nennt sich mechanize, das die Interaktion mit Webseiten um einiges vereinfacht. So wird dem Programmierer beispielsweise das Cookie-Handling komplett abgenommen und auch das Parsen der HTML-Seiten ist erschreckend simpel. Ein weiteres tolles Projekt ist ruby-libnotify, welches ein Wrapper für libnotify implementiert, um Betriebssystem Notifications darzustellen.

Mit diesen beiden Helfern wollen wir ein Programm zusammennageln, das uns immer die neusten Jobangebote anzeigt. Wie dies funktioniert, erfahrt ihr im nächsten Blogeintrag :)

Kommentar schreiben, delicious bookmark del.icio.us


Analyse des Programmierwettbewerbs

vom 22. September 2008

Zuerst einmal möchte ich mich bei allen bedanken die mitgeamcht haben! Insgesammt habe ich sieben Einsendungen in den unterschiedlichsten Programmiersprachen erhalten, das hat mich sehr gefreut!

Mitgemacht haben (sortiert nach Abgabedatum):
Aaron Müller (Ruby, download)
Klaus Breyer (PHP, download)
Thomas Monninger (Bash, download)
Florian Eitel (Ruby, download)
Wolfgang Kuhl (Java, download)
Stefan Nitz (PHP, download)
Marcel Steinle (C#, download)
Robert Giczewski (Java, download)

(Alle Programme herunterladen)

Die Aufgabe

Die gestellte Aufgabe haben alle erfolgreich gemeistert. Ziel war es, einen kleinen Transformator zu schreiben, der Text in eine strukturierte Form bringt. Dabei gab es verschiedene Ansätze, die meisten verwendeten reguläre Ausdrücke um den Text zu analysieren, aber es gab auch andere interessante Lösungen. (Bei den Codeausschnitten habe ich mich aufs Wesentliche beschränkt und Unnötiges wie das Einlesen der Datei oder die Ausgabe oftmals weggelassen. Wer den kompletten Code sehen will, klickt auf den angegebenen Link)

Auswertung

Klaus

  • Sprache: PHP
  • LOC: 10

Schon nach ein paar Stunden erhielt ich seine Lösung. Er verwendete die preg_split()-Funktion um mit Hilfe eines regulären Ausdrucks die Aufzählungszeichen als Trenner zu identifizieren. So zerlegte er den kompletten Eingabetext in einem Rutsch. In einer Schleife entfernte er dann die Zeilenumbrüche durch Leerzeichen. Zum Testen stellte er gleich noch ein Eingabeformular bereit.

$text = $_POST['text'];
$array = preg_split("/((\-\s)|(\>\s)|(\d\.\s)|(\so\s))+/", $text, -1, PREG_SPLIT_NO_EMPTY);
foreach($array as $key => $value) {
    $array[$key] = trim(str_replace("\n", " ", $value));
}

echo "<p>";
foreach($array as $key => $value)   {
    echo " * " . $value . "<br />";
}
echo "</p>";

Kleine Erklärug zum RegExp-Ausdruck: die \s fangen alles auf, was im Entferntesten nach Leerzeichen aussieht, das \d steht für eine Ziffer. Die runden Klammern markieren Anker um später darauf zugreifen zu können (was hier aber nicht verwendet wurde) oder gruppieren Ausdrücke.

Thomas

  • Sprache: Bash
  • LOC: 23

Auch sehr schnell war Thomas mit seiner Bash-Lösung. Auch hier wurde mit regulären Ausdrücken gearbeitet, um zuerst die Leerzeichen und leere Aufzähungspunkte wegzuschneiden (\d), dann nach den Aufzählungspunkten zu suchen und durch Sternchen zu ersetzen (\g) und zum Schluss noch doppelte Sternchen und Ziffern zu entfernen. Wie es unter Linux üblich ist wurde hier sed verwendet. Als temporärer Zwischenspeicher mussten Dateien herhalten.

if [ $# -eq 1 ]
then
 sed -e '/^[ \t]*$/d' -e 's/\(^[ \x9]*\([->o]\|\([0-9]\+[\.]*\)\)\)/'*'/g' -e 's/^\*\([ ]*\)/'*' /g' -e 's/^[ \x9]\+/''/g' $1 > tmp.tmp
 while read zeile; do
 if [ "${zeile:0:1}" = "*" ]
 then
  if [ -n "$tmp1" ]
  then
   echo "$tmp1" > tmp2.tmp
   tmp1="$zeile"
  else
   tmp1="$zeile"
  fi
 else
   tmp1="$tmp1"" ""$zeile"
 fi
 done < tmp.tmp
 cat tmp2.tmp > ergebnis 
 rm tmp.tmp
 rm tmp2.tmp
else
 echo "Usage: thomas.sh <input-file>"
fi

Wer die Syntax von Bash nicht kennt: Um Teile aus einem String herauszufiltern, kann man "${zeile:0:1}" verwenden (ähnlich wie substring() in anderen Sprachen). Das -n beim if prüft ob der String NICHT leer ist (NonZero).

Florian

  • Sprache: Ruby
  • LOC: 2

Sehr kompakt formuliert und ohne jeglichen extra Ballast: Der Eingabetext wird von der Standardeingabe gelesen, drei reguläre Ausdrücke darauf angewendet und wieder auf der Standardausgabe ausgegeben. Der Ablauf ist ähnlich wie bei Thomas: Im ersten Schritt werden die Aufzählungspunkte durch Sternchen ersetzt und die Leerzeichen entfernt, im zweiten Schritt werden die Zeilenumbrüche behandelt. Das letzte gsub() behandelt den Spezialfall, falls eine Ziffer alleine in einer Zeile steht. Dadurch, dass die Aufzählungspunkte extra definiert sind, lässt sich der Transformator sehr schnell erweitern!

bullets = %w( [[:digit:]]+\.  -  >  o )
puts STDIN.read.gsub(/(^\s*(#{ bullets.join("|") })\s+)/, "* ").gsub(/\n(?!\*)\s*/, " ").gsub(/^\* \s+/, "* ")

Für die, die noch nie mit Ruby gearbeitet haben: Das %w erzeugt ein Array, der Separator hier ist das Leerzeichen. gsub() ist äquivalent zum preg_replace() von PHP.

Wolfgang

  • Sprache: Java
  • LOC: 26

Einen ganz anderen Weg hat Wolfgang mit seiner Java-Lösung gewählt. Ganz ohne reguläre Ausdrücke hat er das Problem mit Zeichenvergleiche gelöst. Von jeder Zeile werden zuerst die Leerzeichen am Anfang und am Ende entfernt. Diese Strings werden dann dahingehend überprüft ob das zweite Zeichen ein Punkt oder ein Leerzeichen ist, also ob es sich um ein Aufzählungspunkt handelt. Wenn das der Fall ist, wird der Rest zusammen mit dem Sternchen an die Ausgabe angehängt. So ist der Code auch für alle anderen Typen von Aufzählungszeichen vorbereitet, doch wenn das Aufzählungszeichen aus zwei Zeichen besteht (z.B. "14." oder "#>") oder ein einzelner Buchstabe am Anfang einer neuen Zeile steht, funktioniert das Programm nicht mehr richtig.

public String parseToWikiString(File file) throws IOException{
  try {
    br = new BufferedReader(new FileReader(file));  
    zeile = br.readLine();
    while (zeile != null) {
      if(!zeile.equals("")){  
        zeile = zeile.trim();
        if(zeile.charAt(1) == '.' || zeile.charAt(1) == ' '){
          output = output + "\r\n * ";
          zeile = zeile.substring(2).trim();
          output = output + zeile;  
        }else
          output = output + zeile ;    
      }
      zeile = br.readLine();
    }
    br.close();
    return output;
  }catch (FileNotFoundException e){
    e.printStackTrace();}
  return "Fehler beim Parsen";
}

Stefan (PHP)

  • Sprache: PHP
  • LOC: 11

Stefan hat es sich mit seiner Lösung einfach gemacht. Er nahm wiederum die preg_split()-Funktion zur Hilfe um die Aufzählungspunkte zu finden und an den Stellen zu trennen. Da der Browser (wenn man denn das PHP-Skript auf einem Webserver ausführt) nur ein Leerzeichen zulässt und Zeilenumbrüche ebenfalls ignoriert, kommt seine Lösung ganz ohne Entfernung dieser aus. Klever gemacht :-)

$keywords = preg_split("/- |[0-9]\.|>| o /", $txt);
for ($x = 1; $x < sizeof($keywords); $x++) {
    echo "* ".$keywords[$x]."
"; }

Marcel

  • Sprache: C#
  • LOC: 28

Auch eine C#-Variante wurde von Marcel eingereicht! Schön dokumentiert und übersichtlich. Der Eingabetext wird zuerst an den bekannten Kommentarzeichen gesplittet, von den einzelnen Ergebniszeilen die Leerzeichen entfernt und mit einem Stern ausgegeben. Leider wurde nicht beachtet, dass die Aufzählungspunkte immer am Anfang stehen müssen. So gibt es schnell Fehler, wenn beispielsweise ein HTML-Tag im Text steht oder der 70. Geburtstag der Großeltern gefeiert wird.

StreamReader sr = new StreamReader(args[0]);
StreamWriter sw = new StreamWriter(args[1]);

Regex split = new Regex("-|  o |> |[1-99].", RegexOptions.Multiline | RegexOptions.Compiled);
Regex whitespace = new Regex(@"\s+|\s+\n|\n|\r\n");
while (!sr.EndOfStream)
    input += Convert.ToString((char)sr.Read());

intermediate = split.Split(input);
for (int i = 1; i < intermediate.Length; i++)
{
    sw.WriteLine("* " + whitespace.Replace(intermediate[i], " ").Trim());
}
sw.Close();

Robert

  • Sprache: Java
  • LOC: 108

Auch diese Java-Lösung kommt ganz ohne reguläre Ausdrücke aus. Hier wird in der Methode identifyBulletPoints() in einer Schleife auf die verschiedenen Vorkommen von Aufzählungszeichen überprüft und diese dann durch Sternchen ersetzt. Warum eine neue myTrim()-Methode implementiert wurde, war mir nicht ganz klar. Die rekursive Methode prepareList() bringt dann die noch unstrukturierte Liste in die richtige Form.

private void identifyBulletPoints(ArrayList<String> tmp){
  ArrayList<String> dummy= new ArrayList<String>();
  for(int i=0; i<tmp.size();i++){
     dummy.add(myTrim(tmp.get(i)));
     next = false;
     for(int n=0;n<pattern.length&!next;n++){
        if(dummy.get(i).indexOf(pattern[n])==0){
           if(pattern[n] == "o ")
              final_version.add(dummy.get(i).replaceFirst(pattern[n], "* "));
           else
              final_version.add(dummy.get(i).replaceFirst(pattern[n], "*"));
           next = true;
        }
     }
     if(!next)
        final_version.add(dummy.get(i));
  } 
}

private ArrayList<String> prepareList(ArrayList<String> a){
  for(int i=0; i<a.size();i++){
     if(final_version.get(i).indexOf("*")!=0){
        if(i>>0){
           final_version.set(i-1, final_version.get(i-1).concat(" "+final_version.get(i)));
           final_version.remove(i);
           prepareList(final_version);
        }
     }
  }
  return final_version;
}

Aaron

  • Sprache: Ruby
  • LOC: 6

Zu guter Letzt, meine Version. Ich habe ebenfalls wie einige andere auch, die Aufzählungspunkte als Trenner für die split()-Funktion verwendet, anschließend die Leerzeichen entfernt und alle Zeilen die nicht mit einem Stern anfangen zur vorherigen hinzugefügt.

input = ""
while line = gets do input += line end
input.split(/^[\s]*[-|\*|#|>|+|o]|[\d]+[.|:]?[\s]*/).each do |line|
    task = line.gsub(/  /, "").gsub(/\n[\s]*/, " ").strip
    puts "* #{task}" unless task == ""
end

Analyse

Ich habe mir überlegt, wie man diese Programme anständig miteinander vergleichen könnte. Da alle Programme das gleiche tun, habe ich mich für einen (zugegeben sehr primitiven) Benchmark entschlossen. Das Ergebnis war beeindruckend!

(Als Testumgebung musste mein AMD Duron 1.6 mit 1GB Ram und Ubuntu Linux 8.04 herhalten)

LOC (Die komplette Laufzeittabelle)

Florians Lösung (Ruby) scheint bereits auf Performance getrimmt zu sein, denn auch bei sehr großen Eingaben läuft das Programm innerhalb einer 10tel Sekunde durch. An der Programmiersprache kann es nicht liegen, da meine Ruby-Version ganze 21 Sekunden für 30 Tausend Zeilen braucht. Auch sehr schnell waren die PHP-Versionen von Stefan und Klaus. Hier glaube ich aber, dass die PCRE-Engine von PHP sehr viel an Zeit gespart hat, da gerade diese Funktionen die Kernpunkte der Sprache darstellen und dementsprechend optimiert wurden. Die Bash-Lösung von Thomas arbeitet sehr stabil, die Ausführungszeit steigt linear zum Input.

Erstaunt hat mich die Laufzeit der Java-Versionen von Robert und Wolfgang, hier gibt es sehr starke Unterschiede. Bei kleineren Inputs sind beide noch gleich auf, doch bei größeren Inputs bricht Wolfgangs Lösung sehr stark ein und braucht bei 30.000 Zeilen fast 17 Minuten. Grund dafür könnte die Bearbeitung auf Character-Ebene sein. Vielleicht weiß hier jemand eine treffendere Erklärung?

Die C#-Version von Marcel braucht auch bei kleineren Inputs schon recht lange. Einen Grund dafür könnte das zeichenweise Einlesen des Inputs sein, oder aber Mono ist schuld daran. (Ich habe die C#-Version mit dem Mono JIT-Compiler 1.2.6 übersetzt)

Wer den Benchmark selbst noch einmal auf seinem Rechner ausführen will, kann das gerne tun. Die komplette "Testumgebung" einfach Entpacken und das Ruby-Skript starten.

LOC

Auch interessant ist die größe des Programmcodes. Florian hat es in zwei Zeilen geschafft, wärend Robert ganze 108 Zeilen benötogt hat. Über Übersichtlichkeit und Design lässt sich hier natürlich streiten, doch mir gefällt der Spruch "Keep simple things simple" sehr gut :-) Natürlich liegt es hauptsächlich an der gewählten Programmiersprache und deren Features, wie viele Zeilen Code man braucht und welche Herangehensweise man wählt.

Bewertung

Hier seit ihr gefordert. Verwendet bitte die Kommentarfunktion am linken Seitenrand, um eure Meinung zum Code abzugeben.

Vielen Dank an alle fürs Mitmachen!

Nachtrag: Klaus und Florian haben sich die Mühe gemacht und den Benchmark bei sich auszuprobieren. Danke dafür! Erstaunlich ist, dass die Ergebnisse auf einem Windows-System etwas anders ausfallen, aber seht selbst:

Klaus

  • CPU: AMD Phenom X4 9950 (4 x 2.6Ghz)
  • RAM: 4GB DDR2 800Mhz
  • OS: Ubuntu 64 Bit Hardy Heron

Testergebnisse, Systemauslastung

Florian

  • CPU: Intel Core 2 Quad CPU - 2.40GHz
  • RAM: 2GB
  • OS: WinXP SP3

Testergebnisse

6 Kommentare, delicious bookmark del.icio.us


Programmierwettbewerb

vom 16. September 2008

Da ja zur Zeit sonst nicht viel auf meinem Blog passiert möchte ich einen kleinen Programmier-Wettbewerb starten. Die Aufgabe ist nicht schwer und fordert ca. eine Stunde Spaß.

Problem

Änderungswünsche vom Kunden erhalte ich meistens als Liste per E-Mail oder Word-Dokument (ich weiß, es ist grausam, aber was will man machen?). Damit ich bei all den Mails und Dateien nicht den Überblick verliere, trage ich jeden Änderungswunsch in mein Wiki ein und hake ihn bei Beendigung ab und schreibe evtl. noch einen Kommentar darunter.

Leider hat mein Wiki eine vordefinierte Syntax für Aufzählungslisten, so dass ich zuerst die Punkte ins Wiki kopiere und dann Punkt für Punkt mühsam in Wiki-Syntax umformatieren muss. Das ist mir aber nun zu blöde und ich will diesen Vorgang automatisieren.

Aufgabe

Die Aufgabe ist nun, einen Transformator zu schreiben, der Aufzählungslisten aller Art entgegennimmt, und daraus Wiki-konforme Aufzählungslisten erzeugt. Zum Testen habe ich eine Beispieldatei erstellt, die transformiert werden soll. (zum Vergleich hier der Output) Der Transformator kann in einer beliebigen Sprache geschrieben werden, die auf den gängisten Betriebssystemen Windows, Linux und OSX interpretiert oder compilliert werden kann. Als Input (Dateiname, InputStream, Pipe, ...) erhält der Transformator die chaotische Auflistung, als Output (auf der Console, im Browser oder in einer GUI) wird die transformierte Wiki-Syntax ausgegeben.

Wer mir bis zum Sonntag (21.09.2008) seine Lösung per E-Mail an mail@aaron-mueller.de schickt, nimmt beim Wettbewerb teil. Alle Einsendungen werde ich hier im Blog präsentieren, die Jury seit ihr selbst. Jeder der mitmacht erhält unzählige Lobpreisungen, Ruhm und Ehre, mehr kann ich euch leider im Moment nicht bieten. :)

Warum soll ich da mitmachen?

Weil es eine gute Übung ist. Ich hatte das Problem anfangs mit ca. 2 Seiten Code gelöst. Nach etwas Überlegen waren es nur noch 4 Zeilen. Zudem ist es spannend zu sehen, wie andere an das Problem herangehen und welche Features der gewählten Programmiersprache beonders nützlich dabei sind.

Bei reger Teilnahme (was mich sehr freuen würde) werde ich öfters solche Aufgaben stellen. Ich wünsche euch viel Erfolg bei der Lösung. Happy hacking!

Update: 7 Einreichungen hab ich schon erhalten. Wer noch mitmachen will, hat bis Sonntag 12:00 Uhr Zeit. Ich freue mich über jede Einsendung!

6 Kommentare, delicious bookmark del.icio.us


XML als DSL für GUIs

vom 21. June 2008

Ich bin schon seit vielen Jahren auf der Suche nach einer Möglichkeit, GUIs mit einem grafischen Editor zu erstellen und trotzdem nicht von einer IDE abhängig zu sein. Ich wünschte mir ein Tool, mit dem ich die GUI erstellen könnte und als "Output" eine (XML)-Beschreibung der erstellten Oberfläche erhalten würde.

AWT/Swing in Java war mir schon immer zuwieder (mit und ohne GUI-Builder). Der (live) generierte Java-Code ist meist dermaßen hässlich und nachträglich unwartbar, so dass ich meist wieder selbst Hand angelegt habe und die GUI-Elemente selbst plaziert habe. Dass es hier noch keine vernünftige Lösung gibt, wundert mich immer wieder.

Vor kurzem bin ich auf Ruby-Gnome2 gestoßen, was meine Erwartungen mehr als erfüllt hat: Mit dem general purpose GUI Builder GLADE erstellt man die GUI und speichert diese als Projekt ab. Das abgespeicherte Projekt besteht aus einer einzelnen XML-Datei, in der die komplette GUI inkl. "signals" (Event-/ActionListener für die Java-Programmierer) definiert sind. Diese XML-Datei lässt sich nun mit der GladeXML-Lib in Ruby mit wenigen Zeilen Code einlesen und ausführen. Auf Signale/Events kann man mühelos mit simplen Methoden zugreifen.

Wer mir nicht glaubt, hier eine kleine Demonstration: Zuerst wird mit GLADE die Oberfläche erstellt.

GLADE Interface Designer

Links (1) hat man die übliche Toolbox, die alle Komponenten enthält, die man für eine grafische Oberfläche braucht. Das "Designfenster" (5) in dem die GUI erstellt wrid, verhält sich genauso wie jedes andere Fenster. So kann man seine Oberfläche direkt live testen. Im Eigenschaften-Fenster lassen sich zu jeder platzierten Komponente Einstellungen wie Ausrichtung oder Beschriftung vornehmen (2). Unter dem Reiter "Packing" (3) lässt sich das Layout bestimmen. Unter "Signale" (4) lassen sich Events definieren und diesen Namen geben.

Hat man die Oberfläche soweit fertig, speichert man das Projekt ab. Als Resultat erhält man zwei Dateien. Eine projektname.glade und eine projektname.gladep. Die erste enthält die Beschreibung der GUI, und sieht ungefähr so aus (Ausschnitt!):

<glade-interface>
<widget class="GtkWindow" id="w1">
  <property name="visible">True</property>
  <property name="title" translatable="yes">Beispiel</property>
  <property name="type">GTK_WINDOW_TOPLEVEL</property>
  <property name="window_position">GTK_WIN_POS_NONE</property>
  <property name="modal">False</property>
...

Diese Datei lässt sich nun mit wenigen Zeilen Code in einer Ruby-Applikation einbinden. Wem das noch zu viel Arbeit ist, kann auch das Skript "ruby-glade-create-template" verwenden, um sich ein lauffähiges Grundgerüst zu generieren. Der so generierte Code sieht ungefähr so aus:

require 'libglade2'

class SampleGlade
  include GetText
  attr :glade
  
  def initialize(path_or_data, root = nil, domain = nil, localedir = nil, flag = GladeXML::FILE)
    bindtextdomain(domain, localedir, nil, "UTF-8")
    @glade = GladeXML.new(path_or_data, root, domain, localedir, flag) {|handler| method(handler)}
    
  end
  
  def on_button1_clicked(widget)
    puts "on_button1_clicked() is not implemented yet."
  end
end

if __FILE__ == $0
  SampleGlade.new("sample.glade", nil, "Sample App")
  Gtk.main
end

Das so erstellte GUI-Programm ist direkt unter Windows und Linux lauffähig (GTK, Ruby und die Ruby-Gnome2-Lib vorausgesetzt). Genau so stelle ich mir Oberflächenprogrammierung vor! Sauber, einfach, Plattformunabhängig und komplett vom restlichen Code getrennt.

Toll wäre es nun, wenn sich jemand die Mühe machen würde und GladeXML in Java und Swing nachbilden würde. So könnte man ein und die selbe GUI in einem Java-Programm und in einem Ruby-Programm nutzen!

1 Kommentar, delicious bookmark del.icio.us


JavaScript IDE

vom 1. April 2008

JavaScript IDE Spket

Da ich in letzter Zeit wieder mehr mit JavaScript arbeite, habe ich mich (mal wieder) nach einer schlanken und dennoch starken IDE für die Sprache umgeschaut. Fündig wurde ich auf der Mozilla-Seite. Das Programm heißt Spket IDE und basiert auf Tiny Eclipse, einer abgespeckten Version der Eclipse-Umgebung.

Die IDE hat die von Eclipse bekannten Shortcuts und fühlt sich ziemlich ähnlich an, auch wenn einige Funktionen fehlen. Der Outline-Browser zeigt ähnlich wie in anderen Sprachen einen Baum der Klassen/Objekte und Funktionen an. Der Editor macht einen automatischen Syntax-Check und warnt vor Fehlern.

Ein weiteres Programm das bei der Erstellung von JavaScript-Code nicht fehlen sollte ist SpiderMonkey. Dies ist die JavaScript Engine die in Firefox und einigen anderen Anwendungen eingebettet wurde. SpiderMonkey (im Ubuntu-Universum unter spidermonkey-bin zu finden) kann entweder als interaktive Shell (mit dem Befehl "js") genutzt werden oder zu Ausführung eines Scripts (js -f scriptname.js). Da JavaScript im Browser eigentlich keine Ausgabe hat, bietet der Interpreter einige Zusatzfunktionen wie print() oder load() um mit der Console zu Kommunizieren bzw. andere JavaScript-Dateien nachzuladen (was man im klassischen Sinne über einen HTML-Tag macht).

Mit diesem Interpreter kann man nun wunderbar (automatisch) UnitTests durchführen, ohne den Browser zu starten und sich das Ergebnis in Firebug, der JavaScript Fehlerconsole oder eingebettet im HTML anzuschauen. Ein tolles Framework hierzu ist JSUnit, welches schon Optionen für den Betrieb mit SpiderMonkey bereitstellt.

3 Kommentare, delicious bookmark del.icio.us


F# Tutorial #2

vom 13. February 2008

Andreas hat in seinem Blog ein interaktives F#-Tutorial gestartet, in dem er jedes mal eine kleine Aufgabe am Ende stellt. Da ich mich sowieso sehr gerne mit neuen (und alten) Programmiersprachen und -Konzepten beschäftige, kommt mir dies sehr gelegen (vielen Dank für die Tutorials!). (F# (F Sharp) ist eine funktionale Programmiersprache von Microsoft, die auf dem .NET-Framework aufsetzt. Compiler gibt es für Windows (Visual Studio) und Mono (Linux/OSX.))

Hier nun meine Lösung für die erste Aufgabe (Fakultätsfunktion "!" definieren) vom zweiten Blogeintrag:

#light

let rec ( ! ) (n : int) =
    if (n = 1) then n
    else n * !(n-1)

let endresult = !5
printfn "Die fakultaet von 5 ist %i" endresult

1 Kommentar, delicious bookmark del.icio.us


PHP am Limit?

vom 5. December 2007

Ich hab mich erst schon gefreut, das PHP endlich Namespaces implementiert. Doch auf der Mailingliste ging es die letzten Tage ziemlich rund und nun gibt es ein zusammengefasstes RFC mit dem Titel "Droping Namespaces". Nur dumm das der komplette Code von Phing schon auf Namespaces umgestellt wurde:

Hey guys, Just a heads up, but there is an RFC to pull "namespaces" from PHP 5.3. Personally, give how broken the implementation is, I would be for pulling them until someone just ports Python's module code to PHP. At any rate, thought it would be worth noting here since phing's trunk is using the current NS implementation.

So langsam habe ich das Gefühl, das die Codebase von PHP an ihrem Limit angelangt ist. Die Altlasten machen den Programmierern schon seit Jahren zu schaffen, gerade wenn es um wirkliche Neuerungen geht. Je mehr ich mich mit Ruby beschäftige desto weniger möchte ich mit PHP programmieren, allerdings ist das die einzige Sprache, die jeder Webserver spricht. Ich würde sofort PHP links liegen lassen, wenn da nicht die Kunden wären, die eine Dynamische Webseite wollen, die auf ihrem 2,99 EUR Webspace-Paket läuft. Wenn jemand einen günstigen Ruby/Python/... Hoster kennt der unter 3 EUR/Monat Webspace-Pakete anbietet wäre das schon mal ein erster Schritt.

2 Kommentare, delicious bookmark del.icio.us