Unterabschnitte

Lösungen


Lösung zu Baumdarstellungen


Vater-Zeiger

Die CREATE TABLE mit anschließender INSERT Anweisung sollte nicht das Problem sein:
CREATE TABLE vaterzeiger (
  ID    int not null primary key,
  Name  varchar(100),
  VID   int
);

INSERT INTO vaterzeiger VALUES (1,'Root',0);
INSERT INTO vaterzeiger VALUES (2,'A',1);
INSERT INTO vaterzeiger VALUES (3,'B',1);
INSERT INTO vaterzeiger VALUES (4,'C',1);
INSERT INTO vaterzeiger VALUES (5,'A1',2);
INSERT INTO vaterzeiger VALUES (6,'B1',3);
INSERT INTO vaterzeiger VALUES (7,'B2',3);
INSERT INTO vaterzeiger VALUES (8,'C1',4);
INSERT INTO vaterzeiger VALUES (9,'A1I',5);
INSERT INTO vaterzeiger VALUES (10,'C1I',8);
INSERT INTO vaterzeiger VALUES (11,'C1II',8);

Nachdem nun die Tabelle existiert, die einzelnen Abfragen:

Nested Set


Lösung für ,,Ergebnis-Tabelle ausgeben I``

1. Teilschritt

Ich habe bei der Lösung mysql_fetch_row() verwendet, weil es einfacher ist, mit einer Schleife einen numerischen Index durchzugehen. Das wird im 3. Teilschritt deutlich.
function print_result_table($result){
  // Tabellenanfang
  echo "<table>\n";

  // Tabellenzeilen-Anfang
  echo "  <tr>\n";

  // Zeile aus DB-Anfrage holen
  $row = mysql_fetch_row($result);
  // erstes Feld der Zeile ausgeben
  echo "    <td>$row[0]</td>\n";

  // Tabellenzeilen-Ende
  echo "  </tr>\n";

  // Tabellenende
  echo "</table>\n";
}

2. Teilschritt

function print_result_table($result){
  // Tabellenanfang
  echo "<table>\n";

  // Alle Ergebniszeilen durchgehen
  while ($row = mysql_fetch_row($result)){
    // Tabellenzeilen-Anfang
    echo "  <tr>\n";

    // erstes Feld der Zeile ausgeben
    echo "    <td>$row[0]</td>\n";

    // Tabellenzeilen-Ende
    echo "  </tr>\n";
  }

  // Tabellenende
  echo "</table>\n";
}


3. Teilschritt

Mit int mysql_num_fields(int result) kann man abfragen, wie viele Spalten bei der Abfrage zurückgegeben wurden. Mit einer for-Schleife werden einfach alle Felder ausgegeben.
function print_result_table($result){
  // Tabellenanfang
  echo "<table>\n";

  // Alle Ergebniszeilen durchgehen
  while ($row = mysql_fetch_row($result)){
    // Tabellenzeilen-Anfang
    echo "  <tr>\n";

    // Alle Spalten durchgehen
    for ($i = 0; $i < mysql_num_fields($result); $i++){
      echo "    <td>$row[$i]</td>\n";
    }

    // Tabellenzeilen-Ende
    echo "  </tr>\n";
  }

  // Tabellenende
  echo "</table>\n";
}


4. Teilschritt

Als letztes müssen wir noch abfragen, wie die Spalten heißen. Das macht die Funktion string mysql_field_name(int result, int field_index).
function print_result_table($result){
  // Tabellenanfang
  echo "<table>\n";
  // 1. Tabellenzeile Anfang
  echo "  <tr>\n";
  for ($i = 0; $i < mysql_num_fields($result); $i++){
    echo "    <th>".mysql_field_name($result,$i)."</th>\n";
  }
  // 1. Tabellenzeile Ende
  echo "  </tr>\n";

  // Alle Ergebniszeilen durchgehen
  while ($row = mysql_fetch_row($result)){
    // Tabellenzeilen-Anfang
    echo "  <tr>\n";

    // Alle Spalten durchgehen
    for ($i = 0; $i < mysql_num_fields($result); $i++){
      echo "    <td>$row[$i]</td>\n";
    }

    // Tabellenzeilen-Ende
    echo "  </tr>\n";
  }

  // Tabellenende
  echo "</table>\n";
}


Lösung für ,,Ergebnis-Tabelle ausgeben II``

Kleiner Tipp vorweg: Um zu testen, ob die eine oder andere Abfrage sauber funktioniert, kann man einfach mal einen Fehler provozieren, also z.B. einen falschen Namen bei $db_user eintragen oder die Abfrage zu einem ,,SELECT * FROM Mitarbeiter WHERE MNr = 0`` erweitern, damit keine Mitarbeiter gefunden werden.

<?php
$db_host   =       "localhost";
$db_user   =       "cr";
$db_pass   =       "123";

$datab  =          "cr";


function print_result_table($result){
  // s.o.
}

// Hauptprogramm

/* Verbindung zur Datenbank aufbauen */
$db = @mysql_connect($db_host,$db_user,$db_pass)
      OR die(mysql_error());
@mysql_select_db($datab,$db)
      OR die(mysql_error());

/* HTML-Startcode ausgeben */
echo "<html>\n<body>\n";

/* SQL-Abfrage */
$result = @mysql_query("SELECT * FROM Mitarbeiter");
/* Wenn die Fehlernummer != 0 ist, dann gab es einen Fehler
   => Fehlermeldung ausgeben */
if (mysql_errno() != 0){
  echo mysql_error();
}
// es gab keine Fehler => Ergebnis ausgeben
else {
  // Wie viele Datensätze wurden gefunden?
  // Bei 0 Meldung ausgeben
  if (mysql_num_rows($result) == 0){
    echo "Keine Datens&auml;tze gefunden!";
  }
  // sonst die Funktion aufrufen
  else{
    print_result_table($result);
  }
}

/* HTML-Endcode ausgeben */
echo "</body>\n</html>\n";
?>


Lösung für ,,Abfrage mit sprintf()``

Die vier Fehler sind im Einzelnen:

  1. ,,SELECT %0\$s``: Es gibt kein nulltes Argument. Nie.
  2. ,,%2\$02d``: Im ISO-Datumsformat steht das Jahr vorne - es müßte also das dritte Argument ausgewertet werden, nicht das zweite. Außerdem ist die Jahresangabe in Standard-Notation vierstellig (also zwei Fehler).
  3. ,,%3\02d``: Ein Backslash allein macht noch keine Argument-Referenz ...
  4. ,,-%'``: Soll ein einzelnes Prozentzeichen wirklich ausgegeben werden, muß man es escapen. In diesem Fall sind also zwei Prozentzeichen nötig: Eins zum Escapen und das Prozentzeichen an sich für die Abfrage mit LIKE als Allquantor.


Lösung für Einfügen mit automatischer ID

Um das gewünschte zu erreichen, sind zwei Tricks nötig: Zum einen muß das Erzeugen der MNr MySQL überlassen werden und zum anderen muß diese ID abgefragt werden, um die VNr für die zweite Einfügeoperation zu haben. Ersteres erreicht man durch Weglassen der MNr bei der Liste der Felder (oder bei Angabe der MNr Setzen des Wertes auf NULL), letzteres durch mysql_insert_id().

Folgender Code implementiert dies:

$db_host   =       "localhost";
$db_user   =       "cr";
$db_pass   =       "123";

$datab  =          "cr";

/* Verbindung zur Datenbank aufbauen */
$db = @mysql_connect($db_host,$db_user,$db_pass)
      OR die(mysql_error());
@mysql_select_db($datab,$db)
      OR die(mysql_error());

// Jens einfügen
mysql_query("INSERT INTO Mitarbeiter (VNr,AbtNr,Name,GebDat)
             VALUES (1, 2, 'Jens', '1981-05-26')");
echo mysql_error();

$vnr = mysql_insert_id();

// Kile einfügen und Jens unterstellen
mysql_query("INSERT INTO Mitarbeiter (VNr,AbtNr,Name)
             VALUES ($vnr, 1, 'Kile')");
echo mysql_error();

Lösungen zu Regulären Ausdrücken


Lösung zu ,,einfache Suchmuster``

.

Das Ergebnis ist zweimal nein. Im zweiten Text kommt das Wort ,,hallo`` gar nicht vor und in dem ersten ist es groß geschrieben. Es wird auf Groß-/Kleinschreibung geachtet.

.

Diesmal passen beide Ausdrücke; in beiden Fällen ist das letzte Zeichen kein großer Buchstabe oder eine Zahl.

.

Wieder zwei mal ja.

.

Und nochmal passen beide. In beiden Strings kommt ein 5 Zeichen langes Wort vor.

.

Auf den ersten Text paßt er nicht, dafür auf den zweiten.

Lösung zu ,,Quantifizierer``

.

Der Ausdruck paßt bei keinem der beiden Texte. Der erste besteht zwar aus zwei Wörtern, aber das Ausrufezeichen ! ist nicht in \w enthalten.

.

Auf den ersten Text paßt jetzt der Ausdruck, auf den zweiten immer noch nicht.

.

Dieselbe Antwort wie bei der vorigen Frage.

.

Zweimal ja. In beiden Texten gibt es ein Wort aus vier Buchstaben, gefolgt von einem Leerzeichen und einem zweiten Wort. Im ersten Text lautet das 4-stellige Wort ,, allo``, wo noch ein ,,H`` davor hängt. Es ist keine Aussage gemacht, was vor dem Wort stehen darf, oder nicht.

.

Ja, nein.

.

Der folgende Ausdruck (eigentlich gehört alles in eine Zeile, nur aus Platzgründen ist er umbrochen) paßt auf die angegebene Log-Zeile. Er ist noch nicht optimal (siehe auch die Erklärung), aber da ich bei den regulären Ausdrücken auch noch am Lernen bin, ist mir bis jetzt noch nichts besseres eingefallen. Wer einen besseren Ausdruck weiß, kann ihn mir gerne schicken.
/^[\w.-]+ [-\w]+ [-\w]+ \[\d{2}\/\w{3}\/\d{4}:\d{2}:\d{2}:\d{2}
[+-]\d{4}\] "\w+ [\/.\w]+ HTTP\/\d\.\d" [1-5]\d{2} [-\d]*$/
Was macht der Ausdruck? Als erstes soll er die gesamte Zeile überprüfen, deshalb das Dach ^ am Anfang und das Dollarzeichen $ am Ende. Am Anfang der Zeile soll der Name bzw. die IP des Anfragenden kommen. Das habe ich an dieser Stelle mit einem [\w.-]+ erschlagen. Da der Punkt und der Bindestrich vorkommen darf, aber nicht bei den ``Wort``-Zeichen enthalten ist, müssen sie hier extra aufgeführt werden. Dieser Teil ist nicht optimal, weil ,,1.1``, ,, keine.gueltiger.name`` oder ,,rechnername`` keine gültigen Namen oder IP-Adressen sind, trotzdem aber auf den Ausdruck passen.

Die nächsten beiden Felder sind einfacher, weil hier entweder normale Wörter oder ein Minus für ,,nicht vorhanden`` stehen. Die eckigen Klammern müssen mit einem Backslash escaped werden, weil ich hier keine Menge von Zeichen definieren will, sondern die Zeichen ,,eckige Klammer auf`` und ,,eckige Klammer zu`` haben will. Ähnliches gilt für den Slash: Wenn ich ihn nicht mit einem Backslash escaped hätte, wäre der Ausdruck an der Stelle zu Ende.

Der Ausdruck in den eckigen Klammern müßte eigentlich klar sein. Da die Anzahl der einzelnen Zeichen feststehen, habe ich sie hier explizit angegeben.

In den Anführungszeichen steht das nächste nicht-Optimum. Das erste Wort in den Anführungszeichen ist entweder ,,GET`` oder ,,POST``; trotzdem muß ich beliebige Wörter erlauben, weil ich noch nicht geschrieben habe, wie man Oder realisiert. Der Punkt bei HTTP muß auch mit einem Backslash escaped werden, damit nur der Punkt paßt und nicht ein beliebiges Zeichen.

Da der Statuscode immer eine dreistellige Zahl zwischen 100 und 505 ist, muß auch hier explizit die Anzahl der Ziffern angegeben. Ist auch nicht ganz optimal, weil nicht weit genug geprüft wird. Die Größenangabe (das letzte Feld) kann auch nichts (d.h. ein Minus -) enthalten, wenn es keinen Sinn machen würde. Daher dieser Ausdruck.

Gruppierungen

.

/(\d*),\1DM/


Lösungen zur Fehlersuche

Lösung für ,,ein komisches IF``

Teil 1

Der Sinn des Programms sollte eigentlich auf den ersten Blick klar sein. Zu Beginn wird die Variable $i auf einen Wert gesetzt (hier 0) und dann überprüft, ob sie den Wert 1 hat. Wenn ja, soll der Text ,,Die Variable $i hat den Wert 1`` ausgegeben werden, sonst ,,Die Variable hat nicht den Wert 1``.

Das Programm gibt folgendes ohne Fehlermeldung aus: ,,Die Variable 1 hat den Wert 1``. Der erste ist ein beliebter Fehler: statt des Vergleichsoperators == wurde der Zuweisungsoperator = benutzt. Damit wurde nicht die Variable mit dem Wert verglichen, sondern der Variablen wurde der Wert ,,1`` zugewiesen. Anschließend wurde dieser Wert genommen, so daß alle Werte, die nicht ,,0`` waren, als ,, true`` interpretiert wurden. Somit wurde in der IF-Anweisung der 1. Anweisungsblock ausgeführt.

Der zweite Fehler steckt in der Ausgabe. Ausgegeben werden sollte ,,Die Variable $i ...`` und nicht ,,Die Variable 1 ...``. Es gibt zwei Lösungen für das Problem. Entweder benutzt man die einfachen Anführungszeichen und verhindert damit das Ersetzen von Variablennamen durch deren Werte oder man escaped das $, dazu muß man statt $i ein \$i schreiben.

Solche Fehler könnte man vermeiden, würde man statt if ($i == 1) ein if (1 == $i) schreiben. Dann erhielte man nämlich eine Fehlermeldung, wenn man nur 1 = $i schriebe, weil man dem Wert 1 nicht den Wert der Variablen zuweisen kann.

Teil 2

Im Prinzip gilt das oben Gesagte. Hier wird allerdings der else-Block ausgeführt. Bei der if-Anweisung wird der Wert ,,0`` genommen und der gilt als ,,false``.

Lösung für ,,Fehler in einer leeren Zeile¿`

Die Ausgabe des Programms sollte eigentlich folgende sein:
1
2
3
4
5
Das waren die Zahlen 1-5

Das Programm gibt aber unter PHP3 folgendes aus:

1
Das waren die Zahlen 1-5
Parse error: parse error in /home/httpd/html/test.php3 on line 6
Und bei PHP4 folgendes:
Parse error: parse error in /home/httpd/html/test.php4 on line 6

Die unterschiedlichen Ausgaben kommen vom unterschiedlichen Design von PHP3 und PHP4. Die Fehlermeldung ist aber in beiden Fällen dieselbe: In Zeile 6 gibt es einen parse error. Zeile 6 ist allerdings leer. Der Fehler ist, daß in Zeile 2 ein Anweisungsblock mit einer { angefangen, aber nicht mehr beendet wurde. PHP kann das erst am Ende der Datei merken, daher wird in der Fehlermeldung Zeile 6 angegeben.

Vermeiden kann man solche Fehler, indem man richtig einrückt. Bei folgender Schreibweise fällt leichter auf, daß die (geschweifte Klammer zu) fehlt. Auf jeden Fall läßt sich so der Fehler leichter finden. Vorteilhaft in solchen Situationen ist es auch, einen Editor zu benutzen, der zueinander gehörige Klammern farblich hervorhebt.

<?php
for ($i=1; $i<=5; $i++){
  echo "$i<br>\n";
echo "Das waren die Zahlen 1-5";
?>

Manche schreiben es noch deutlicher.

<?php
for ($i=1; $i<=5; $i++)
{
  echo "$i<br>\n";
echo "Das waren die Zahlen 1-5";
?>

Lösung zu ,,Wieso fehlt ein `;` wo eins ist¿`

Das gewollte Ergebnis ist dasselbe wie in der vorigen Übung. Die Ausgabe unterscheidet sich wieder je nach PHP-Version.

Ausgabe von PHP3 (die letzten beiden Zeilen sind eigentlich eine):

1
; } echo 
Parse error: parse error, expecting `','' or `';'' 
in /home/httpd/html/test.php3 on line 5
Ausgabe von PHP4:
Parse error: parse error, expecting `','' or `';'' 
in /home/httpd/html/test.php4 on line 5

Wie im obigen Fall ist auch hier die Fehlermeldung bei beiden Versionen dieselbe; PHP erwartet in Zeile 5 ein ,,;``. Bei genauerem Betrachten sieht diese aber richtig aus. Der Fehler steckt in Zeile 3. Dort werden die Anführungszeichen nicht wieder geschlossen. Daher auch die Ausgabe bei PHP3. Nach dem Anführungszeichen in Zeile 3 wird alles bis zum nächsten Anführungszeichen ausgegeben, in unserem Fall in Zeile 5. Damit wird aber das, was eigentlich in den Anführungszeichen stehen sollte, als Reihe von PHP-Befehlen interpretiert. Und mit einem ,,Das`` nach einer Ausgabe kann PHP eben nicht viel anfangen und will, daß wir erstmal mit einem ,,;`` die Anweisung beenden.

An diesem Beispiel sieht man, daß der Fehler nicht immer in der gemeldeten Zeile liegen muß. In diesem Fall hilft nur konzentriertes Programmieren und ein Editor mit Syntax-Highlighting[*].


Lösung zu Spruch des Abrufs

Es gibt zwei Gründe für die zusätzliche Funktion: Wenn man die Frage umdrehen würde, wäre sie sinnvoll: Wozu die Funktion get_spruch implementieren, wenn es ein get_sprueche gibt?


Lösung zum Image-Beispiel

Der folgende Code erzeugt transparente Bilder im PNG-Format[*]. Wohlgemerkt, es werden Bilder erstellt, kein HTML!

Als erstes die Image-Klasse:

/**
 * Image Klasse
 * Legt ein Bild (default: 100x100) an
 * und gibt es bei Bedarf aus
 */
class Image {
 /** Image stream */
  var $im;

 /** Breite */
  var $width;

 /** Höhe */
  var $height;

 /** Malfarbe */
  var $dcolor;

  /**
   * Konstruktor
   * Bild anlegen und Standardfarben setzen
   *
   * @param int Breite
   * @param int Höhe
   */
  function Image($width=100, $height=100) {
    if ($width<1 || $height<1)
      $width = $height = 100;

    $this->width = $width;
    $this->height = $height;

    $this->im = @ImageCreate($width, $height)
      or die ("Konnte keinen Image stream anlegen");

    // transparenter Hintergrund, schwarzer "Stift"
    $whitecol = ImageColorAllocate($this->im, 0, 0, 0);
    ImageColorTransparent($this->im, $whitecol);
    $this->dcolor = ImageColorAllocate($this->im, 0, 0, 0);
  }

  /**
   * Bild anzeigen (PNG)
   */
  function show() {
    Header("Content-type: image/png");
    ImagePNG($this->im);
  }

  /**
   * Breite des Bildes zurückliefern
   *
   * @return int Breite
   */
  function getWidth() {
    return $this->width;
  }

  /**
   * Höhe des Bildes zurückliefern
   *
   * @return int Höhe
   */
  function getHeight() {
    return $this->height;
  }
}

Nun die Punkt-Klasse:

/**
 * Point Klasse
 * Kann einen Punkt malen und ausgeben
 */
class Point extends Image {
 /** X-Koordinate */
  var $x = 0;

 /** Y-Koordinate */
  var $y = 0;

  /**
   * Koordinaten und sonstiges setzen
   *
   * @param int X-Koordinate
   * @param int Y-Koordinate
   */
  function set($x=0, $y=0) {
    $this->x = $x;
    $this->y = $y;
    ImageSetPixel($this->im,$this->x,$this->y,$this->dcolor);
  }
}

Und schließlich die Ausgabe:

$mypoint = new Point();
$mypoint->set(50, 50);
$mypoint->show();

Als nächstes der Kreis:

/**
 * Circle Klasse
 * Kann einen Kreis malen und ausgeben,
 * erfordert aber die GD-Lib 2.0
 */
class Circle extends Point {
 /** Radius */
  var $r = 0;

  /**
   * Koordinaten und Radius setzen
   *
   * @param int X-Koordinate
   * @param int Y-Koordinate
   * @param int Radius
   */
  function set($x=0, $y=0, $r=10) {
    parent::set($x, $y);
    $this->r = $r;
    ImageEllipse($this->im, $this->x, $this->y,
                 $this->r, $this->r, $this->dcolor);
  }
}

Kreis-Ausgabe (funktioniert nur mit GD-Lib 2.0!):

$mycircle = new Circle();
$mycircle->set(50, 50, 30);
$mycircle->show();

Da ich die GD-Lib 2.0 nicht habe, fehlt hier das entsprechende Bild ...

Ein Rechteck ist auch nicht schwer zu erstellen:

/**
 * Rectangle Klasse
 * Kann ein Rechteck malen und ausgeben
 */
class Rectangle extends Point {
 /** zweite X-Koordinate */
  var $x2;

 /** zweite Y-Koordinate */
  var $y2;

  /**
   * Koordinaten und Ausmaße setzen;
   * ggf. zentrieren
   *
   * @param int X-Koordinate
   * @param int Y-Koordinate
   * @param int Breite
   * @param int Höhe
   * @param boolean Um X/Y-Koordinaten zentrieren
   */
  function set($x=0,$y=0,$width=10,$height=10,$center=false){
    if ($center) {
      parent::set($x - $width/2, $y - $height/2);
      $this->x2 = $x + $width/2;
      $this->y2 = $y + $height/2;
    } else {
      parent::set($x, $y);
      $this->x2 = $x + $width;
      $this->y2 = $y + $height;
    }

    // (x,y) ist links oben, (x2,y2) rechts unten
    ImageRectangle($this->im, $this->x, $this->y,
                 $this->x2, $this->y2, $this->dcolor);
  }
}

Beispiel-Rechteck:

$myrect = new Rectangle();
$myrect->set(50, 50, 30, 40, true);
$myrect->show();

Zum Schluß noch das Quadrat:

/**
 * Square Klasse
 * Kann ein Quadrat malen und ausgeben
 */
class Square extends Rectangle {
  /**
   * Koordinaten und Ausmaße setzen;
   * ggf. zentrieren
   *
   * @param int X-Koordinate
   * @param int Y-Koordinate
   * @param int Seitenlänge
   * @param boolean Um X/Y-Koordinaten zentrieren
   */
  function set($x=0, $y=0, $length=10, $center=false) {
    parent::set($x, $y, $length, $length, $center);
  }
}

Und die obligatorische Ausgabe:

$mysquare = new Square();
$mysquare->set(50, 50, 30);
$mysquare->show();

Abbildung B.1: erzeugte Bilder: Punkt, Rechteck und Quadrat
Image pointImage Image rectImage Image squareImage


Lösung zur Referenzsemantik

Eine korrekte Implementierung sähe z.B. so aus:

class MyArray {
  var $array;
  function MyArray() {}
  function add(&$val) {
    $this->array[] =& $val;
  }
}

class Test {
  var $var;
  function Test($var, &$array) {
    $this->var = $var;
    $array->add($this);
  }
  function setVar($var) {
    $this->var = $var;
  }
  function getVar() {
    return $this->var;
  }
}

$array = new MyArray();
$test  =& new Test(42, $array);
$test2 =& $test;
$test->setVar(0);
echo $test->getVar()." ".$test2->getVar()."\n";
var_dump($array);

Christoph Reeg