Willkommen im Jahr der Astronomie!
vom 2. January 2009Etwas spät, aber noch im Rahmen: Ich wünsche euch allen ein gutes, gesundes und erfolgreiches Jahr 2009! Mögen alle Vorhaben und Wünsche gelingen und in Erfüllung gehen.
Das Jahr 2008 war für mich ein tolles Jahr. Ich habe die Blogpostings von letztem Jahr durchgesehen und ein paar persönliche "Highlights" herausgepickt. Viel Spaß damit:
Jannuar: Das ISS-Modul Collumbus ist nach mehreren Fehlversuchen bereit und wird Tage später vom deutschen Astronaut Thomas Reiter und seinen Kollegen installiert.
Februar: Ich wurde Opfer eines von Microsoft veranstaltetem Alternate Reality Game und wurde durch eine Sanduhr eingeladen. Mir ist bie heute nicht klar, warum sie mich - ein Linux-Fan - ausgewählt hatten. :-)
März: Ich wechselte den vServer-Anbieter und den Web-Server. Hier musste ich einiges wieder zurechtbiegen.
April: Über das Fach AKSE hatte ich mich intensiv mit JavaScript beschäftigt und das ein oder andere coole Feature entdeckt.
Mai: zensiert
Juni: Mein Bruder und ich kauften uns eine Playstation 3 und spielten Nächte lang Metal Gear Solid 4. Mein Bruder ist mittlerweile ausgezogen.
Juli: Mein GTD, endlich mal zu Papier gebracht.
August: Keine Aufzeichnungen vorhanden
September: Erster Programmierwettbewerb, bei dem 8(!) Personen mitgemacht haben.
Oktober: Mittem im Streß ...
November: Blut gespendet.
Dezember: Mein erster Krankenhaus"besuch" als Patient.
Was habt ihr im letzten Jahr erlebt? Und noch wichtiger: Was für Ziele habt ihr euch für dieses Jahr gesteckt?
3 Kommentare,
del.icio.us,
Etwas spät, aber noch im Rahmen: Ich wünsche euch allen ein gutes, gesundes und erfolgreiches [Jahr 2009][0]! Mögen alle Vorhaben und Wünsche gelingen und in Erfüllung gehen.
Das Jahr 2008 war für mich ein tolles Jahr. Ich habe die Blogpostings v
Programmierwettbewerb, Runde 2
vom 1. January 2009Da der erste Programmierwettbewerb so großen Andrang hatte und ich schon mehrfach angesprochen wurde, ob es denn nicht einen zweiten Teil geben wird, habe ich mir wieder eine kleine Programmieraufgabe ausgedacht. Ideal also, um die neuen Vorsätze fürs neue Jahr auf die Probe zu stellen und das Wochenende "sinnvoll" zu nutzen :-)
Die Aufgabe (ca. 2-4 Stunden Aufwand)
Es gilt, einen Chat-Client zu programmieren, der mit einem von mir gestellten Chat-Server kommunizieren kann. Die Programmiersprache ist frei wählbar, solange sie unter Linux, OSX und Windows interpretiert oder compiliert werden kann. Es dürfen alle eingebauten Features der gewählten Programmiersprache verwendet werden, jedoch keine externen "fertigen Lösungen". Eine Socket-Lib, GUI-Toolkit o.ä. ist natürlich erlaubt.
Der Chat-Client sollte eine Eingabemöglichkeit haben, um Befehle und Chatnachrichten an den Server zu schicken. Zudem sollte er einen Textbereich o.ä. besitzen, um Statusnachrichten und Chat-Nachrichten darzustellen. Eine sich aktualisierende Liste mit den aktuellen im Chat befindlichen Usern ist nicht notwendig, macht aber mehr her :-)
Bewertet werden: Features, Codedesign, Nutzung der Sprachfeatures und Usability.
Der Server
Der Chat-Server ist unter dem Host "aaron-mueller.de" mit dem Port "2009" erreichbar. Der Server kann mit Befehlen - ähnlich wie ein IRC-Server - gesteuert werden. Sendet der Client bspw. den Befehl "/quit" an den Server, trennt dieser die Verbindung zum Client. Weitere Befehle sind "/nick NeuerBenutzername" um den Benutzernamen zu ändern und "/users" um alle im Chat befindlichen User auszugeben. Alle Statusnachrichten, die vom Server kommen, haben einen *(Stern) am Anfang. Die Kommunikation läuft über PlainText-Nachrichten.
Am besten ist es, wenn man sich mit dem Server zuerst mit Telnet oder einem anderen Tool vertraut macht, und dann an die Programmierung geht.
Ziel
Jeder der am Sonntag den 11.01.2009 um 19:00 Uhr mit seinem eigenen Chat-Client in den Chat kommt und seinen Code per E-Mail an mail@aaron-mueller.de abgibt, hat die Aufgabe gemeistert. (Wer an dem Sonntag nicht kann, macht natürlich trotzdem mit :-) Der Gewinner erhält von mir eine Überraschung!
Ich freue mich über jeden Beitrag! Viel Erfolg!
Nachtrag: Ich habe noch ein paar kleinere Bugs im Server korrigiert. Danke an Marc und Florian fürs Testen und Bugs finden.
5 Kommentare,
del.icio.us,
Da der erste Programmierwettbewerb so großen Andrang hatte und ich schon mehrfach angesprochen wurde, ob es denn nicht einen zweiten Teil geben wird, habe ich mir wieder eine kleine Programmieraufgabe ausgedacht. Ideal also, um die neuen Vorsätze fürs
Funktionale- und Integrationstests mit Selenium
vom 7. December 2008Webseiten testen macht kein Spaß. Vor allem dann nicht, wenn man ein zweiseitiges Formular schon zum 25sten Mal ausgefüllt hat und immer noch ein Bug drin ist. Da ich gerne Dinge so weit es geht automatisiere, habe ich mich vor etwas längerer Zeit (diesen Blog-Post wollte ich schon vor ca. einem Jahr erstellen :) nach Test-Frameworks für Webseiten umgesehen.
Es gibt unzählige, allen voran SimpleTest und PHPUnit. Allerdings haben all diese Frameworks ein Problem: Die Testfälle sind dermaßen aufwendig zu erstellen, dass es bei größeren und komplexeren Seiten einfach zu zeitaufwändig und langweilig ist. Ein Beispiel hierfür aus dem Tutorial von SimpleTest:
function testWeAreTopOfGoogle() {
$this->get('http://google.com/');
$this->setField('q', 'simpletest');
$this->click("I'm Feeling Lucky");
$this->assertTitle('SimpleTest - Unit Testing for PHP');
}
Um diesen Vorgang zu beschleunigen, habe ich mich weiter auf die Suche gemacht und bin dann eines Tages auf Selenium gestoßen. Hiermit lassen sich die Tests viel einfacher erstellen.
Selenium besteht aus mehreren Komponenten. Zum Einen hat man zum Erstellen der Tests ein Firefox-Plugin, mit dem Tests einfach aufgezeichnet werden können:

Man drückt auf "Record" und beginnt mit seinem Testcase. Asserts lassen sich über das Kontextmenü hinzufügen. Will man beispielsweise sicherstellen, dass auf der aktuellen Seite eine Wortkombination erschienen ist, markiert man diese und wählt im Kontextmenü "assertTextPresent". Auch Formulareingaben, Seitensprünge usw. werden aufgezeichnet. Will man kompliziertere Testfälle aufsetzen, kann man die aufgenommenen Schritte nachträglich bearbeiten und erweitern. Reichen die Befehle von Selenium nicht aus, kann immer noch auf JavaScript ausgewichen werden.
Hat man seinen Testfall fertig, kann man diesen auf unterschiedliche Weise abspeichern. Zum Einen kann man den Testfall als simple HTML-Tabelle abspeichern, das ist das Standardformat von Selenium. Eine weitere Möglichkeit ist das Konvertieren in einer der vielen Programmiersprachen (Java, PHP, Ruby, Python, ...), so dass man am Ende ein ähnliches Ergebnis wie beim SimpleTest hat, nur mit viel weniger Tipparbeit. Dieses Script kann dann einfach ausgeführt werden.
Ein weiteres Modul von Selenium ist die Umgebung, die die aufgenommenen Testfälle starten kann. Hier gibt es wiederum mehrere Möglichkeiten. Die Serverkomponente ist für den "Hausgebrauch" relativ umbedeutend, und spielt erst eine entscheidende Rolle, wenn wirklich bei jedem Code-commit alle Testfälle ausgeführt werden sollen. Die einfachere Variante ist der Test-Runner, der komplett in JavaScript geschrieben ist, und somit direkt im Browser läuft. Das hat den Vorteil, dass man nicht an einen bestimmten Browser gebunden ist und somit seine Testfälle auf jedem beliebigen Browser testen kann, der einigermaßen neues JavaScript unterstützt.

Ich habe mir, um den Testvorgang weitgehend zu automatisieren, eine Subdomain angelegt, die - wenn ich sie aufrufe - automatisch alle Tests durchlaufen werden. So kann ich in regelmäßigen Abständen und bei größeren Code-Änderungen immer prüfen, ob noch alles tut so wie es soll.
SetUp und TearDown?
Ein Problem gibt es dennoch mit Selenium: Wenn man beispielsweise ein Formular auf ungültige Eingaben testet, werden diese Testdaten evtl. in die Datenbank geschrieben und bleiben auch nach Beendigung des Testlaufs bestehen. Dies ist natürlich nicht gewollt. Eine SetUp- und eine TearDown-Methode muss her. Leider gibt es so etwas in Selenium nicht, da das Framework logischerweise die eigene Datenbank nicht kennt und auch keinen Zugriff darauf hat.
Um dieses Problem zu umgehen, habe ich mir ein kleines PHP-Skript geschrieben, dass diese Funktionalität "nachrüstet". Abgespeckt sieht es ungefähr so aus:
define('SECRET', '12345xyz');
if (isset($_GET['action'])) {
$key = isset($_GET['secret']) ? $_GET['secret'] : '';
if ($key == SECRET) dispatch($_GET['action'], $_GET);
else die('Wrong secret, sorry.');
}
function dispatch($action, $params) {
$preparator = new SeleniumPreparator();
if (method_exists($preparator, $action)) {
PicoraActiveRecord::connect(CONNECTION_STRING);
output($preparator->$action($params));
} else die("No method $action found.");
}
class SeleniumPreparator {
public function setup() {
// Make temp-Entry visible
$entry = $this->getTestentry();
$entry->display = 1;
$entry->allow_comments = 1;
$entry->save();
return true;
}
public function teardown() {
$entry = $this->getTestentry();
$entry->display = 0;
$entry->allow_comments = 0;
// Clear all comments and annotations
$comments = Comment::findAllByField('Comment', 'entry_id', $entry->id);
foreach ($comments as $c) {
$c->delete();
}
$entry->annotations = '[]';
$entry->save();
return true;
}
public function activateAllComments() {
$entryID = $this->getTestentry()->id;
$comments = Comment::findAllByField('Comment', 'entry_id', $entryID);
foreach ($comments as $c) {
$c->approved = 1;
$c->save();
}
return true;
}
private function getTestentry() {
return Entry::findByField('Entry', 'url_name', 'selenium-testentry');
}
}
Aufgerufen wird das Skript mit test_setup.php?secret=12345xyz&action=setup, um beispielsweise die Setup-Methode aufzurufen, die den Testeintrag sichtbar macht. Diesen Aufruf kann man natürlich auch mit Selenium machen. Mit dieser Technik lassen sich dann auch administrative Aufgaben wie das Aktivieren von Kommentaren durchführen, ohne manuell einzugreifen.
Ich hoffe, dass durch Selenium mehr Web-Developer ihre Webseite testen, denn das hat das Web dringend nötig.
6 Kommentare,
del.icio.us,
Webseiten testen macht kein Spaß. Vor allem dann nicht, wenn man ein zweiseitiges Formular schon zum 25sten Mal ausgefüllt hat und immer noch ein Bug drin ist. Da ich gerne Dinge so weit es geht automatisiere, habe ich mich vor etwas längerer Zeit (die
Bääm!
vom 4. December 2008Da lag ich also. Total am Ende, vollgepumpt mit Medika, gelb wie die Simpsons, die Infusion im Arm und Reste von klebrigem Ultraschall-Gel auf dem Bauch. Unfähig aufzustehen, lag ich in einem kleinen sterilen Kämmerlein in der Notaufnahme und wartete auf den im Gesicht mit Akne übersähten Doktor. Er fragte mich unzähliche Dinge - das glaubte ich zummindest - und fingerte an mir herum. Irgend wann ging er wieder und ein Zivi brachte mich in die "innere". Das ist der Teil des Krankenhauses, in dem Leute liegen, die keine Mägen haben oder die auf ihre Magen/Darm-Spiegelung warten um mit Sonden nach Tumoren zu suchen.
Ich schlief eine ganze Weile und als ich aufwachte lag ich zwischen zwei älteren Männern. Der ohne Magen war eigentlich recht still, der andere war offensichtlich Kapitän von irgend einem Kutter im Norden und musste immer reden. Ich war richtig froh, als er dann mit Kotzen und Scheißen beschäftigt war, so konnte ich wenigstens etwas schlafen. Ich schnappte ab und an Fetzen auf: "Ihr Stuhl muss Kamilletee-Farben sein, sonst bringt das nichts.". "Aber ich trink doch gar kein Tee.". "Soll ich ihnen einen machen machen, damit sie selbst vergleichen können?",... Irgend wie tat er mir dann wieder leid.
Mitten in der Nacht wachte ich schweißgebaded auf, scheiß Drüsenfieber. Der Mann ohne Magen machte eigenartige "Plop"-Geräusche im schlaf, die ich nicht deuten konnte. Ich dachte eine ganze Zeit lang, die kämen von drausen, war aber nicht so. Der Kapitän saß mal wieder auf dem Klo und ballerte die Schüssel voll. Zu dumm, dass die Lüftung ausfiel, denn es roch ziemlich mies. Ab diesem Tag ging ich im Gang aufs Klo.
Vier Tage lang lag ich neben den beiden. Jeden Morgen schraubte eine Schwester zwei Bäutel Infusion an meinen "Anschluss-Stutzen", den ich bei der Einlieferung verpasst bekommen hatte. Erinnerte mich irgend wie an Matrix. Ausser Frühstück um sieben, Mittagessen um 12 und Abendessen um fünf passierte dann nicht mehr viel. Die ersten Tage schlief ich fast durch, die letzten beiden Tage waren sterbenslangweilig. Das einzige, was ich vor mir sah, war eine Kopie eines Gemäldes von Georgia O'Keeffe "One Hundred Flowers". Nach 20 Minuten konnte ich es nicht mehr sehen. Ich war so froh, als ich mich mehr oder weniger selbst entlassen konnte. Meine Entlassungspapiere waren noch nicht fertig, aber ich ging trotzdem. Nur raus aus dem Laden.
Epstein Barr Visus. Quelle: Wikipedia
Wer sich nun wundert, warum ich nun im Krankenhaus war und warum ich mich die letzten Wochen nicht mehr gemeldet habe, sei gesagt: Ich habe mich (warscheinlich im Bus oder in der Mensa) mit dem Epstein-Barr-Virus infiziert, für das es keinerlei Medikamente gibt. Da ich zur gleichen Zeit ein Angina hatte und nicht richtig schlucken/trinken konnte, fehlte der Leber Flüssigkeit und ich brauchte demzufolge Infusionen.
Das Virus ist ansteckend und ich werde deshalb noch eine Woche daheim verbringen müssen. Glücklicherweise funktioniert mein Hirn wieder einigermaßen, so dass ich wieder nützliche Dinge tun kann, wie zum Beispiel Bloggen. :)
7 Kommentare,
del.icio.us,
Da lag ich also. Total am Ende, vollgepumpt mit Medika, gelb wie die Simpsons, die Infusion im Arm und Reste von klebrigem Ultraschall-Gel auf dem Bauch. Unfähig aufzustehen, lag ich in einem kleinen sterilen Kämmerlein in der Notaufnahme und wartete au
Angezapft und ausgeblutet
vom 4. November 2008Ich war heute das erste mal Blut spenden. Einen halben Liter habe ich mir mit Gewalt aus dem Körper gepumpt, hätte nicht gedacht, dass das so schnell geht. Bea hatte auch ihr Blut fließen lassen.
War eigentlich kein großes Ding, hab mir das irgend wie spektakulärer vorgestellt. Lob an das DRK, die Ärzte und Helfer waren alle sehr zuvorkommend, kompetent und Nett.
7 Kommentare,
del.icio.us,
Ich war heute das erste mal Blut spenden. Einen halben Liter habe ich mir mit Gewalt aus dem Körper gepumpt, hätte nicht gedacht, dass das so schnell geht. [Bea](http://blog.beatrice-fischer.de/index.php?/archives/3-Blutspende,-meine-Erste.html) hatte
Kompetenzzentrale
vom 31. October 2008Da mich kb genötigt hat (ok, eigentlich hab ichs ihm ja versprochen), ein paar Bilder von meiner "Kompetenzzentrale" zu zeigen: Hier also mein Arbeitszimmer:




4 Kommentare,
del.icio.us,
Da mich kb [genötigt](http://zerfall.com/2008/10/29/kompetenzzentrale/) hat (ok, eigentlich hab ichs ihm ja versprochen), ein paar Bilder von meiner \"Kompetenzzentrale\" zu zeigen: Hier also mein Arbeitszimmer:
:
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)
(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.

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
6 Kommentare,
del.icio.us,
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):
Aar
Programmierwettbewerb
vom 16. September 2008Da 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,
del.icio.us,
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
AJAX ohne AJAX
vom 4. September 2008Vorgestern bin ich auf ein Problem gestoßen, an das ich noch nicht gedacht hatte: Es ist nicht möglich, eine Datei mit einem XMLHTTPRequest zu versenden. Grund dafür ist JavaScript, welches in der Browser-Sandbox läuft und nicht auf lokale Dateien zugreifen kann.
Doch wie hat das GMail gelöst? Hier werden Dateianhänge nach dem Auswählen ohne ein Neuladen der Seite hochgeladen. Nach ein paar Experimenten und etwas Hilfe von ajaxpatterns.com fand ich ein paar interessante Browsereigenschaften, mit denen man GET- und POST-Requests auch ohne das XMLHTTPRequest-Interface absetzen kann.
Der iFrame Tag
Die Idee ist Folgende: Wenn per JavaScript ein GET-Request abgesetzt werden soll, wird dynamisch ein iFrame erzeugt und dessen src-Attribut auf eine URL gesetzt, die dann aufgerufen wird. Die GET-Parameter können dann wie gewohnt in Form von ?p1=val1...&pn=valn an die Adresse angehängt werden. Um die Server-Response abzufangen, bekommt das iFrame eine onLoad-Funktion verpasst, die den Inhalt des iFrames extrahiert, weiterverarbeitet und das iFrame wieder löscht. Das iFrame dient hier als eine Art Kontainer zum Senden und Empfangen.
<script type="text/javascript">
function $(id) { return document.getElementById(id); }
function call() {
var d = document.createElement('div');
d.innerHTML =
'<iframe id="iFrame" style="display: none;" ' +
'onLoad="callback()"></iframe>';
document.body.appendChild(d);
$("iFrame").src = "server.php?val=" + $("var").value;
}
function callback() {
$("result").innerHTML =
$("iFrame").contentDocument.body.innerHTML;
$('iFrame').parentNode.removeChild($('iFrame'));
}
</script>
<p id="result"></p>
<form onSubmit="call(); return false;">
<input type="text" id="var" />
<input type="submit" />
</form>
Mit dieser Technik hat man einen fast vollwertigen Ersatz für das XMLHTTPRequest-Interface. Manche Frameworks nutzen dieses Feature, um auch ältere Browser zu unterstützen.
Doch was tun, wenn der Browser keine iFrames unterstützt? Hier kann man sich einer ähnlichen Technik bedienen: Sobald man einem img-Tag das src-Attribut verpasst, versucht der Browser das Bild zu laden. Hier lässt sich wie im iFrame-Beispiel auch eine URL angeben die zu einem Script führt und Parameter anhängen.
var img = document.createElement('img');
img.setAttribute('src', 'server.php?p1=val1');
Dies ist natürlich ein one-way-ticket. Nach dem GET-Request kann der Server nur noch mit einem Bild antworten.
Und was ist mit POST?
Hier gibt es einen weiteren Trick, POST-Requests an den Server zu senden. Beim dynamischen Erstellen des iFrames fügt man diesem ein Formular hinzu, welches die Parameter als input-Tags beinhaltet. Mit JavaScript sendet man dann das Formular ab und extrahiert wiederum die Server-Response aus dem iFrame.
Einfacher geht es mit dem target-Attribut des form-Tags. Hier kann man ein iFrame als Target angeben. Das iFrame dient hier wiederum als Kontainer für das Abfangen der Antwort vom Server. Im folgenden wird wird die ausgewählte Datei auf den Server geladen:
<script type="text/javascript">
function $(id) { return document.getElementById(id); }
function call(form) {
var d = document.createElement('div');
d.innerHTML =
'<iframe id="iFrame" name="iFrame" ' +
'style="display: none;" ' +
'onload="callback()"></iframe>';
d.setAttribute('id', 'repository');
document.body.appendChild(d);
return false;
}
function callback() {
$('result').innerHTML =
$('iFrame').contentDocument.body.innerHTML;
$('repository').parentNode.removeChild($('repository'));
}
<p id="result"></p>
<form method="post" enctype="multipart/form-data"
action="server.php" target="iFrame"
onSubmit="call(this);">
<input type="file" name="file" />
<input type="submit" />
</form>
Auf Serverseite wird die Datei nur noch entgegengenommen und in das richtige Verzeichnis geschoben.
foreach ($_FILES as $f) move_uploaded_file($f['tmp_name'], "upload/".$f['name']);
Warum der Umstand?
Eigentlich erstaunlich, dass sich Web-Entwickler immer wieder solche dreckige Hacks einfallen lassen müssen, um ans Ziel zu kommen. Der Browser ist mittlerweile zum "Plattformübergreifenden Betriebssystem" mutiert, doch die Programmierung ist alles andere als komfortabel.
Mit dem neuen Google Browser Chrome kommt hoffentlich wieder etwas frischen Wind in den Browser-Markt. Ich warte derweil geduldig auf eine Linux-Version von Chrome.
4 Kommentare,
del.icio.us,
Vorgestern bin ich auf ein Problem gestoßen, an das ich noch nicht gedacht hatte: Es ist nicht möglich, eine Datei mit einem [XMLHTTPRequest][4] zu versenden. Grund dafür ist JavaScript, welches in der Browser-Sandbox läuft und nicht auf lokale Dateie
Mein GTD
vom 30. July 2008Seit knapp 2 Jahren geht der GTD (Getting Things Done) Hype um. Ich laß natürlich die Klassiker wie "The 7 Habits of Highly Effective People", "Getting Things Done" von David Allen und "Getting Real" von 37Signals. Auch hab ich mir die RSS-Feeds von Blogs zu diesem Thema wie zenhabits.net, lifehacker.com, diyplanner.com und dumblittleman.com abboniert. Doch schon am Anfang viel mir auf, dass ich gewisse Dinge schon seit Jahren mache, auch wenn mir dies nie so wirklich bewusst waren. Aber ich habe auch einiges dazugelernt und ein paar Ideen in meinem eigenen "GTD-System" adaptiert.
Da ich des Öfteren gefragt werde, was ich denn so alles in mein Notizbuch schreibe, warum ich einen eeePC oder ein Whiteboard brauche oder wo ich meine Links speichere, will ich hier mal mein privates "StuffToDone"-System vorstellen. Vielleicht findet der ein oder andere ein paar Ideen oder weiß noch ein Verbesserungsvorschlag. Ich würde mich freuen.
Das System
Wichtig war und ist mir, dass ich all die Informationen, die täglich auf mich herabprasseln, irgend wo sammeln kann. Am Besten an zentralen Orten, auf die ich von überall Zugriff habe, um diese Informationen zu verarbeiten. Zudem ist es mir wichtig, die verarbeiteten Informationen ebenfalls von überall abzurufen, wenn ich sie brauche.
Ich habe grundlegend 6 "Container", in denen ich Informationen sammle. Diese Container "leere" ich in regelmäßigen Abständen.
Das sind zum einen das Firefox-Plugin "ReadItLater". Damit sammle ich alle möglichen URLs, die ich in irgend einer Form verarbeiten will. Diese URLs können von den unterschiedlichen Stellen kommen, sei es von einer Newsseite, von einem Freund über IRC oder aus einem RSS-Feed. Längere Texte, die ich im Moment nicht lesen kann landen auch hier. Diese Link-Liste arbeite ich jeden (oder jeden zweiten) Tag ab, und entscheide was mit ihnen passiert. Wenn es eine Webseite ist, die ich vielleicht später noch einmal suchen werde, landen in meinem del.icio.us Account (eine online Bookmarkverwaltung). Wenn ich die URL in 2 Wochen noch einmal brauche, speichere ich sie in meinem Kalender (bei Google). Ansonsten lese ich sie und lösche sie dann aus der Liste. Wenn ich hier immer noch keine Zeit habe, mich näher damit zu beschäftigen, speichere ich sie in meinem privaten Wiki auf der "incubator"-Seite. Siese Seite schaue ich immer an, wenn es mir langweilig ist oder ich für ein paar Stunden nichts zu tun habe.
Ein weiterer "Container" ist mein E-Mail Postfach, das ich von überall per IMAP abrufen kann. Hier sammle ich in meiner Inbox E-Mails von allen meinen Konten, die Newsgroups sind extra. Mein Server teilt mir über diesen "Kanal" über Status E-Mails auch mit, wenn was nicht stimmt. Meine Inbox muss immer leer sein, da bin ich sehr penibel. Gelesene und beantwortete E-Mails werden sofort an ihren Platz verschoben, Spam wird natürlich gelöscht. Wenn eine E-Mail noch auf etwas wartet (z.B. die Bestellbestätigung oder eine E-Mail die mich an etwas erinnert, das ich machen muss) landet diese in dem Ordner @ACTIONS. Diesen Ordner schaue ich für gewöhnlich mehrmals am Tag an und arbeite die angefallenen E-Mails ab.
Mein PC-Desktop ist eine weitere Sammelstelle für alle eingehenden Dateien. Diese können vom Internet, USB-Sticks oder von meinem Handy kommen. Alles landet zuerst einmal auf meinem Desktop (vorausgesetzt es hat nicht von Anfang an einen bestimmten Platz). Von hier aus wird es "verarbeitet" und dann entweder aufs RAID verschoben, hochgeladen oder gelöscht. Allerdings habe ich keinen überfüllten Desktop, hier geht es mir ähnlich wie mit meiner E-Mail Inbox. Alles was da länger als 2 Tage rumliegt wird gelöscht.
Wenn ich nicht zuhause bin oder kein Notebook zur Hand habe, schreibe ich meine Ideen, Gedanken, Termine oder lustige Zitate in ein kleines Notizbuch. Wenn ich zuhause bin, verwende ich meist mein Whiteboard für die Gedankenblitze und Telefonnotizen. Dies sind eigentlich nur "Zwischencontainer", denn die Informationen, die ich mir hier aufschreibe, übertrage ich dann meist in den Kalender, bookmarke sie in del.icio.us, schreibe es in mein Wiki oder blogge darüber. Ich habe lange nach einer digitalen Lösung gesucht, doch das schien mir am Ende doch das einfachste und schnellste. Kein booten, kein warten, kein klicken, kein Sync-Kabel.
Für alles Andere habe ich ein Ablagefach auf meinem Schreibtisch stehen, in das ich all den Kleinkram reinwerfe, der im laufe des Tages so anfällt. Die ungeöffnete Post, einen herausgerissenen Zeitungsausschnitt, die Bedienungsanleitung für das neue Telefon oder die leeren Batterien als Erinnerung, neue zu kaufen. Dieses Ablagefach ist zugleich mein "Ausgang", also alles was wieder raus muss. Da ich nicht so viel Zettelwirtschaft habe, funktioniert das auch ganz gut, doch wenn es mal mehr wird, wird es unübersichtlich.
Wie man sieht, habe ich grob drei Ablageplätze (del.icio.us, Kalender und Wiki) für Informationen die später nochmal gebracht werden. Das reicht mir völlig. Ich habe auch kein modernes Hängefachregister oder sonstige fancy GTD-Tools oder Software. Ich habe zwar alles mal ausprobiert, doch nichts passte wirklich. Mit dem Wiki (und einigen Plugins wie ToDo-Listen und Prioritäten), dem Google-Kalender und der Bookmarkverwaltung bin ich bis jetzt sehr zufrieden und klappt eigentlich ganz gut.
Was ich bis heute nicht wirklich hinbekommen habe, ist eine zentrale Kontaktverwaltung. Ich habe alles durch, von abook bis LDAP, aber mit nichts war ich zufrieden. Momentan verwende ich mein IMAP-Account dafür. In einem Ordner speichere ich für jeden Kontakt eine E-Mail mit einem Kolab-Groupware-Object. Das lässt sich gut mit dem Thunderbird-Plugin "Sync Kolab" mit dem lokalen Adressbuch synchronisieren. Keine optimale Lösung, aber besser als nichts. Wer eine bessere Idee hat, nur her damit.
Ich bin kein GTD-Fanatiker (mehr), aber dieses Systme funktioniert bei mir schon länger und ich bin wirklich besser organisiert als früher.
3 Kommentare,
del.icio.us,
Seit knapp 2 Jahren geht der GTD (Getting Things Done) Hype um. Ich laß natürlich die Klassiker wie \"The 7 Habits of Highly Effective People\", \"Getting Things Done\" von David Allen und \"Getting Real\" von 37Signals. Auch hab ich mir die RSS-Feeds v
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18