Technik, Gothic und Anderes

Technik ist Spiel, Gothic ist ernst und Zeit hat man zuviel

Archiv

PHP: NHL-Spielerstatistiken in Excel ausgeben für Fantasy-Leagues

Geschrieben von admin am 15. November 2007

Das Problem

HockeyIch habe mir sagen lassen, dass es auch hier bei uns Leute gibt die in einer Fantasy League mitspielen. Irgendwie werden da virtuelle aber real existierende Sportler gezogen und verkauft und nach einem speziellen Algorithmus werden Punkte verteilt, die abhängig von den tatsächlichen Resultaten sind, die dieser Spieler erzielt. Nun, auf jeden Fall wird da viel mit Statistiken gewurstelt. Ein Fantasy League-Mitglied ist an mich herangetreten, weil er die online publizierten Resultate der Skaters und Goalies der NHL in einem Excel haben wollte.

Zuerst dachte ich, das sei ein Fall für Dapper. War es aber nicht, da auf der Webpage reine Text-Exporte waren.

Die Erfahrung hat gezeigt, dass die Form der Ausgabe auf diesen Seiten oftmals ändert und man sich auf so wenig wie möglich verlassen sollte.

Die Felder dieser Tabelle haben keine Trennzeichen, sondern eine feste Breite und sind links- oder rechtsbündig formatiert.

Einige Punkte der Lösung

Download der Webseite in eine Variable

Zuerst muss man mal an die Seite rankommen. In PHP den Inhalt einer URL in einen String laden ist in der Theorie relativ einfach. In der Praxis jedoch ist aus Sicherheitsgründen der Zugriff auf URLs für die file* Funktionen oftmals eingeschränkt. Nunja, dann wird halt ein cURL-Fallback mit eingebaut, so dass wir auf jeden Fall in $page ein Array aus den Zeilen der HTML-Datei haben:

if(!($page = @file($url))) {
    $ch = curl_init();
    curl_setopt ($ch, CURLOPT_URL, $url);
    curl_setopt ($ch, CURLOPT_USERAGENT, 'User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.6) Gecko/20070723 Iceweasel/2.0.0.6 (Debian-2.0.0.6-0etch1+lenny1)');
    curl_setopt ($ch, CURLOPT_HEADER, 0);
    curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);
    $result = curl_exec ($ch);
    curl_close ($ch);
    $page=explode("\n",$result);
  }

Überschriften auslesen

Ich lese die Tabellenübberschriften mit einem regulären Ausdruck aus (man beachte dass die Sternchen sich der Formatierung (links/rechts) anpassen), aus dem danach die Spaltenbreiten gelesen werden können. Aus den Spaltenbreiten wiederum kann ein regulärer Ausdruck gebaut werden über den auf die Werte zugegriffen werden kann.

Beispielsweise steht auf der Webpage:

NAME                                   POS GP   G    A PTS SOG +/- PIM PPG SHG

daraus lese ich mit folgendem Regexp die Feldbreiten:

'@^(NAME *)(POS *)(GP)( *G)( *A)( *PTS)( *SOG)( *\\+/\\-)( *PIM)( *PPG)( *SHG)$@'

Damit baue ich dynamisch einen neuen regulären Ausdruck, der die Tabellendaten auslesen kann:

/^(.{39})(.{4})(.{2})(.{4})(.{5})(.{4})(.{4})(.{4})(.{4})(.{4})(.{4})$/

Codiert sieht es so aus. $tline im Codebeispiel beinhaltet eine getrimmte Zeile aus dem HTML.

// Überschriften und Feldgrössen
if(preg_match('@^(NAME *)(POS *)(GP)( *G)( *A)( *PTS)( *SOG)( *\\+/\\-)( *PIM)( *PPG)( *SHG)$@',$tline,$matches)) {
  if(!$inresults) {
    // Nach den Überschriften kommen die Tabelleneinträge
    $inresults=true;
    // Überschriften auslesen
    $headercaptions=preg_split('/ +/', $tline, -1, PREG_SPLIT_NO_EMPTY);
    // Überschriften ausgeben
    echo "\r\n".join("\t",$headercaptions)."\r\n";
  }
  // Wie breit ist ein Feld (in Zeichen)
  $sizes=array();
  for($i=1;$i<count($matches);$i++) {
    $sizes[$i-1]=strlen($matches[$i]);
  }

  // Suchmuster für Einträge aufbauen
  $searchpattern="";
  foreach($sizes as $size) {
    $searchpattern.='(.{'.$size.'})';
  }
  //print $searchpattern."<br />";
  $searchpattern='/^'.$searchpattern.'$/';
  continue;
}
// Tabellenzeilen auslesen
if($inresults && preg_match($searchpattern, $tline, $matches)) {
   $row=array();
   for($i=1; $i<count($matches); $i++) {
     $row[]=trim($matches[$i]);
   }
   // Und ausgeben
   echo join("\t",$row)."\r\n";
   continue;
}

Ausgabe als Excel

Hier bescheisse ich… Ich gebe nur eine tabulatorseparierte Liste aus. Excel schnallts, OpenOffice leider nicht (hat da wer eine gescheitere Lösung? HTML-Tabellen?)…

Zuerst die Headers:

header("Content-type: application/vnd.ms-excel");
header('Content-Disposition: inline; filename="'.$dateiname.'.xls"');

Einzelne Zellen werden mittels Tabulator (\t) voneinander getrennt, Zeilenenden werden durch “\r\n” markiert:

echo join("\t",$row)."\r\n";

Die Lösung

Die komplette Lösung kann von der Homepage heruntergeladen werden und eine Demo gibts ebenda…

Ähnliche Artikel

Eingeordnet in Webapplikationen | Keine Kommentare »

Einfache Animationen auf der Konsole mit PHP (1-Dimensional)

Geschrieben von skaldrom am 7. November 2007

AnimationWenn man mit PHP Konsolenapplikationen schreibt, wäre es manchmal schön, eine pfundige Ausgabe mit etwas Bewegung erzeugen zu können. Solche Kleinstanimationen eignen sich auch für Aufgaben beim Erlernen von PHP, denn sie sind sehr unaufwändig und es tut sich trotzdem was.

Diese Ausgaben sind Eindimensional (auf einer Linie) und funktionieren unter Linux und Windows. Ich warte sehnsüchtig auf die PHP-Languagebindings für die aalib oder libcaca :-)…

Der Trick ist der Carriage Return (CR), der den Cursor an den Anfang der Zeile setzt, aber nicht vertikal weiterscrollt. in PHP wird dieses Zeichen mit \r codiert. Grundsätzlich ist der Ablauf wie folgt:

Bilderfolge in ein Array rendern
Solange man Lust hat:
    Zeige nächstes Bild
    Warte
  }
}

Unter Linux können zur weiteren Effektsteigerungen auch noch ANSI-Escapesequenzen verwendet werden. Mit diesen kryptischen Dingern kann man den einzelnen Zeichen Vorder- und Hintergrundfarbe verpassen und verwirrendes einschalten wie etwa ein Blinken. ESCAPE ist in PHP beispielsweise 33 in oktal und wird mittels \033 dargestellt.

Nun, zeig mal Code!

<?php
// Render Movie
$frames=array();
// 10 images for this movie
for($i=0;$i<10;$i++) {
    // The background consits of blue underscores
    $frame=array_fill(0, 10, "\033[34m_\033[0m");
    // One actor is a red star
    $frame[$i]="\033[31m*\033[0m";
    // The other a green dot
    $frame[9-$i]="\033[32m.\033[0m";
    // Join Background and actors
    $frames[]=join("",$frame);
}

// Output
for($i=0;$i<20;$i++) {
  foreach($frames as $frame) {
        // Rewind cursor and show frame
        print "\r".$frame;
        // Sleep a bit, because nights are hard these times...
        usleep(100000)
  }
}
print "\n";
?>

Ähnliche Artikel

Eingeordnet in Theorie und Schnipsel | Keine Kommentare »

Erstellen eines Timers in Xoops

Geschrieben von skaldrom am 1. November 2007

Xoops ist ein geniales CMS, auch wenn es in letzter Zeit durch administrative Querelen nicht sehr gute Schlagzeilen gemacht hat. Wir benutzen es in einer erweiterten Version als Intranet.

Nun braucht ein Modul plötzlich unendlich lange um sich darzustellen. Das hindert unseren rasanten Arbeitsfluss und darum muss darum korrigiert werden. Statt rumzufrickeln lohnt es sich, die Sache seriös anzugehen.

Xoops bietet eine sehr gute Debugfunktion an: Adminsitration → Systemeinstellungen → Voreinstellungen → Allgemeine Einstellungen → Debug-Modus kann auf PHP-Debug gesetzt werden. Danach werden unten an der Seite viele Infos dargestellt, unter Anderem SQL-Befehle und Timer.

Xoops Debug

Genau solche Timer kann man selber einfach erstellen. Zuerst muss der XoopsLogger eingebunden werden:

include_once(XOOPS_ROOT_PATH . '/class/logger.php');

Um einen Timer zu starten:

$xoopsLogger->startTime( 'absence_count_query' );

Um ihn wieder zu stoppen:

$xoopsLogger->stopTime( 'absence_count_query' );

Diese Timer wird nun - wie oben ersichtlich - wunderschön in die Debugfunktion eingereiht.

Ähnliche Artikel

Eingeordnet in Theorie und Schnipsel, Web | 1 Kommentar »

Eine eigene Programmiersprache erschaffen? Lexer und Parser in PHP!

Geschrieben von skaldrom am 25. October 2007

Ich will Gott sein!

Paper SnippetsWer träumt nicht davon: Eine eigene Programmiersprache, denn der Syntax und das Wort sind Ausdruck! Einen Schritt weiter gehen und vom Programmiersprachenanwender zum Programmiersprachenerschaffer aufsteigen! Auch dies ist nun möglich in PHP, doch leider nur mit minimaler Dokumentation.

Ich gehe davon aus, dass die Leserin/Der Leser Grundlagen im Compilerbau besitzt. Ansonsten müsste man sich diese noch aneignen. Compilerbau ist rekursiv und somit göttlich! *schwärm*.

Das Beispiel

Ich möchte einen Interpreter bauen für normale, arithmetische Ausdrücke wie 9*(8+6)-2/(-4). Klar könnte man diesen einfach durch eval hauen, aber erstens macht das nicht so viel Spass und zweitens ist dieses Beispiel sowas wie das Hello World des Compilerbaus.

Als BNF habe ich folgendes vor:

<Expression> ::= '+' <Expression> // Unäres + (Vorzeichen)
<Expression> ::= '-' <Expression> // Unäres - (Vorzeichen)
<Expression> ::= <Term>
<Expression> ::= <Expression> '+' <Term>
<Expression> ::= <Expression> '-' <Term>
<Term> ::=  <Factor>
<Term> ::= <Term> '*' <Factor>
<Term> ::= <Term> '/' <Factor>
<Factor> ::=  NUMBER // Ok, hier hab ich abgekürzt ;) Soll der Lexer erledigen...
<Factor> ::= '(' <Expression> ')'

Ein Problem stellen die unären Plus- und Minusoperatoren dar. Sie besitzen das gleiche Symbol, nicht aber die gleiche Priorität wie ihre binären Kollegen. Lemon erlaubt keine änderung der Priorität nach Regeln und so funzen jetzt halt auch Konstrukte wie —+++—+1…

Die Werkzeuge

Ich verwende PHP_LexerGenerator, der kompatibel zum re2c Format ist und den PHP_ParserGenerator der in grossen Teilen dem LEMON Parser Generator entspricht. Heruntergeladen werden können sie mittels folgenden Zeilen, die allerdings unter Umständen noch eine Anpassungen der Versionsnummer brauchen:

pear install channel://pear.php.net/PHP_ParserGenerator-0.1.5
pear install channel://pear.php.net/PHP_LexerGenerator-0.3.4

Als einziges, weiteres Beispiel für diese Libs ausserhalb der Doku habe ich nur dieses Drupalmodul gefunden.

Die Realisierung

Der Lexer

Für den Lexer muss eine plex Datei erstellt werden. Sie beinhaltet den Rumpf des Lexerobjekts und die lexikalischen Regeln:

TermLexer.plex

<?php
class TermLexer
{
    private $data;
    public $counter;
    public $token;
    public $value;
    public $node;
    public $line;
    private $state = 1;

    function __construct($data)
    {
        $this->data = $data;
        $this->counter = 0;
        $this->line = 1;
    }

/*!lex2php
%input $this->data
%counter $this->counter
%token $this->token
%value $this->value
%line $this->line
number = /[0-9]+(\.[0-9]+)?/
multiplication = /\*/

division = @/@
plus = /\+/
minus = /\-/
openP = /\(/
closeP = /\)/
other = /./
*/
/*!lex2php
%statename START
multiplication {
  $this->token = TermParser::TP_MULTIPLICATION;
  //echo "multiplication: ".$this->value."\n";
}
division {
  $this->token = TermParser::TP_DIVISION;
  //echo "division: ".$this->value."\n";
}
plus{
  $this->token = TermParser::TP_PLUS;
  //echo "plus: ".$this->value."\n";
}
minus{
  $this->token = TermParser::TP_MINUS;
  //echo "plus: ".$this->value."\n";
}
openP {
  $this->token = TermParser::TP_OPENP;
  //echo "openP: ".$this->value."\n";
}
closeP {
  $this->token = TermParser::TP_CLOSEP;
  //echo "closeP: ".$this->value."\n";
}
number {
  $this->token = TermParser::TP_NUMBER;
  //echo "number: ".$this->value."\n";
}
other {
  return false;
}
*/

}

So richtig laufen tuts aber noch nicht, da er Konstanten des Parsers benötigt. Die stehen erst zur Verfügung wenn wir den Parser auch gebaut haben.

Der Parser

Der Parser wird vom Lexer mit Tokens gefüttert. Die Notation im Lemon-Style unterscheidet sich von Lex und Yacc, ist aber durchaus logisch. Hier der ganze Code, mal hingeknallt:

TermParser.y

/* This is an example for a Parser in PHP */
%name TP_
%declare_class {class TermParser}
%include_class
{
    // states whether the parse was successful or not
    public $successful = true;
    public $retvalue = 0;
    private $lex;
    private $internalError = false;

    function __construct($lex) {
        $this->lex = $lex;
    }
}

%token_prefix TP_

%parse_accept
{
    $this->successful = !$this->internalError;
    $this->internalError = false;
    $this->retvalue = $this->_retvalue;
    echo "WORKED!!\n\n";
}

%syntax_error
{
    $this->internalError = true;
    echo "Syntax Error on line " . $this->lex->line . ": token '" .
        $this->lex->value . "' count ".$this->lex->counter." while parsing rule: ";
    foreach ($this->yystack as $entry) {
        echo $this->tokenName($entry->major) . '->';
    }
    foreach ($this->yy_get_expected_tokens($yymajor) as $token) {
        $expect[] = self::$yyTokenName[$token];
    }
    echo "\n"
    throw new Exception('Unexpected ' . $this->tokenName($yymajor) . '(' . $TOKEN. '), expected one of: ' . implode(',', $expect));
}

%left PLUS MINUS.
%left MULTIPLICATION DIVISION.

start(res)       ::= expression(expr). { res = expr; }

/* Unary minus or plus */
expression(res)  ::= PLUS expression(e). { res = +e; }
expression(res)  ::= MINUS expression(e). { res = -e; }

/* The common stuff */
expression(res)  ::= term(t). { res = t; }
expression(res)  ::= expression(e1) PLUS term(t2). { res = e1+t2; }
expression(res)  ::= expression(e1) MINUS term(t2). { res = e1-t2; }

term(res)        ::= factor(f). { res = f; }
term(res)        ::= term(t1) MULTIPLICATION factor(f2). { res = t1*f2; }
term(res)        ::= term(t1) DIVISION factor(f2). { res = t1/f2; }

factor(res)      ::= NUMBER(n). { res = n; }
factor(res)      ::= OPENP expression(e) CLOSEP. { res = e; }

Generieren der eigentlichen Parser und Lexer

Damit Parser und Lexer generiert werden und Terme gefüttert werden können, sei hier ein kleines Beispiel gegeben:

<?php
// Create Parser
passthru('php /usr/share/php/PHP/ParserGenerator/cli.php TermParser.y');

// Create Lexer
require_once 'PHP/LexerGenerator.php';
$lex = new PHP_LexerGenerator('TermLexer.plex');

# Test
include_once("TermParser.php");
include_once("TermLexer.php");

$teststr='9*(8+6)-2/(-4)';

$lex = new TermLexer($teststr);
// $lex = new TermLexer(file_get_contents($lexerfile));

echo "-------------------------------\n";
echo " Parsing $teststr\n";
echo "-------------------------------\n";
$parser = new TermParser($lex);
while ($lex->yylex())  {
    echo "Parsing  {$lex->token} Token {$lex->value} \n";
    $parser->doParse($lex->token, $lex->value);
}
$parser->doParse(0, 0);

print "Returnvalue: ".$parser->retvalue."\n";
?>

Und nu?

Nun viel Spass… Ich bin auf der Suche nach der EBNF für RegExps, wenn die jemand hat :) …. Ihr könnt den ganzen Code hier auch downloaden…

Ähnliche Artikel

Eingeordnet in Theorie und Schnipsel | 7 Komentare »

Moodle Block Resource-Download

Geschrieben von skaldrom am 22. October 2007

Der Moodle Resource Download Block

Moodle Resource Download Block Auf vielfachen Schülerwunsch hin habe ich einen Block für unser Learning Management System Moodle codiert: Den Resource Download Block. Er erlaubt den Download aller Kursdateien und Verzeichnisse in einem ZIP-Archiv. Dieses Zip-Archiv wird wie im Beitrag Zipdateien on-the-fly erstellen mit PHP dynamisch erstellt, da (in der Theorie) jeder Lernende eine andere Kursansicht haben kann.

Einen Block erstellen ist relativ einfach, wenn man dem Block Howto folgt, aber der Teufel liegt wie immer im Detail.

Configwerte

Jeder Block kann verschiedene Konfigurationswerte erfragen und erhalten. Diese unterscheiden sich aber, ob der Block global (pinned, sticky) ist, in welchem er nur von einem Ort aus konfiguruiert wird oder ob er den individuellen Kursen hinzugefügt wurde, wobei dann jede Instanz ihre eigene Konfiguration hat. Ich habe mich für den zweiten Weg entschieden.

Um die Konfigurationsvariablen mit einem Defaultwert zu versehen, muss man sich in der instance_config_print() Methode darum kümmern:

if (!isset ($this->config)) {
    // ... teacher has not yet configured the block, let's put some default values here to explain things
    $this->config->exclusionregexp= block_downloader_default_exclusionregexp();
    $this->config->compression= block_downloader_default_compression();
    $this->config->maxsize= block_downloader_default_maxsize();
}

Die Datei, die das Zip zusammenstellt ist mehr oder weniger ausserhalb von Moodle, da sie nicht “als Block” erscheinen kann. Um da an die Konfigurationsdaten zu kommen, muss man etwas mehr Aufwand treiben. Sie befinden sich base64 codiert in den Tabellen block_instance respektive blocks_pinned für globale Blocks. courseid und instanceid werden dabei vom Link im Block übergeben.

<?php

/**
 * Create the zip on the fly and push it to the browser.
 */


require_once ('../../config.php');
require_once ($CFG->dirroot . '/blocks/downloader/lib/archive.php');
require_once ($CFG->dirroot . '/blocks/downloader/lib/downloadlib.php');
require_once ($CFG->dirroot . '/lib/filelib.php');
require_once ($CFG->dirroot . '/lib/moodlelib.php');
require_once ($CFG->dirroot . '/blocks/downloader/lib/downloadlib.php');

$courseid= required_param('courseid', PARAM_INT); // Course identification
$instanceid= required_param('instanceid', PARAM_INT); // Instance of the block

// Securitycheck
[SNIP]

// Get Config Data
$where= "pagetype = 'course-view' AND visible = 1 AND id=" . addslashes($instanceid);

// Instance?
$blockarr= get_records_select('block_instance', $where . " AND pageid=" . addslashes($courseid));

// Maybe pinned? In this case we have no courseid
if (!$blockarr) {
    $blockarr= get_records_select('block_pinned', $where);
}

if (!$blockarr) {
    error("cannot find block with: " . $where . " AND pageid=" . addslashes($courseid));
    exit ();
}

// Take the first result
$block= array_pop($blockarr);

$configdata= unserialize(base64_decode($block->configdata));

print_r($configdata);

[...]

Dateien pro Kurs abholen

Das Zusammenstellen aller Pfade für Dateien, die ein Benutzer sieht und auf die er Zugriff hat ist echt mühsam. Ich wünsche mir hier ein Bisschen den Servicegedanken: Beispielsweise würde man viel Zeit sparen, wenn der Entwickler mit dem entsprechenden Wissen Funktionen wie can_read($userid, $resourceid) implementieren würde. Zusätzlich wäre man unabhängig vom verwendeten Absicherungsschema. Ich werde das zukünftig in meinen Programmen berücksichtigen.

Reaktionen

Die Community hat - wie meistens bei Moodle - sehr nett auf die Veröffentlichung reagiert. Zwei Tage nach Version 1.0.0 habe ich schon eine Slovakische Übersetzung und diverse Hinweise auf Bugs erhalten.

Ähnliche Artikel

Eingeordnet in Theorie und Schnipsel, Web | Keine Kommentare »

Zipdateien on-the-fly erstellen mit PHP

Geschrieben von skaldrom am 17. October 2007

Das Problem

On-The-Fly An unserer Schule wird Moodle als LMS (Learning Management System) verwendet. Es eignet sich sehr gut um Unterrichtsunterlagen wie Skripts, Aufträge und Präsentationen zu publizieren und den Lernenden auf eine angenehme Art und Weise zur Verfügung zu stellen. In einigen Kursen gibt es sehr viele dieser Ressourcen (Dateien und Verzeichnisse) zum Download und alleine schon das Anklicken und Speichern nimmt einen grossen Teil des zur Verfügung stehenden Zeitbudgets ein.

Einzelne Ressourcen können von der Lehrperson unsichtbar gemacht werden und nicht alle Dateien eines Kurses stehen zum Download bereit. Die Ansicht ist also sehr individuell und kann sich schnell ändern. Die Dateien werden über eine Versionsverwaltung in das System eingespiesen und können sich so ausserhalb des Systems verändern. Leider verunmöglicht dies die offensichtliche Lösung, einfach ein statisches Zip-Archiv zum Download zur Verfügung zu stellen.

Screenshot Moodle

Die Lösung

Das Zip muss also dynamisch (im Speicher) generiert werden bei einem Aufruf. Fündig wurde ich bei der Klasse archive bei phpclasses.org. Neben Zip-Archiven kann sie auch tar’s, gzip’s und bzip2’s erzeugen. Ich habe einige, kleine Erweiterungen vorgenommen, die noch beschrieben werden.

Die Handhabung ist sehr einfach:

// Archivklasse einbinden:
require_once ('lib/archive.php');

// Objekt erzeugen. Das Argument bezeichnet den Dateinamen
$zipfile= new zip_file('Meine Zipdatei.zip');

// Die Optionen
$zipfile->set_options(array (
        'basedir' => "/home/me/toZip/", // Das Basisverzeichnis. Sonst wird der ganze Pfad von / an im Zip gespeichert.
        'followlinks' => 1, // Symlinks sollen berücksichtigt werden
        'inmemory' => 1, // Die Datei nur im Speicher erstellen
        'level' => 6, // Level 1 = schnell, Level 9 = gut
        'recurse' => 1, // In Unterverzeichnisse wechseln
        // Wenn zu grosse dateien verarbeitet werden, kannes zu einem php memory error kommen
        // Man sollte nicht über das halbe memory_limit (php.ini) hinausgehen
        'maxsize' => 12*1024*1024 // Nur Dateien die <= 12 MB gross sind zippen
));

// Alle Dateien im Verzeichnis /home/me/toZip/Stuff hinzufügen
// Alle ".doc" Dateien und alle Ordner im Verzeichnis /home/me/toZip/Letters hinzufügen
$zipfile->add_files(array("Stuff", "Letters/*.doc"));

// Alle ".tmp" dateien in Stuff ausschliessen
$zipfile->exclude_files("htdocs/*.tmp");

// Alle Dateien in ".svn" und "CVS" Verzeichnissen ausschliessen (Regular Expressions)
$zipfile->exclude_regexp_files('.*/CVS|.*/CVS/.*|.*/\.svn|.*/\.svn.*');

// Archiv erstellen
$zipfile->create_archive();

// Archiv zum Download anbieten
$zipfile->download_file();

Änderungen

Ich habe folgende Dinge gegenüber der original archive.php geändert:

  • Mittels exclude_regexp_files() können Dateien auf Grund von Regular Expressions ausgeschlossen werden.
  • Die maxsize Option erlaubt das Ausschliessen von Dateien die grösser sind als die angegebene Zahl in Byte.
  • Verzeichnisse unter Linux werden als Verzeichnisse behandelt (Bug?)
  • exclude_file() prüft, ob eine Datei ausgeschlossen würde

Meine Version kann natürlich auch frei heruntergeladen werden.

Ähnliche Artikel

Eingeordnet in Theorie und Schnipsel | 9 Komentare »

Schere, Stein, Papier: Bewertungen im Kreis

Geschrieben von skaldrom am 27. September 2007

Round n RoundDie Regeln von Schere, Stein, Papier sind weitherum bekannt. Zwei Mitspieler zeigen gleichzeitig ein Symbol, danach wird ausgewertet: Schere wird von Stein geschlagen → Stein wird von Papier geschlagen → Papier wird von Schere geschlagen. Die Gewinne sind im Kreis herum (zirkulär wie der geneigte Klugscheisser sagen würde). Diese Kreisbewertung gibt es oft, auch wenn mir jetzt überhaupt kein Beispiel dazu einfällt :-D .

Nehmen wir an, wir müssten die Auswertung programmieren und bestimmen, wer gewonnen hat. Die Symbole seien Konstanten und auf Ziffern gemappt:

define("SCHERE", 0);
define("STEIN", 1);
define("PAPIER", 2);

Insgesamt gibt es 9 Fälle (ich kann 3 Symbole zeigen, mein Mitspieler kann auf jedes mit 3 anderen Symbolen reagieren: 3 mal 3 macht 9). Der Programmierer, der nach SLOCs bezahlt wird würde also 9 ifs schreiben.

Mein Ziel ist es aber, nur 3 ifs zu benötigen für die 3 Resultate: Spieler 0 gewinnt, Spieler 1 gewinnt und unentschieden.

Die erste Reduktion ist trivial: Wenn beide dasselbe Symbol zeigen, dann ist unentschieden. Damit sind wir auf 7 ifs.

Weiter hilft eine Faustregel des alten Informatikers: Wenn etwas im Kreis herum geht, ist fast immer ein Modulo beteiligt.

Die Grundüberlegung: Wenn der Spieler 0 um 1 darunter liegt (beispielsweise STEIN=1 unter PAPIER=2) dann hat Spieler 1 gewonnen. Und nun muss das ganze halt noch im Kreis herum: PAPIER liegt 1 unter SCHERE.

Wenn nicht unentschieden ist, und auch Spieler 1 nicht gewonnen hat, ja dann, hmm, hat wohl Spieler 0 gewonnen!

Code!

define("SCHERE", 0);
define("STEIN", 1);
define("PAPIER", 2);

[...]
    if($symbolFromPlayer0==$symbolFromPlayer1) {
        // Unentschieden:
    } elseif( ($symbolFromPlayer0+1)%3 == $symbolFromPlayer1) {
        // Player1 hat gewonnen
    } else {
        // Player0 hat gewonnen
    }
[...]

Jou, juhu, hat geklappt! Wir haben unsere 3 ifs…

Ähnliche Artikel

Eingeordnet in Theorie und Schnipsel | Keine Kommentare »

Observer Pattern in PHP mit der SPL

Geschrieben von skaldrom am 12. September 2007

Das Observer Pattern

Observed!Normalerweise läuft es mir kalt den Rücken runter wenn ich das Wort Pattern höre. Ich will keine Fassaden, Dekoratöre und Factories in meinen Applikationen (ausser für Anwendungen in der entsprechenden Problemdomäne). An Patterns lastet der Geruch von CRC-Karten, Pair-Programming, millionenzeiliger, selbstgeschriebener Frameworks die Dinge vereinfachen sollen die man nicht begriffen hat und anderen Dingen an, die ich nicht so mag :-) .
Eine Ausnahme ist das Observer-Pattern (was hat es wohl angestellt, dass es auch Pattern geschimpft wird?). Grundsätzlich geht es darum, dass eine Klasse für Daten oder Ereignisse zuständig ist (das Subject). Andere Klassen können sich als Beobachter (Observer) anmelden und werden bei Statusänderungen informiert. MVC (Model-View-Controller) macht rege davon Gebrauch, indem das Model die Daten verwaltet (Subject) und Views (Observer) die Daten darstellen. Eine View könnte beispielsweise für eine Baumansicht verantwortlich sein, während sich eine andere View um eine listenartige Ansicht kümmert. Ändern sich nun die Daten, so werden beide benachrichtigt und können ihre Ausgaben anpassen. So ist es einfach, neue Klassen hinzuzufügen die auf Statusänderungen reagieren können müssen.

Das Observerpattern mit SPL

SPL ist die Standard PHP Library die ab PHP 5.0 dabei ist, stetig weiterentwickelt wird und ohne zusätzlichen Aufwand/Code verwendet werden kann. Sie erlaubt ein paar ganz nette Gags, aber das soll hier nicht Thema sein.

Ohne SplObjectStorage

Leider sind Beispiele für die PHP Implementierung etwas knapp gesät. Das PHP-Wiki ist offline und auch sonst gibt es neben PHP Avancado nur wenig Code. Die offizielle SPL-Seite hat zwar viel Doku aber leider auch keine Beispielimplementierung.

Darum hier etwas Code. Die Klasse Ticker generiert jede Sekunde einen Tick. Einige Observer beobachten sie und reagieren darauf.

Die erste Version arbeitet ohne SplObjectStorage, damit das Prinzip klar wird. Man muss sich somit selber um die Speicherung der Observer kümmern.

<?php
/**
 * Observer pattern without SplObjectStorage
 *
 * Little example because there are not much of them on the net.
 * It implements a Model-View relationship.
 * Taken from <a href="http://blog.oncode.info">Technik, Gothic und Anderes</a>.
 * Needs at least PHP 5.1
 * 
 * @see http://www.php.net/~helly/php/ext/spl/interfaceSplObserver.html
 * @see http://phpavancado.net/comment/reply/410
 * @see http://wiki.cc/php/?title=SplObserver
 * @author Skaldrom Y. Sarg
 */


/**
 * The Class we are observing
 *
 * This class generates a tick every second.
 */

class Ticker implements SplSubject {
    protected $numTicks= 0; // Stores the ticks
    protected $observers= array (); // Stores all observers

    /**
     * Needed to register observers
     *
     * Note the typehinting here.
     */

    public function attach(SplObserver $observer) {
        $this->observers[]= $observer;
    }

    /**
     * Needed to unregister observers
     *
     * Note the typehinting here.
     */

    public function detach(SplObserver $observer) {
        // Not implemented here
    }

    /**
     * Notify all Observers that we have changed
     */

    public function notify() {
        foreach ($this->observers as $obj) {
            $obj->update($this);
        }
    }

    /**
      * Get the number of ticks
      */

    public function getNumTicks() {
        return $this->numTicks;
    }

    /**
     * The ticker itslef
     */

    public function doTicks() {
        while ($this->numTicks < 10) {
            sleep(1); // Sleep for one second
            $this->numTicks++;
            echo "\nTicker: Added a Tick (" . $this->numTicks . ")\n";
            $this->notify(); // Tell all observers we have changed     
        }
    }
}

/**
 * Observer that counts the ticks
 */

class Observer1 implements SplObserver {
    public function update(SplSubject $subject) {
        echo "Observer 1: Wow, a Tick!!! We are at tick number " . $subject->getNumTicks() . "\n";
    }
}

/**
 * Observer that calculates the number of ticks until 10
 */

class Observer2 implements SplObserver {
    public function update(SplSubject $subject) {
        echo "Observer 2: TickTick, there are " . (10 - $subject->getNumTicks()) . " ticks left until 10\n";
    }
}

$ticker= new Ticker(); // The subject to be observed

$observer1_1= new Observer1(); // First observer of type 1
$ticker->attach($observer1_1);

$observer1_2= new Observer1(); // Another observer of type 1
$ticker->attach($observer1_2);

$observer2= new Observer2(); // Observer of type 2
$ticker->attach($observer2);

// Start the run
$ticker->doTicks();
?>

Und die Ausgabe (im Sekundentakt):

Ticker: Added a Tick (1)
Observer 1: Wow, a Tick!!! We are at tick number 1
Observer 1: Wow, a Tick!!! We are at tick number 1
Observer 2: TickTick, there are 9 ticks left until 10

Ticker: Added a Tick (2)
Observer 1: Wow, a Tick!!! We are at tick number 2
Observer 1: Wow, a Tick!!! We are at tick number 2
Observer 2: TickTick, there are 8 ticks left until 10
[...]

SplObjectStorage

SplObjectStorage nimmt uns einiges ab. Er implementiert aber das Interface SplSubject nicht. SplObjectStorage hat eine Implementierung von attach() und detach() und SplSubject auch, aber eine andere :-( . Nun muss man halt zusammenleimen, sonst wärs noch kürzer…

<?php
/**
 * Observer pattern with SplObjectStorage
 *
 * Little example because there are not much of them on the net.
 * It implements a Model-View relationship with the help of the SPL object storage class.
 * Taken from <a href="http://blog.oncode.info">Technik, Gothic und Anderes</a>.
 * Needs at least PHP 5.1
 * 
 * @see http://www.php.net/~helly/php/ext/spl/classSplObjectStorage.html
 * @see http://wiki.cc/php/?title=SplObjectStorage
 * @author Skaldrom Y. Sarg
 */


/**
 * The Class we are observing
 *
 * This class generates a tick every second.
 */

class Ticker extends SplObjectStorage implements SplSubject {
    protected $numTicks= 0; // Stores the ticks

    /**
     * Needed to register observers
     *   
     * This method glues plObjectStorage::attach() and SplSubject::attach(). Otherwise you will get something like:
     *  "Fatal error: Declaration of SplObjectStorage::attach() must be compatible with that of SplSubject::attach()"
     */

    public function attach(SplObserver $observer) {
        // Duplicate-Checking is done in SplObjectStorage::attach()
        parent :: attach($observer);
    }

    /**
     * Needed to unregister observers
     *   
     * This method glues plObjectStorage::detach() and SplSubject::detach(). Otherwise you will get something like:
     *  "Fatal error: Declaration of SplObjectStorage::detach() must be compatible with that of SplSubject::dettach()"
     */

    public function detach(SplObserver $observer) {
        // Duplicate-Checking is done in SplObjectStorage::attach()
        parent :: detach($observer);
    }

    /**
     * Notify all our observers
     *
     * The observers are stored in the SplObjectStorage and this class implements an iterator.
     */

    public function notify() {
        foreach ($this as $observers) {
            $observers->update($this);
        }
    }

    /**
      * Get the number of ticks
      */

    public function getNumTicks() {
        return $this->numTicks;
    }

    /**
     * The ticker itslef
     */

    public function doTicks() {
        while ($this->numTicks < 10) {
            sleep(1); // Sleep for one second
            $this->numTicks++;
            echo "\nTicker: Added a Tick (" . $this->numTicks . ")\n";
            $this->notify(); // Tell all observers we have changed     
        }
    }
}

/**
 * Observer that counts the ticks
 */

class Observer1 implements SplObserver {
    public function update(SplSubject $subject) {
        echo "Observer 1: Wow, a Tick!!! We are at tick number " . $subject->getNumTicks() . "\n";
    }
}

/**
 * Observer that calculates the number of ticks until 10
 */

class Observer2 implements SplObserver {
    public function update(SplSubject $subject) {
        echo "Observer 2: TickTick, there are " . (10 - $subject->getNumTicks()) . " ticks left until 10\n";
    }
}

$ticker= new Ticker(); // The subject to be observed

$observer1_1= new Observer1(); // First observer of type 1
$ticker->attach($observer1_1);

$observer1_2= new Observer1(); // Another observer of type 1
$ticker->attach($observer1_2);

$observer2= new Observer2(); // Observer of type 2
$ticker->attach($observer2);

// Start the run
$ticker->doTicks();
?>

Fazit

Man beachte: Hier könnten noch viele beliebige Observer hinzugefügt werden, ohne dass das Subject (Ticker) verändert werden muss.

Ähnliche Artikel

Eingeordnet in Theorie und Schnipsel |