<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>PHP hates me - Der PHP Blog &#187; Akademischer Tag</title>
	<atom:link href="http://www.phphatesme.com/archives/category/akademischer-tag/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.phphatesme.com</link>
	<description>PhpHatesMe, but that&#039;s ok!</description>
	<lastBuildDate>Sat, 11 Feb 2012 10:13:45 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Understanding advanced regular expressions</title>
		<link>http://www.phphatesme.com/blog/webentwicklung/understanding-advanced-regular-expressions/</link>
		<comments>http://www.phphatesme.com/blog/webentwicklung/understanding-advanced-regular-expressions/#comments</comments>
		<pubDate>Sun, 09 May 2010 13:00:00 +0000</pubDate>
		<dc:creator>Nils Langner</dc:creator>
				<category><![CDATA[Akademischer Tag]]></category>
		<category><![CDATA[Tools & Helferlein]]></category>
		<category><![CDATA[Vorträge]]></category>
		<category><![CDATA[Webentwicklung]]></category>

		<guid isPermaLink="false">http://www.phphatesme.com/blog/webentwicklung/understanding-advanced-regular-expressions/</guid>
		<description><![CDATA[]]></description>
			<content:encoded><![CDATA[]]></content:encoded>
			<wfw:commentRss>http://www.phphatesme.com/blog/webentwicklung/understanding-advanced-regular-expressions/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Wer braucht Variablen? Die funktionale Welt&#8230;</title>
		<link>http://www.phphatesme.com/blog/softwaretechnik/wer-braucht-variablen-die-funktionale-welt/</link>
		<comments>http://www.phphatesme.com/blog/softwaretechnik/wer-braucht-variablen-die-funktionale-welt/#comments</comments>
		<pubDate>Wed, 10 Mar 2010 06:00:07 +0000</pubDate>
		<dc:creator>Wolfgang Gassler</dc:creator>
				<category><![CDATA[Akademischer Tag]]></category>
		<category><![CDATA[Softwaretechnik]]></category>

		<guid isPermaLink="false">http://www.phphatesme.com/?p=5446</guid>
		<description><![CDATA[Ich nutze gleich einmal Nils Abwesenheit (Urlaub) ;) und verbiete Variablen, globale Zustände, globale Variablen, Schleifen und Referenzübergaben bei Funktionsaufrufen. Auch wenn sich jetzt alle Leser Nils zurückwünschen, diese Einschränkungen ergeben Sinn und ermöglichen sehr effizient zu programmieren. Machbar ist dies durch das zur imperativen Welt sehr unterschiedliche Paradigma der <a href="http://de.wikipedia.org/wiki/Funktionale_Programmierung">funktionalen Programmierung</a>, das auch immer mehr in imperativen Sprachen, wie zum Beispiel PHP, integriert wird.]]></description>
			<content:encoded><![CDATA[<p>Ich nutze gleich einmal Nils Abwesenheit (Urlaub) <img src='http://www.phphatesme.com/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' />  und verbiete Variablen, globale Zustände, globale Variablen, Schleifen und Referenzübergaben bei Funktionsaufrufen. Auch wenn sich jetzt alle Leser Nils zurückwünschen, diese Einschränkungen ergeben Sinn und ermöglichen sehr effizient zu programmieren. Machbar ist dies durch das zur imperativen Welt sehr unterschiedliche Paradigma der <a href="http://de.wikipedia.org/wiki/Funktionale_Programmierung">funktionalen Programmierung</a> (basierend am <a href="http://de.wikipedia.org/wiki/Lambda-Kalk%C3%BCl">Lambda-Kalkül</a>), das auch immer mehr in imperativen Sprachen, wie zum Beispiel PHP, integriert wird. Neben Vorteilen, die die Programmierung vereinfachen, ist das bereits sehr alte Konzept (<a href="http://de.wikipedia.org/wiki/Lambda-Kalk%C3%BCl">erste Grundlagen 1936</a>) auch bestens für die Zukunft mit Multiprozessoren und Cloud-Computing gerüstet, da eine Parallelisierung beinahe automatisch erfolgen kann. Näheres jedoch im Laufe des Artikels.<br />
Beginnen wir zuerst einmal mit den größten praktischen Unterschieden (zum leichteren Verständnis mit PHP Code) ganz nach dem Motto: &#8220;Alles ist eine Funktion&#8221;.</p>
<h3><strong>Keine Variablen? Wie kann ich dann ohne Variablen x + y rechnen, wenn  z.B. x=3 und y=2?</strong></h3>
<p>Da wir nur Funktionen zur Verfügung haben, erstellen wir für die Addition einfach 3 Funktionen:<br />
1. Funktion mit dem Namen x:</p>
<pre><code>function x() { return 3; }</code></pre>
<p>2. Funktion mit dem Namen y:</p>
<pre><code>function y() { return 2; }</code></pre>
<p>3. Funktion mit dem Namen +:</p>
<pre><code>function plus($x,$y) { return $x + $y; }
function result() { return plus(x(),y()); }
</code></pre>
<p>Die Funktionen x und y sind daher Funktionen, die immer einen bestimmten konstanten Wert zurückgeben. Dieser Wert kann jedoch im Gegensatz zu Variablen nicht mehr geändert werden. Das Ergebnis <em>result</em> ist durch den Aufruf der Funktion <em>plus</em> mit den Parametern x und y definiert. Wir haben also die Berechnung ohne Variablen durchgeführt. Natürlich ist eine derartige Konstruktion in PHP nur bedingt sinnvoll, soll aber zeigen, dass der Verzicht auf Variablen auch grundsätzlich in PHP technisch möglich ist.</p>
<h3><strong>Keine Zustände oder globalen Variablen?</strong></h3>
<p>In der funktionalen Welt ist es sehr wichtig, dass Funktionen beim Aufruf mit gleichen Parametern (Input) immer den selben Wert zurückliefern (Output). Egal zu welchen Zeitpunkt im Programm oder auf welchem Rechner ausgeführt, muss eine Funktion bei einem bestimmten Input immer den selben Output haben.</p>
<pre><code>$GLOBALS['einwert'] = 0;
function garNichtFunktional($x) {
  $GLOBALS['einwert'] += 1;
  return $x + $GLOBALS['einwert'];
}</code></pre>
<p>Bei dieser Funktion wird bei zwei aufeinander folgenden Aufrufen nicht der selbe Rückgabewert errechnet, da sich zwischen zwei Aufrufen der globale Wert <em>einwert</em> ändert. So liefert <em>garNichtFunktional(4)</em> zuerst 5 als Ergebnis und bei einem erneuten Aufruf das Ergebnis 6. Die Funktion wird in diesem Beispiel von außen (der globalen Variable) beinflusst. Genau aus diesem Grund, um eine Abhängigkeit von einem Bereich außerhalb der Funktion zu verhindern, ist es ebenfalls nicht möglich, eine Referenz als Parameter zu übergeben. Erhält eine Funktion eine Referenz, könnte die Funktion einen Zustand/Wert außerhalb ihres Bereiches über die Referenz verändern und damit andere Funktionen bzw. Zustände beeinflussen. Diese Veränderung von Bereichen außerhalb der Funktion ist unter dem Begriff <a href="http://de.wikipedia.org/wiki/Seiteneffekt">Seiteneffekte</a> bekannt. Um Seiteneffekte zu verhindern, wird daher nur das <a href="http://de.wikipedia.org/wiki/Call_by_value">Call-by-Value</a> Konzept angewandt. Eine Funktion kann daher nur über die Parameter und dem Return-Wert mit der &#8220;Außenwelt&#8221; kommunizieren.</p>
<h3><strong>Keine Schleifen?</strong></h3>
<p>Da Schleifen Zählvariablen besitzen, sind diese in strengen funktionalen Konzepten ebenfalls nicht erlaubt. Ersetzt wird die Mächtigkeit von Schleifen durch die Rekursion. Die folgende <em>while</em> Schleife</p>
<pre><code>$x = 1;
while ($x &lt;= 10) {
  $x += 1;
}</code></pre>
<p>kann zum Beispiel rekursiv durch die folgende Funktion abgebildet werden.</p>
<pre><code>function bisZehn($x) {
  if ($x &gt;= 10) return $x;
  else return bisZehn($x+1);
}
$x = bisZehn(1);</code></pre>
<h3><strong>Funktionen als Parameter?</strong></h3>
<p>Da es keine klassischen Variablen gibt und jeder Ausdruck als auswertbare Funktion angesehen wird, können als Parameter nur Funktionen übergeben werden. Dieses Feature (sogenannte Funktionen höherer Ordnung oder <a href="http://en.wikipedia.org/wiki/Higher-order_function">Higher-Order Functions</a>) ist auch seit der PHP Version 5.3 vorhanden und ermöglicht zum Beispiel folgende Konstrukte:</p>
<pre><code>function array_map($array,$funktion) {
  for ($i=0; $i &lt; count($array); $i++) {
    $array[$i] = $funktion($array[$i]);
  }
  return $array;
}

//oder streng funktional ohne for Schleife mit Rekursion implementiert

function array_map($array,$funktion,$index) {
  if ($index &gt;= count($array)) {
    return array();
  }
  return array_merge(
    array($funktion($array[$index])),
    array_map($array,$funktion,$index+1)
  );
}

array_map($mein_array,function($element) { return $element+5; },0);
array_map($mein_array,function($element) { return md5($element); },0);

</code></pre>
<p>Die Funktion <em>array_map</em> wendet dabei eine beliebige übergebene Funktion auf das übergebene Array an. Der erste Aufruf würde zum Beispiel zu jedem Element 5 addieren. Der zweite Aufruf übergibt eine anonyme Funktion, die den md5 Hash Wert für einen Parameter $element berechnet, und liefert daher als Gesamtergebnis das Array mit den md5 Hash Werten der originalen Array-Elemente.<br />
Der große Vorteil besteht hierbei in der flexiblen Wiederverwendbarkeit der Funktion <em>array_map</em>. Diese kann mit beliebigen Funktionen kombiniert werden und spart viel Programmierarbeit. Natürlich ist es nicht sinnvoll in PHP streng funktional zu programmieren und keine Schleifen und Variablen mehr zu verwenden. Auch im Beispiel der Funktion <em>array_map</em> ist ersichtlich, dass der rein funktionale Code nicht unbedingt einfacher und verständlicher wird. Funktionale Sprachen sind dagegen für das funktionale Paradigma optimiert, wie das folgende Beispiel derselben Funktion <em>array_map</em> in der funktionalen Programmiersprache <a href="http://de.wikipedia.org/wiki/Ocaml">OCaml</a> zeigt.</p>
<pre><code>let rec array_map my_fun = function
| [ ] -&gt; [ ]
| x::xs -&gt; [my_fun x] @ array_map my_fun xs;;</code></pre>
<blockquote><p><em>OCaml Code Erklärung:</em> Die | Zeilen dienen hierbei als Vergleichsstruktur (Pattern Matching) und funktionieren ähnlich wie ein <em>switch</em> Befehl. Ist das übergebene Array (bzw. in diesem Fall eine Liste) leer, entspricht also [ ], wird eine leere Liste zurückgegeben. Sind noch Elemente in der Liste, wird diese in das erste Element (x) und eine Restliste (xs) aufgetrennt. Auf das erste Element wird die übergebene Funktion <em>my_fun</em> angewandt und die Funktion <em>array_map</em> rekursiv mit der Restliste aufgerufen. Das Symbol <em>@</em> verbindet zwei Listen und führt in dem Beispiel die resultierenden Listen der rekursiven Aufrufe zusammen. Das Ergebnis ist eine Liste, in der auf alle ursprünglichen Elemente die übergebene Funktion <em>my_fun</em> angewandt wurde.</p></blockquote>
<h3><strong>Was bringts?</strong></h3>
<p>Bisher waren funktionale Programmiersprachen vor allem in der akademischen Welt anzutreffen, da sie der Mathematik sehr nahe stehen und daher im wissenschaftlichen Bereich oft Anwendung finden. Klassische Vertreter sind <a href="http://de.wikipedia.org/wiki/Haskell_%28Programmiersprache%29">Haskell</a>, <a href="http://de.wikipedia.org/wiki/Erlang_%28Programmiersprache%29">Erlang</a>, <a href="http://de.wikipedia.org/wiki/Ocaml">OCaml</a>, <a href="http://de.wikipedia.org/wiki/Scala_%28Programmiersprache%29">Scala</a>, <a href="http://de.wikipedia.org/wiki/Scheme">Scheme</a>, <a href="http://de.wikipedia.org/wiki/Lisp">Lisp</a>, aber auch moderne Sprachen wie zum Beispiel PHP, integrieren Features der funktionalen Welt. Einen wichtigen Beitrag zur Verbreitung hat sicher auch JavaScript geleistet, da hier stark auf funktionale Sprachkonstrukte zurückgegriffen wird.<br />
<strong>Doch welche Vorteile ergeben sich durch die Verwendung von funktionalen Sprachen bzw. ihren Features?</strong><br />
Die Verwendung von Funktionen als Parameter (Higher-Order Functions) ist eine der wichtigsten Funktionalitäten, die einem das Programmierleben stark vereinfachen können. Sie sind meist wesentlich flexibler und können daher öfters im Programmcode wiederverwendet werden. Zusätzlich ist es möglich, komplexe Abläufe zu abstrahieren und dann in beliebigen Bereichen wiederzuverwenden. Dank PHP 5.3 ist das nun auch für den PHP Programmierer möglich.<br />
Das funktionale Konzept ist auch keineswegs nur ein akademisches Hirngespinst, sondern ist seit langer Zeit auch im produktiven Einsatz (z.B. <a href="http://de.wikipedia.org/wiki/Erlang_%28Programmiersprache%29">Anwendungen in der Telekommunikation mit Erlang</a>) anzutreffen.<br />
Ein weiterer Vorteil, der vor allem durch die starke Zunahme der Parallelrechner (Quad-Core ist bereits im Consumer-Bereich der Standard) immer wichtiger wird, ist die einfache Möglichkeit zur Parallelisierung. Da in streng funktionalen Programmiersprachen keine Seiteneffekte auftreten können, kann eine Funktion unabhängig vom restlichen Programm zum Beispiel auf einem anderen Prozessor oder Rechner ausgeführt werden. Auch Google bedient sich mit dem MapReduce Konzept (beschrieben im phphatesme Artikel <a href="http://www.phphatesme.com/blog/mysql/und-das-leben-nach-sql-geht-weiter-jetzt-wird-reduziert/">…und das Leben nach SQL geht weiter … jetzt wird reduziert!</a>) diesem Vorteil und vereinfacht so die Verteilung von Tasks (z.B. Suchindexerzeugung) über mehrere tausend Server hinweg.<br />
Auch die laufende Integration von funktionalen Sprachfeatures in aktuellen modernen Programmiersprachen zeigt zusätzlich, dass es sich hierbei um ein gelungenes Konzept handelt, das die Programmierung stark vereinfachen kann. Eine tiefere Auseinandersetzung mit diesem Thema ist also auch für den klassischen PHP Programmierer sinnvoll und erweitert nicht nur den imperativen Tellerrand, sondern ermöglicht auch eine Verbesserung des Programmierstils bzw. Ausnützung modernen Sprachfeatures in PHP. PHPhatesme plant ebenfalls weitere Beiträge zu diesem Thema &#8211; über Anregungen und Input freuen wir uns natürlich auch in den Kommentaren.</p>
<p>Hier wie immer noch einige weiterführende Links:</p>
<ul>
<li><a href="http://de.wikipedia.org/wiki/Funktionale_Programmierung">Wikipedia: Funktionale Programmierung</a></li>
<li><a href="http://www.recessframework.org/page/functional-php-anonymous-functions-lambdas-closures">Functional PHP 5.3</a></li>
<li>Unterlagen zur funktionalen Programmierung der <a href="http://www.complang.tuwien.ac.at/knoop/fp185161_ws0910#lehrbehelfe">Universität Wien</a>, <a href="http://cl-informatik.uibk.ac.at/teaching/ws09/fp/schedule.php">Universität Innsbruck</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.phphatesme.com/blog/softwaretechnik/wer-braucht-variablen-die-funktionale-welt/feed/</wfw:commentRss>
		<slash:comments>25</slash:comments>
		</item>
		<item>
		<title>Reguläre Ausdrucke (PCRE)</title>
		<link>http://www.phphatesme.com/blog/webentwicklung/regulare-ausdrucke-pcre/</link>
		<comments>http://www.phphatesme.com/blog/webentwicklung/regulare-ausdrucke-pcre/#comments</comments>
		<pubDate>Wed, 03 Mar 2010 14:00:00 +0000</pubDate>
		<dc:creator>Nils Langner</dc:creator>
				<category><![CDATA[Akademischer Tag]]></category>
		<category><![CDATA[Vorträge]]></category>
		<category><![CDATA[Webentwicklung]]></category>

		<guid isPermaLink="false">http://www.phphatesme.com/blog/webentwicklung/regulare-ausdrucke-pcre/</guid>
		<description><![CDATA[]]></description>
			<content:encoded><![CDATA[<p>Understanding Regular Expressions</p>
]]></content:encoded>
			<wfw:commentRss>http://www.phphatesme.com/blog/webentwicklung/regulare-ausdrucke-pcre/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Suchbäume &#8211; Akademischer Tag 6</title>
		<link>http://www.phphatesme.com/blog/akademischer-tag/suchbaume-akademischer-tag-6/</link>
		<comments>http://www.phphatesme.com/blog/akademischer-tag/suchbaume-akademischer-tag-6/#comments</comments>
		<pubDate>Fri, 27 Nov 2009 06:00:09 +0000</pubDate>
		<dc:creator>Andre Moelle</dc:creator>
				<category><![CDATA[Akademischer Tag]]></category>

		<guid isPermaLink="false">http://www.phphatesme.com/?p=4487</guid>
		<description><![CDATA[Nachdem es beim letzten akademischen Tag um Hashmaps, einer Struktur zum Lösen des sog. Suchproblems, ging, wird auch dieser Artikel von einer Technik handeln, mit der dieses Problem gelöst werden kann. Dieses Mal wird der Ansatz jedoch auf der Graphentheorie basieren, d.h. wir werden einige Graphen mit besonderen Strukturen kennenlernen, mit denen man das Suchproblem möglichst schnell lösen kann. Bei diesen Graphen wird es sich, wie bereits im Titel angedeutet, um Bäume handeln.]]></description>
			<content:encoded><![CDATA[<p>Nachdem es beim letzten akademischen Tag um <a href="http://www.phphatesme.com/blog/akademischer-tag/hashmaps-akademischer-tag-5/">Hashmaps</a>, einer Struktur zum Lösen des sog. <a href="http://de.wikipedia.org/wiki/Suchproblem">Suchproblems</a>, ging, wird auch dieser Artikel von einer Technik handeln, mit der dieses Problem gelöst werden kann. Dieses Mal wird der Ansatz jedoch auf der <a href="http://www.phphatesme.com/blog/akademischer-tag/einfuehrung-in-die-graphentheorie/">Graphentheorie</a> basieren, d.h. wir werden einige Graphen mit besonderen Strukturen kennenlernen, mit denen man das Suchproblem möglichst schnell lösen kann. Bei diesen Graphen wird es sich, wie bereits im Titel angedeutet, um Bäume handeln.<span id="more-4487"></span></p>
<p>Zuerst möchte ich die Definitions eines gerichteten Baums ins Gedächtnis rufen, damit keine Missverständnisse auftreten: <em>Ein gerichteter Baum, dessen Kantenrichtung zu den Blättern zeigt, ist ein Graph, bei dem es für jeden Knoten x genau einen Pfad von der Wurzel zu x gibt.</em> Bereits aus der Definition ergibt sich eine Eigenschaft, die bei der Suche hilft: Wenn zu jedem Knoten nur ein Pfad existiert, dann kann man nach einem <a href="http://www.phphatesme.com/blog/akademischer-tag/graphtraversierungen-akademischer-tag-4/">Traversierungsschritt</a> die Knoten, die nicht in derselben Richtung liegen, nicht mehr erreichen (ohne einen Schritt zurückzugehen), womit der Suchraum bei jedem Schritt auf ein Minimum reduziert wird. Optimalerweise wird der Baum derart strukturiert, dass man bei der Traversierung des Baums sofort den richtigen Weg geht, sodass man bei einer Baumhöhe <em>h</em> insgesamt <em>O(h)</em> Schritte zum Suchen eines Schlüssels benötigt, womit der o.g. Vorteil zum Tragen kommt. Eine weitere hilfreiche Eigenschaft ist die Kreisfreiheit, die sich leicht aus der Definition eines Baums folgern lässt. Durch diese Kreisfreiheit kann man die verschiedenen Operationen auf Bäumen sehr einfach rekursiv implementieren, ohne Endlosschleifen zu provozieren, was vor allem Organisationsaufwand spart.</p>
<p>Der Unterschied zwischen Bäumen und Suchbäumen besteht vor allem in ihrer Struktur, denn Suchbäume sind derart strukturiert, dass sich die drei Operationen <em>insert</em>, <em>find</em> und <em>delete</em> effizient auf ihnen implementieren lassen. Bei den gängigsten Arten von Suchbäumen wird die Struktur anhand einer <a href="http://de.wikipedia.org/wiki/Ordnungsrelation">Ordnung</a> auf den Schlüsseln festgelegt. Meist handelt es sich dabei um eine sog. <a href="http://de.wikipedia.org/wiki/Ordnungsrelation#Totalordnung">Totalordnung</a>, die wir nachfolgend mit ≤ bezeichnen. Ein Beispiel für eine solche Totalordnung ist die ≤-Relation auf den natürlichen Zahlen. Nimmt man also die Menge der natürlichen Zahlen als Schlüsselmenge, so muss man nur noch den ≤-Vergleichsoperator der jeweiligen Programmiersprache benutzen und schon basiert der Baum auf einer Totalordnung. Benötigt wird eine solche Ordnung, um anhand des gesuchten Schlüssels und des Schlüssels des aktuell besuchten Knotens entscheiden zu können, wo die Suche fortgesetzt werden muss. Als Beispiel wähle ich den binären Suchbaum, der beim nächsten akademischen Tag unter die Lupe genommen wird:</p>
<ol>
<li>Ist x ≤ y und y ≤ x, d.h. x = y, dann ist die Suche beendet.</li>
<li>Ist x ≤ y, wird die Suche im linken Teilbaum fortgesetzt.</li>
<li>Ist y ≤ x, wird die Suche im rechten Teilbaum fortgesetzt.</li>
</ol>
<p>Da dies ein Artikel für den akademischen Tag ist, geben wir uns nicht mit dem Nennen von Begriffen zufrieden, sondern erläutern, was eine Totalordnung ist, damit wir Suchbäume nicht nur für natürlich aussehende Schlüsselmengen wie Zahlen verwenden können, sondern auch für komplexere Schlüssel.  Die <a href="http://de.wikipedia.org/wiki/Lexikographische_Ordnung">lexikographische Ordnung</a> von Zeichenketten wäre ein weiteres Beispiel, aber auch Arrays könnten als Schlüsselmengen verwendet werden, sofern man eine Totalordnung dafür angibt. Damit man weiß, ob man eine Totalordnung gefunden hat, definieren wir eine Totalordnung.</p>
<p>Für eine Totalordnung ≤ auf einer beliebigen Menge M gelten folgende Gesetze:</p>
<ul>
<li>≤ ist reflexiv :⇔ ∀ x ∈ M: x ≤ x
<ul>
<li>Jedes Element ist kleiner-gleich sich selbst.</li>
</ul>
</li>
<li>≤ ist antisymmetrisch :⇔ ∀ x,y ∈ M: x ≤ y und y ≤ x ⇒ y = x
<ul>
<li>Bsp.: Wenn 42 ≤ y und y ≤ 42, dann ist folglich 42 = y.</li>
</ul>
</li>
<li>≤ ist transitiv :⇔ ∀ x,y,z ∈ M: x ≤ y und y ≤ z ⇒ x ≤ z
<ul>
<li>Bsp.: Wenn 1 ≤ 2 und 2 ≤ 3, dann ist folglich 1 ≤ 3.</li>
</ul>
</li>
<li>≤ ist total :⇔ ∀ x,y ∈ M: x ≤ y oder y ≤ x
<ul>
<li>Alle Elemente sind miteinander vergleichbar.</li>
<li>Bsp.: 21 ≤ 42 oder 42 ≤ 21.</li>
</ul>
</li>
</ul>
<p>Nehmen wir an, eines dieser Gesetze würde nicht gelten, dann gäbe es an verschiedenen Stellen solcher Suchbäume Probleme:</p>
<ul>
<li>≤ ist nicht reflexiv: Da x ≤ x und x ≤ x nicht gelten, kann x = x nicht gefolgert werden, weshalb man  nicht erkennen würde, wenn man den gesuchten Schlüssel gefunden hat.</li>
<li>≤ ist nicht antisymmetrisch: Da sich x = y nicht aus x ≤ y und y ≤ x folgern ließe, wüsste man nicht, ob man den gesuchten Schlüssel gefunden hat oder in welchen Teilbaum man weitersuchen muss.</li>
<li>≤ ist nicht total: Es gibt ein Paar x, y für das weder x ≤ y noch y ≤ x gilt. Man kann also weder entscheiden, ob der gesuchte Schlüssel gefunden wurde, noch in welchem Teilbaum man weitersuchen muss.</li>
<li>≤ ist nicht transitiv: Sei x ≤ y und y ≤ z, aber es gelte nicht x ≤ z. Dann ist ≤ entweder nicht total oder es gilt stattdessen z ≤ x. Da ≤ jedoch total sein soll, betrachten wir den Fall  z ≤ x. D.h. es würde gelten x ≤ y ≤ z ≥ x, bedeuten würde, dass x im rechten und im linken Teilbaum vom Baum mit der Wurzel z zu finden sein könnte. Vor allem beim Umstrukturierungen des Baums, beispielsweise durch Löschen oder Rotation, könnte das zu Fehlern führen.</li>
</ul>
<p>Es gibt einige bekannte Suchbäume, die ich nachfolgend nennen und kurz beschreiben möchte:</p>
<ul>
<li><a href="http://de.wikipedia.org/wiki/Bin%C3%A4rer_Suchbaum">Binärer Suchbaum</a>: Basiert auf der Eigenschaft, dass alle Schlüssel des linken Teilbaums kleiner als der Schlüssel der Wurzel sind und alle Schlüssel des rechten Teilbaums größer als der o.g. Schlüssel sind.</li>
<li><a href="http://de.wikipedia.org/wiki/AVL-Baum">AVL-Baum</a>: Erweitert den binären Suchbaum um Rotationen, die ggf. beim Einfügen oder Löschen von Elementen durchgeführt werden, damit</li>
<li><a href="http://de.wikipedia.org/wiki/Rot-Schwarz-Baum">Rot-Schwarz-Baum</a>: Bei diesem Baum hat jeder Knoten eine Farbe (rot oder schwarz) für die einige Regeln gelten, durch die sichergestellt wird, dass man O(log n) Schritte zum Finden eines beliebigen Schlüssels benötigt.</li>
<li><a href="http://de.wikipedia.org/wiki/B-Baum">B-Baum</a>: Im Gegensatz zu den bereits genannten Bäumen ist dieser Baum nicht binär, d.h. er kann mehr als zwei Kindknoten haben, wodurch er flacher ist. In jedem Knoten befinden sich weitere Informationen, anhand derer er feststellen kann, welcher Knoten als nächstes besucht werden muss.</li>
<li><a href="http://de.wikipedia.org/wiki/Trie">Präfixbaum</a>: Hierbei handelt es sich um einen speziellen Suchbaum, bei dem der Schlüssel in Einzelteile zerlegt wird, um daraus die Struktur des Baums zu bestimmen. Bei  Schlüsseln, die nur aus Ziffern bestehen, hätte der Baum einen Verzweigungsgrad von 10, da eine Zahl in seine Bestandteile (Ziffern) zerlegt wird. Das Suchen nach einer n-stelligen Zahl würde dann n Schritte im Baum benötigen.</li>
</ul>
<p>Im Vergleich zu Hashmaps sind Bäume meist langsamer, aber dafür wird der Platz besser ausgenutzt und sie sind robuster, wenn es sich um Ausnahmefälle handelt. Natürlich lassen sich beide Techniken miteinander kombinieren, aber im Endeffekt kommt es auf den Anwendungsfall an, ob Bäume oder Hashmaps zu bevorzugen sind. Beispielsweise sind Hashmaps in Datenbanken quasi nicht nutzbar, da die Datenmengen häufig größer sind als der Arbeitsspeicher Platz bietet. Da die meisten Daten deshalb auf der Festplatte gespeichert werden müssen, kommt der B-Baum zum Einsatz, der durch seine geringe Höhe ein Minimum an Festplattenzugriff garantiert.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.phphatesme.com/blog/akademischer-tag/suchbaume-akademischer-tag-6/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Hashmaps &#8211; Akademischer Tag 5</title>
		<link>http://www.phphatesme.com/blog/akademischer-tag/hashmaps-akademischer-tag-5/</link>
		<comments>http://www.phphatesme.com/blog/akademischer-tag/hashmaps-akademischer-tag-5/#comments</comments>
		<pubDate>Fri, 23 Oct 2009 05:00:44 +0000</pubDate>
		<dc:creator>Andre Moelle</dc:creator>
				<category><![CDATA[Akademischer Tag]]></category>

		<guid isPermaLink="false">http://www.phphatesme.com/?p=4306</guid>
		<description><![CDATA[]]></description>
			<content:encoded><![CDATA[<p>Beim heutigen akademischen Tag geht es um eine Datenstruktur, die jeder PHP-Entwickler bereits verwendet hat &#8211; wahrscheinlich ohne es zu wissen: <em>Hashmaps</em>. Wem dieser Begriff nichts sagt: Assoziative Arrays werden i.d.R. als Hashmaps implementiert, so auch in PHP. Deshalb werden heute die Hintergründe ein wenig näher beleuchtet.<span id="more-4306"></span></p>
<p>Der Begriff setzt sich aus den beiden englischen Begriffen <em>hash</em>, der eine Hashfunktion bezeichnet, und <em>map</em> (dt. Abbildung) zusammen. Geläufiger dürfte die Bezeichnung Funktion für eine (mathematische) Abbildung sein. Was versteht man unter Funktion? Eine Beziehung zwischen zwei Mengen, dem Definitions- und dem Wertebereich.  Dabei ordnet eine Funktion jedem Element des Definitionsbereichs genau ein Element aus dem Wertebereich zu. In der Schule werden zur Beschreibung von Funktionen häufig Tabellen benutzt, um diese Relationen zu beschreiben. Dadurch erschließt sich auch der deutsche Begriff <em>Hashtabelle</em>.</p>
<p>Nicht nur der Begriff Hashmap, sondern auch die Datenstruktur Hashmap setzt sich aus einer Abbildung und einer Hashfunktion zusammen. In diesem Kontext bezeichnen wir den Definitionsbereich als <em>Schlüsselmenge</em> und den Wertebereich als <em>Objektmenge</em>. D.h. die Funktion bildet einen Schlüssel auf ein Objekt ab. Der Begriff Objekt ist hierbei nicht zu verwechseln mit dem gleichnamigen objektorientierten Konzept. Deshalb kann die Objektmenge natürlich auch skalare Objekte, z.B. Strings, enthalten. Da wir nun wissen, welchen Zweck man mit einer Hashmap verfolgt, nämlich das Speichern und Auslesen von Objekten anhand von Schlüsseln, können wir dazu übergehen, wie eine Hashmap implementiert wird. Hierfür ist die bereits erwähnte Hashfunktion der Namensgeber. Um Missverständnisse zu vermeiden, muss noch geklärt werden, was eine Hashfunktion ist: Eine Funktion, die einem Objekt einen numerischen Wert zuordnet. Das prominente Beispiel hierfür ist die Funktion <em>md5</em>, die einem String einen hexadezimalen Wert zuordnet. In PHP könnte man eine Hashfunktion schreiben, die eine Variable <em>$obj</em> auf <em>hexdec(md5(serialize($obj)))</em> abbildet. Durch die Serialisierung wird sichergestellt, dass <em>md5</em> den MD5-Hash des Objekts berechnen kann. Da der Rückgabewert von <em>md5</em> hexadezimal ist, ist die Funktion <em>hexdec</em> notwendig, um dem hexadezimalen Wert den gleichen dezimalen Wert zuzuordnen.</p>
<p>Nachdem wir die begrifflichen Voraussetzungen geschaffen haben, wird es Zeit, die Verwendung der Hashfunktion zu erläutern. Wir benutzen die Hashfunktion, die nachfolgend mit <em>hash</em> bezeichnet wird, um den Speicherort eines Objekts anhand seines Schlüssels zu bestimmen. Hierfür werden Arrays mit numerischen Indizes, wie z.B. <a title="SplFixedArray" href="http://de3.php.net/manual/de/class.splfixedarray.php" target="_blank">SplFixedArray</a>, verwendet. Damit diese Zuordnung funktioniert, ist es notwendig, dass das Array stets dieselbe Länge hat. Angenommen, die Länge sei mit <em>n</em> begrenzt, dann suchen wir eine Funktion <em>f</em>, die jedem Element der Schlüsselmenge ein Element aus {0, &#8230;, n-1} zuordnet. <em>f</em> ließe sich dann folgendermaßen definieren: <em>f(key) = hash(key) modulo n</em>. D.h. erst wird der Hash eines Schlüssels generiert und anschließend wird <em>modulo n</em>, d.h. der Rest bei einer ganzzahligen Division durch <em>n</em>, berechnet. Durch die modulo-Operation ergibt sich auch die Einschränkung der konstanten Länge des Arrays. Mit <em>f</em> haben wir nun eine Funktion gefunden, die jedem Schlüssel einen Speicherort im Array zuweist. Da diese Funktion <a href="http://www.phphatesme.com/blog/akademischer-tag/akademischer-tag-1-o-notation/" target="_blank">O(1)</a>-Schritte zum Berechnen der Position benötigt, lassen sich auch die beiden Operationen zum Einfügen und Auslesen von Objekten in O(1) realisieren. Die Schritte beim Einfügen und Auslesen können folgendermaßen zusammengefasst werden:</p>
<ul>
<li>insert(key, obj):
<ol>
<li>Position <em>p</em> anhand von <em>f(key)</em> berechnen.</li>
<li><em>obj</em> an Position <em>p</em> schreiben.</li>
</ol>
</li>
<li>read(key):
<ol>
<li>Position <em>p</em> anhand von <em>f(key)</em> berechnen.</li>
<li>Lesen an Position <em>p</em> und ggf. Objekt zurückgeben.</li>
</ol>
</li>
</ul>
<p>Dieser Algorithmus und die obere Schranke gelten allerdings nur für den besten Fall, in dem keine sog. Kollisionen auftreten. Bei einer Kollision erhalten zwei Schlüssel dieselbe Position innerhalb des Arrays. <span style="background-color: #ffffff;">Um dieses Problem zu lösen, gibt es zwei gängige Verfahren:<span style="background-color: #ffffff;"> </span><span style="background-color: #ffffff;"><em>Hashing mit Verkettung</em> und <em>Hashing mit offener Adressierung</em>.</span></span></p>
<p><span style="background-color: #ffffff;"><span style="background-color: #ffffff;">Beim <em>Hashing mit Verkettung</em> enthält jedes Array-Element sog. <em>Buckets</em> (dt. Behälter), in denen die kollidierten Objekte mitsamt Schlüssel enthalten sind. Im trivialen Fall handelt es bei den Buckets um Arrays, aber natürlich sind auch komplexere Datenstrukturen wie Suchbäume möglich. Die Prozeduren zum Einfügen und Auslesen müssen dann folgendermaßen modifiziert werden:</span></span></p>
<ul>
<li>insert(key, obj):
<ol>
<li>Position <em>p</em> anhand von<em> f(key)</em> berechnen.</li>
<li>Wenn <em>key</em> im Bucket der Position <em>p</em> existiert, wird das Paar <em>(key, obj)</em> aktualisiert, ansonsten wird es eingefügt.</li>
</ol>
</li>
<li>read(key):
<ol>
<li>Position <em>p</em> anhand von <em>f(key)</em> berechnen.</li>
<li>Wenn <em>key</em> im Bucket der Position <em>p</em> existiert, wird das zugehörige Objekt zurückgegeben.</li>
</ol>
</li>
</ul>
<p><span style="background-color: #ffffff;">Im schlimmsten Fall sind alle Elemente der Hashmap in einem Bucket, womit sich die Komplexität verschlechtert, z.B. auf O(log n) bei guten Suchbäumen.</span></p>
<p><span style="background-color: #ffffff;">Eine Alternative stellt das <em>Hashing mit offener Adressierung</em> dar. Hierbei wird eine weitere Funktion <em>g</em> eingeführt, die zum Generieren einer Folge von Positionen verwendet wird. Die einfachste Funktion dieser Bauart ist die sog. Nachfolgerfunktion, die einer Zahl seinen Nachfolger zuordnet. Bei diesem Verfahren enthält jedes Array-Element nur das Objekt mitsamt Schlüssel. Natürlich müssen die Prozeduren zum Einfügen und Auslesen auch für diese Strategie angepasst werden.</span></p>
<ul>
<li>insert(key, obj):
<ol>
<li>Position <em>p</em> anhand von<em> f(key)</em> berechnen.</li>
<li>Ist das Array an Position <em>p</em> leer, so wird<em> (key, obj)</em> an Position <em>p</em> eingefügt und die Prozedur beendet.</li>
<li>Passt der Schlüssel an Position <em>p</em> zu <em>key</em>, so wird das vorhandene Objekt durch <em>obj</em> ersetzt und die Prozedur beendet.</li>
<li>Setze <em>p = g(p) modulo n</em>.</li>
<li>Fahre bei 2. fort.</li>
</ol>
</li>
<li>read(key):
<ol>
<li>Position <em>p</em> anhand von <em>f(key)</em> berechnen.</li>
<li>Ist das Array an Position <em>p</em> leer, so wird <em>null</em> zurückgegeben.</li>
<li>Passt der Schlüssel an Position <em>p</em> zu <em>key</em>, so wird das Objekt an Position <em>p</em> zurückgegeben.</li>
<li>Setze <em>p = g(p) modulo n</em>.</li>
<li>Fahre bei Schritt 2 fort.</li>
</ol>
</li>
</ul>
<p><span style="background-color: #ffffff;">Wie man sieht, ist hier eine Endlosschleife möglich. Dieser Fall kann auftreten, wenn eine ungünstige Funktion <em>g</em>, wie z.B. die identische Funktion <em>id</em> mit <em>id(x) = x</em>, gewählt wurde, oder alle Positionen des Arrays bereits besetzt sind.  Zum Verhindern der zuletzt genannten Situation, kann man das Array vergrößern und die enthaltenen Elemente umordnen. Auch beim <em>Hashing mit Verkettung</em> ist das Vergrößern des Arrays sinnvoll, da man damit die Anzahl der Kollisionen reduziert und somit im Mittel eine geringere Zugriffszeit erhält.</span></p>
<p>Resümierend lässt sich sagen, dass Hashmaps aufgrund der bestmöglichen Best-Case-Laufzeit von O(1) und einer Worst-Case-Laufzeit von O(log n) bzw. O(n) zu den schnellsten Datenstrukturen zählen. Dieser Vorteil wird durch die Nutzung einer geeigneten Hashfunktion realisiert, mit der das Suchen im günstigsten Fall nicht notwendig ist, da sich die Position im Array in einem konstanten Schritt berechnen lässt. Eben diese Funktion fehlt noch <span style="background-color: #ffffff;">für eine effiziente Implementierung in nativem PHP, wohingegen die restlichen Strategien ausreichend beschrieben wurden, um sie implementieren zu können.</span></p>
]]></content:encoded>
			<wfw:commentRss>http://www.phphatesme.com/blog/akademischer-tag/hashmaps-akademischer-tag-5/feed/</wfw:commentRss>
		<slash:comments>13</slash:comments>
		</item>
		<item>
		<title>Graphtraversierungen &#8211; Akademischer Tag 4</title>
		<link>http://www.phphatesme.com/blog/akademischer-tag/graphtraversierungen-akademischer-tag-4/</link>
		<comments>http://www.phphatesme.com/blog/akademischer-tag/graphtraversierungen-akademischer-tag-4/#comments</comments>
		<pubDate>Mon, 31 Aug 2009 05:00:13 +0000</pubDate>
		<dc:creator>Andre Moelle</dc:creator>
				<category><![CDATA[Akademischer Tag]]></category>

		<guid isPermaLink="false">http://www.phphatesme.com/?p=3805</guid>
		<description><![CDATA[]]></description>
			<content:encoded><![CDATA[<p>Nachdem wir im <a href="http://www.phphatesme.com/blog/akademischer-tag/einfuehrung-in-die-graphentheorie/" target="_blank">letzten Artikel</a> geklärt haben, was Graphen sind und wie man sie im Computer darstellen kann, geht es heute darum, wie man Graphen traversieren kann, d.h. es geht darum, jeden Knoten des Graphen zu besuchen. Hierfür gibt es zwei fundamentale Strategien: Die Breitensuche und die Tiefensuche.<span id="more-3805"></span></p>
<p>Der Einfachheit halber werde ich Breiten- und Tiefensuche nur auf folgenden Graphen anwenden:</p>
<div id="attachment_3809" class="wp-caption aligncenter" style="width: 366px"><img class="size-full wp-image-3809" src="http://www.phphatesme.com/upload/2009/08/Graph4.png" alt="Beispielgraph" width="356" height="539" /><p class="wp-caption-text">Beispielgraph</p></div>
<p>Es handelt sich dabei um einen ungerichteten und zusammenhängenden Graphen. Die Verwendung eines ungerichteten Graphen macht dabei keinen wesentlichen Unterschied, da die gesamte Argumentation sehr leicht auf gerichtete Graphen übertragen werden kann. Für einen ungerichteten Graphen G = (V,E) bedeutet der Begriff &#8220;zusammenhängend&#8221; nichts anderes, als dass für je zwei Knoten v, w ∈ V (mindestens) einen Pfad von v nach w bzw. von w nach v existiert. Der Vollständigkeit halber sollte noch eine Definition für einen Pfad angegeben werden: <em>Ein Pfad ist ein Tupel (v</em><sub><em>1</em></sub><em>, v<sub>2</sub>, &#8230;, v</em><sub><em>n</em></sub><em>) mit {{v</em><sub><em>1</em></sub><em>,v</em><sub><em>2</em></sub><em>}, {v</em><sub><em>2</em></sub><em>,v</em><sub><em>3</em></sub><em>}, &#8230;, {v</em><sub><em>n-1</em></sub><em>, v</em><sub><em>n</em></sub><em>}} ⊆ E und v<sub>i</sub> ≠ v<sub>j</sub> für i ≠ j.</em> Anschaulich: Ein Pfad ist eine Folge von unterschiedlichen Knoten, bei dem für alle benachbarten Knoten gilt, dass sie miteinander verbunden sind. <span style="background-color: #ffffff;">Da Breiten- und Tiefensuche in PHP implementiert werden, bietet es sich an, den Beispielgraphen als Adjazenzliste in PHP-Code darzustellen:</span></p>
<pre style="font: normal normal normal 12px/18px Consolas, Monaco, 'Courier New', Courier, monospace;"><code>$adj = array(
	'A' =&gt; array('B','C','D'),
	'B' =&gt; array('A','E','H'),
	'C' =&gt; array('A','D','F','G'),
	'D' =&gt; array('A','C','E','G','H','I'),
	'E' =&gt; array('B','D','H'),
	'F' =&gt; array('C','G','I'),
	'G' =&gt; array('C','D','F','I'),
	'H' =&gt; array('B','D','E','I'),
	'I' =&gt; array('D','F','G','H')
);</code></pre>
<p>Traversiert man einen Graphen, so erhält man eine Traversierungsreihenfolge und einen Traversierungsbaum. Zwar ist der Begriff eines Baums geläufig, aber dennoch möchte ich ihn präzise definieren: <em>Ein ungerichteter Baum ist ein Graph, bei dem es zwischen je zwei Knoten x und y genau einen Pfad von x nach y bzw. von y nach x gibt.</em> Diese Definition lässt sich leicht auf gerichtete Graphen übertragen: <em>Ein gerichteter Baum, dessen Kantenrichtung zu den Blättern zeigt, ist ein Graph, bei dem es für jeden Knoten x genau einen Pfad von der Wurzel zu x gibt.</em> Blätter sind hierbei die Knoten, die keine ausgehende Kanten haben, und die Wurzel ist der (einzige) Knoten, der keine eingehende Kante hat.</p>
<p>Nachdem alle für das Verständnis notwendigen Begriffe erläutert wurden, können wir mit der Breitensuche (engl. <strong>B</strong>readth <strong>F</strong>irst <strong>S</strong>earch) beginnen, die im vorangegangen Artikel bereits erwähnt wurde. <span style="background-color: #ffffff;">Der Einfachheit halber wird die BFS-Prozedur, die ich vorstelle, lediglich die Reihenfolge der Knoten darstellen, in der sie besucht wurden &#8211; also das Aufstellen der <a href="http://de.wikipedia.org/wiki/Traversierung" target="_blank">Traversierungsreihenfolge</a>.</span></p>
<p>Das Herz der BFS-Prozedur ist die Verwendung einer Warteschlange (<a href="http://de3.php.net/manual/de/class.splqueue.php" target="_blank">SplQueue</a>), die nach dem <a href="http://de.wikipedia.org/wiki/FIFO" target="_blank">FIFO</a>-Prinzip arbeitet, d.h. für je zwei Knoten, die in die Warteschlange eingefügt wurden, gilt, dass derjenige, der vorher eingefügt wurde, auch vor dem anderen Knoten entnommen wird. Dadurch entsteht das Verhalten, das man als Breitensuche charakterisiert: Wird ein Knoten bearbeitet, so werden alle seine benachbarten (und noch nicht besuchten) Knoten der Breite nach in die Warteschlange eingefügt <strong>und</strong> sie werden später in derselben Reihenfolge entnommen.</p>
<p>Natürlich benötigt die BFS-Prozedur nicht nur einen Graphen, auf dem sie ausgeführt werden soll, sondern auch einen Startknoten, der mit $start bezeichnet wird.</p>
<pre style="font: normal normal normal 12px/18px Consolas, Monaco, 'Courier New', Courier, monospace;"><code>function bfs ($adj, $start) {
	$const = function($val) { return function($x) use ($val) { return $val; };};
	$base = array_flip(array_keys($adj));
	$result = array_map($const(0), $base);
	$visited = array_map($const(false), $base);

	$queue = new SplQueue();
	$queue-&gt;enqueue($start);
	$visited[$start] = true;
	$number = 1;

	foreach($queue as $item) {
		$result[$item] = $number++;

		foreach($adj[$item] as $next) {
			if(!$visited[$next]) {
				$queue-&gt;enqueue($next);
				$visited[$next] = true;
			}
		}
	}

	return $result;
}</code></pre>
<p><span style="background-color: #ffffff;"><em>bfs($adj, &#8216;A&#8217;)</em> ergibt <em>array(&#8216;A&#8217; =&gt; 1, &#8216;B&#8217; =&gt; 2, &#8216;C&#8217; =&gt; 3, &#8216;D&#8217; =&gt; 4, &#8216;E&#8217; =&gt; 5, &#8216;F&#8217; =&gt; 7, &#8216;G&#8217; =&gt; 8, &#8216;H&#8217; =&gt; 6, &#8216;I&#8217; =&gt; 9)</em>. Aber nicht nur die BFS-Nummerierung, sondern auch der sog. Traversierungsbaum ist interessant. Die Kantenrelation dieses Baums lässt sich folgendermaßen beschreiben: Eine gerichtete Kante (x,y) existiert genau dann, wenn y beim Bearbeiten von x in die Warteschlange eingefügt wurde. <span style="background-color: #ffffff;">Dass es sich dabei um einen Baum handelt, lässt sich leicht mit der oben angegebenen Definition erklären. Da jeder Knoten genau einmal eingefügt wird, hat jeder Knoten (mit Ausnahme der Wurzel) maximal eine eingehende Kante. Dadurch kann es von der Wurzel zu jedem Knoten maximal einen Weg geben und die Existenz eines solchen Wegs ist bei einem zusammenhängenden Graphen trivialerweise erfüllt. Folglich gibt es für jeden Knoten x genau einen Weg von der Wurzel zu x.</span></span></p>
<div id="attachment_3811" class="wp-caption aligncenter" style="width: 519px"><img class="size-full wp-image-3811" src="http://www.phphatesme.com/upload/2009/08/Graph51.png" alt="Traversierungsbaum" width="509" height="251" /><p class="wp-caption-text">Traversierungsbaum</p></div>
<p>Der Anschaulichkeit halber erhält jeder Knoten eine neue Bezeichnung, die sich aus seiner alten Bezeichnung und BFS-Nummerierung zusammensetzt. Auffällig ist dabei, dass die Zahlenfolge in jeder Ebene von links nach rechts aufsteigend ist. Diese Eigenschaft lässt sich verallgemeinern: <em>Sei (w,1) die Wurzel und seien (x,n) und (y,m) zwei beliebige Knoten im Traversierungsbaum, so gilt, wenn  n &lt; m, dann gibt es keinen Weg von w nach y, der kürzer ist als der Weg von w nach x.</em> Weshalb gilt diese Eigenschaft? Das lässt sich aus der FIFO-Eigenschaft folgern: Beginnend mit Startknoten werden alle Knoten, die direkt mit dem Startknoten verbunden sind, in die Warteschlange eingefügt. D.h. alle Knoten in der Warteschlange haben eine Entfernung, also die Anzahl der Kanten zwischen zwei Knoten, von 1, da sie direkt mit dem Startknoten verbunden sind. Werden diese Knoten abgearbeitet, werden ans Ende der Warteschlange alle Knoten eingefügt, die eine Entfernung von 2 zum Startknoten haben. Dieses Verfahren wird solange fortgesetzt wie es Knoten in der Warteschlange gibt. Anhand dieser Eigenschaft lässt sich auch erklären, weshalb die Breitensuche das Kürzester-Weg-Problem auf ungewichteten Graphen löst. Das Kürzester-Weg-Problem lässt sich auf gewichteten Graphen übrigens sehr ähnlich lösen: Statt einer Warteschlange wird eine <a href="http://de.wikipedia.org/wiki/Priorit%C3%A4tswarteschlange" target="_blank">Prioritätswarteschlange</a> verwendet, in der die Knoten nach der Entfernung (anhand der Kantengewichte) sortiert sind. Eine Einschränkung muss hierbei allerdings gemacht werden: Negative Kantengewichte funktionieren mit obigen Ansatz im Allgemeinen nicht. In der englischen Wikipedia finden sich weitere <a href="http://en.wikipedia.org/wiki/Breadth_first_search#Applications" target="_blank">Anwendungen für Breitensuche</a>.</p>
<p>Im Gegensatz zur Breitensuche verwendet die Tiefensuche (engl. <strong>D</strong>epth <strong>F</strong>irst <strong>S</strong>earch) einen Stapel (<a href="http://de3.php.net/manual/de/class.splstack.php" target="_blank">SplStack</a>) oder wird rekursiv implementiert, woraus das <a href="http://de.wikipedia.org/wiki/Last_In_%E2%80%93_First_Out" target="_blank">LIFO-Prinzip</a> folgt. Konkret bedeutet das, dass man alle benachbarten Knoten auf einem Stapel speichert und das Element auf dem Stapel als nächsten Knoten wählt. Durch die Verwendung des Stapels liegt immer ein Knoten oben, der möglichst tief liegt. Um dabei nicht zu sehr an die Vorstellungskraft zu appellieren: Ein Knoten x liegt genau dann tiefer als y, wenn der Pfad, der zu x geführt hat, länger ist als derjenige, der zu y geführt hat. D.h. man geht solange in die Tiefe bis es nicht mehr möglich ist. Von diesem Knoten aus geht man wieder zurück, bis man zu einem Knoten gelangt, an dem man die Tiefensuche fortsetzen kann. Das lässt sich sehr schön an der DFS-Nummerierung zeigen: <em>dfs($adj, &#8216;A&#8217;)</em> gibt <em>array(&#8216;A&#8217; =&gt; 1, &#8216;B&#8217; =&gt; 2, &#8216;C&#8217; =&gt; 5, &#8216;D&#8217; =&gt; 4, &#8216;E&#8217; =&gt; 3, &#8216;F&#8217; =&gt; 6, &#8216;G&#8217; =&gt; 7, &#8216;H&#8217; =&gt; 9, &#8216;I&#8217; =&gt; 8 )</em> zurück. Beim Nachvollziehen anhand des Beispielgraphen fällt einem noch ein anderer Sachverhalt auf: Es gibt einen Pfad, mit dem man alle Knoten erreichen kann, was im Allgemeinen nicht der Fall ist. Beispielsweise gibt es für Bäume einen ähnlichen Traversierungsbaum wie er bei der Breitensuche entstanden ist. Eine konkrete Implementierung der DFS-Prozedur könnte folgendermaßen aussehen:</p>
<div style="position: absolute; left: -10000px; top: 2473px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;">Array</div>
<div style="position: absolute; left: -10000px; top: 2473px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;">(</div>
<div style="position: absolute; left: -10000px; top: 2473px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;">[A] =&gt; 1</div>
<div style="position: absolute; left: -10000px; top: 2473px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;">[B] =&gt; 2</div>
<div style="position: absolute; left: -10000px; top: 2473px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;">[C] =&gt; 5</div>
<div style="position: absolute; left: -10000px; top: 2473px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;">[D] =&gt; 4</div>
<div style="position: absolute; left: -10000px; top: 2473px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;">[E] =&gt; 3</div>
<div style="position: absolute; left: -10000px; top: 2473px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;">[F] =&gt; 6</div>
<div style="position: absolute; left: -10000px; top: 2473px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;">[G] =&gt; 7</div>
<div style="position: absolute; left: -10000px; top: 2473px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;">[H] =&gt; 9</div>
<div style="position: absolute; left: -10000px; top: 2473px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;">[I] =&gt; 8</div>
<div style="position: absolute; left: -10000px; top: 2473px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;">)</div>
<pre><code>function dfs ($adj, $start) {
	$const = function($val) { return function($x) use ($val) { return $val; };};
	$base = array_flip(array_keys($adj));
	$result = array_map($const(0), $base);
	$visited = array_map($const(false), $base);

	$stack = new SplStack();
	$stack-&gt;push($start);
	$number = 1;

	while(!$stack-&gt;isEmpty()) {
		$item = $stack-&gt;pop();

		if(!$visited[$item]) {
			$result[$item] = $number++;
			$visited[$item] = true;

			foreach(array_reverse($adj[$item]) as $next) {
				$stack-&gt;push($next);
			}
		}
	}

	return $result;
}</code></pre>
<p>Diese Implementierung benutzt einen Stapel, um den Gegensatz zur Breitensuche bzw. der Warteschlange zu verdeutlichen. Außerdem spart man durch das Verwenden eines Stapels entweder eine weitere Funktion oder überflüssige Überprüfungen der übergebenen Werte. Weitere Unterschiede zur Breitensuche sind zum einen der Zeitpunkt der Unterscheidung, ob ein Knoten bereits besucht wurde, und zum anderen die Reihenfolge, in der die Knoten auf den Stapel gelegt werden. Die umgekehrte Reihenfolge ist zwar nicht notwendig, aber sorgt dafür, dass &#8211; analog zur Breitensuche &#8211; immer der Knoten, der in der Adjazenzliste am weitesten links steht, zuerst besucht wird. Hingegen ist der Zeitpunkt der Überprüfung essentiell für die Tiefensuche. In der Breitensuche wird ein Knoten genau dann in die Warteschlange eingefügt, wenn er noch nicht in der Warteschlange ist. Bei der Tiefensuche hingegen möchte man dieses Verhalten nicht: Befinden wir uns gerade bei der Bearbeitung von Knoten y und existiert ein (unbesuchter) Knoten x, der von y aus erreichbar ist, bereits auf dem Stapel, so bedeutet es, dass es einen Knoten z gibt, von dem x aus erreichbar ist, der aber höher als y liegt. Würde man x in diesem Fall nicht (erneut) auf den Stapel legen, würde es dem Prinzip Tiefensuche widersprechen, da man die Knoten in der Tiefe sucht. Anschaulich  könnte x = D, y = E und z = A im Beispielgraphen gelten. Nach dem ersten Schritt, also beim Knoten A, werden B, C, D auf den Stapel gelegt und bei B wird in der Tiefe gesucht. Nach einem weiteren Schritt und weiteren Knoten auf dem Stapel tritt obiges Verhalten auf, d.h. D soll beim Besuch von E auf den Stapel gelegt werden. Würde D also nicht von E aus besucht werden, würde das nicht der Tiefensuche entsprechen.</p>
<p>Unter den <a href="http://en.wikipedia.org/wiki/Depth-first_search#Applications" target="_blank">möglichen Anwendungen für die Tiefensuche</a> möchte ich besonders den <a href="http://de.wikipedia.org/wiki/Backtracking" target="_blank">Backtracking-Algorithmus</a> hervorheben, der sich sehr gut zum Problemlösen eignet. Zwar wird Backtracking meist nicht explizit auf einem Graphen ausgeführt, aber er arbeitet nach dem Prinzip der Tiefensuche, da er bei der Suche nach einer Lösung möglichst viele Schritte in die Tiefe geht und bei einem Fehler zur letzten Verzweigung zurückkehrt um von dort aus wieder in die Tiefe zu gehen.</p>
<p>Anmerkung: Der PHP-Code funktioniert aufgrund der Nutzung von Closures mit PHP 5.3 und höher. Außerdem funktionieren die Algorithmen derzeit nur auf zusammenhänden Graphen. Bei nicht zusammenhängen Graphen muss man eine weitere Schleife hinzufügen, sodass alle Knoten erfasst werden. Dabei entsteht natürlich kein Traversierungsbaum mehr, sondern ein sog. Traversierungswald &#8211; also ein Graph dessen Zusammenhangskomponenten Bäume darstellen.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.phphatesme.com/blog/akademischer-tag/graphtraversierungen-akademischer-tag-4/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Einführung in die Graphentheorie &#8211; Akademischer Tag 3</title>
		<link>http://www.phphatesme.com/blog/akademischer-tag/einfuehrung-in-die-graphentheorie/</link>
		<comments>http://www.phphatesme.com/blog/akademischer-tag/einfuehrung-in-die-graphentheorie/#comments</comments>
		<pubDate>Thu, 20 Aug 2009 05:00:07 +0000</pubDate>
		<dc:creator>Andre Moelle</dc:creator>
				<category><![CDATA[Akademischer Tag]]></category>

		<guid isPermaLink="false">http://www.phphatesme.com/?p=3709</guid>
		<description><![CDATA[]]></description>
			<content:encoded><![CDATA[<p>Der Begriff &#8220;Graph&#8221; tauchte schon häufiger auf phphatesme.com auf, weshalb ich den Begriff ein wenig formaler erklären möchte. Bei der sog. Graphentheorie handelt es sich lt. Wikipedia um ein &#8220;Teilgebiet der Mathematik, das die Eigenschaften von Graphen und ihre Beziehungen zueinander untersucht&#8221;. Da die theoretische Informatik viel mit der Mathematik gemein hat, ist eine Einführung sehr gut für den Akademischen Tag geeignet.<span id="more-3709"></span></p>
<p>Bevor wir mit der Theorie beginnen, möchte ich dieses Thema motivieren, da die Graphentheorie in sehr vielen Bereichen der Informatik eingesetzt werden kann. Sie schlägt sich innerhalb der praktischen Informatik nicht nur in Algorithmen oder Datenstrukturen (wie z.B. dem allseits bekannten Baum) nieder, sondern auch in diversen UML-Diagrammen. Das bekannte Klassendiagramm kann genauso wie ein Graph aufgefasst werden wie ein Aktivitätsdiagramm. Insgesamt lassen sich Graphen sehr gut zur Modellierung von Problemen und deren Lösung nutzen. Mithilfe der <a title="Komplexitätstheorie" href="http://de.wikipedia.org/wiki/Komplexit%C3%A4tstheorie" target="_blank">Komplexitätstheorie</a> lässt sich sogar für einige Probleme beweisen, dass sie nicht effizient lösbar sind. Da die Graphentheorie sehr vielschichtig ist, möchte ich in diesem einführenden Artikel vor allem grundlegende Graphen erklären und darauf eingehen, wie man diese in einem Computer darstellen kann. In den weiteren Artikeln zur Graphentheorie sollen dann die o.g. Themen sowie praktische Algorithmen auf Graphen vertieft werden.</p>
<p>Als erstes stellt sich die Frage, was ist denn nun ein Graph? Vereinfacht gesagt: Ein Gebilde aus Knoten und Kanten, d.h. Punkten und Linien, die jeweils zwei (nicht notwendigerweise unterschiedliche) Punkte verbinden. Die abstrakte Beschreibung lässt noch nicht viel erahnen, wie ein solche Graph aussieht, weshalb ich auf eine Visualisierung eines Graphen verweisen möchte:</p>
<div id="attachment_3727" class="wp-caption aligncenter" style="width: 190px"><img class="size-full wp-image-3727" src="http://www.phphatesme.com/upload/2009/08/Graph1.jpg" alt="Graph mit sechs Knoten" width="180" height="443" /><p class="wp-caption-text">Graph mit sechs Knoten</p></div>
<p>Hier sieht man, was die Knoten und was die Kanten sind. Das interessante an solchen Darstellungen ist, dass es theoretisch unendlich viele Darstellungen eines Graphs gibt, sodass man den Graphen auch völlig anders hätte anordnen können. Im wesentlichen geht es darum, dass in jeder dieser Darstellungen die gleichen Knoten miteinander verbunden sind. Es gibt in der Informatik sogar eine eigene Disziplin, die sich mit dem Zeichnen von Graphen beschäftigt, das sog. <a title="graph drawing" href="http://de.wikipedia.org/wiki/Graphzeichnen" target="_blank">graph drawing</a>.</p>
<p>Da wir nun wissen, wie sich Graphen visualisieren lassen, sollte auch noch geklärt werden, wie sich Graphen definieren lassen. Die allgemeinste Formulierung ist: G = (V,E) ist ein Graph. V entspricht der Menge der Knoten (engl. vertices) und E der Menge der Kanten (engl. edges). D.h. der Graph G besteht aus der Knotenmenge V und der Kantenmenge E. Sei G = (V,E) der Graph, der durch obige Grafik dargestellt ist, so ist V = {1,2,3,4,5,6} und E = {{1,2}, {1,5}, {2,3}, {2,5}, {3,4], {4,5}, {4,6}}. Für jede Kante {x,y} ∈ E gilt, dass x und y durch eine Kante miteinander verbunden sind. Durch die Mengenschreibweise wird klar, dass {x,y} = {y,x} gilt und somit die Reihenfolge der durch die Kanten verbundenen Knoten nicht von Belang ist. Wir sprechen in diesem Fall von einem <em>ungerichteten Graphen</em>.</p>
<p>Ein praxisrelevantes Beispiel für einen ungewichteten Graphen wurde bereits in einem anderen Artikel gegeben:  <a rel="bookmark" href="../blog/webentwicklung/six-degrees-of-seperation-friend-of-a-friend/">Six Degrees of Separation (friend of a friend)</a>. Dabei stellte jede Person im sozialen Netzwerk einen Knoten dar und eine Kante zwischen zwei Personen existierte genau dann, wenn die beiden Personen befreundet waren. Das zu lösende Problem bestand darin, herauszufinden über wie viele Ecken man einen anderen Benutzer kennt &#8211; dabei war natürlich die geringste Anzahl von Ecken gefragt. Graphentheorisch formuliert, lautete das Problem, das sog. <a href="http://de.wikipedia.org/wiki/K%C3%BCrzester_Pfad">kürzester Weg-Problem</a> zu lösen, was im konkreten Fall durch den  <a href="http://de.wikipedia.org/wiki/Algorithmus_von_Dijkstra" target="_blank">Dijkstra-Algorithmus</a> geschah. D.h. man sucht den kürzesten Weg von einem Knoten zu einem anderen Knoten.</p>
<p>Im Gegensatz zu den <em>ungerichteten Graphen</em> gibt es natürlich auch <em>gerichtete Graphen</em>, bei denen die Kanten eine Richtung besitzen. Ein Beispiel hierfür ist der folgende Graph:</p>
<div id="attachment_3726" class="wp-caption aligncenter" style="width: 189px"><img class="size-full wp-image-3726" src="http://www.phphatesme.com/upload/2009/08/Graph2.jpg" alt="Gerichteter Graph" width="179" height="443" /><p class="wp-caption-text">Gerichteter Graph</p></div>
<p>Formal entspricht der obige Graph G = (V, E) mit V = {A,B,C,D,E,F} und E = {(A,B), (B,C), (C,E), (D,B), (E,D), (E,F)}. Für jede gerichtete Kante (x,y) ∈ E gilt, dass eine Kante von x nach y führt. Aus der Tupelschreibweise folgt, dass die Richtung relevant ist, da (x,y) ≠ (y,x) gilt, wenn x ≠ y. Verschiedene Arten von Graphen unterscheiden sich also in der Definition ihrer Kanten.</p>
<p>Da jeder Webentwickler bereits mit Datenbanken gearbeitet haben wird, sollte jedem derselben <a href="http://de.wikipedia.org/wiki/Referenzielle_Integrit%C3%A4t" target="_blank">referentielle Integrität</a> bekannt sein. Das Ziel ist es, in jedem Zustand zu gewährleisten, dass zu jeder Zeile einer Tabelle die referenzierten Zeilen existieren. Verwendet man eine Löschweitergabe (CASCADE DELETE), so soll in jedem Schritt die referentielle Integrität gewährleistet sein. Auch dieses Problem lässt sich auf einen Graphen abstrahieren: Jede Zeile stellt einen Knoten dar und zwischen einer Zeile A und einer Zeile B existiert genau dann eine gerichtete Kante von A nach B, wenn A auf B referenziert. Auf Grundlage eines solchen Graphen lässt sich dann ein Algorithmus, das sog. <a href="http://de.wikipedia.org/wiki/Topologisches_Sortieren" target="_blank">Topologische Sortieren</a>, anwenden, der eine Löschreihenfolge bestimmt ohne die referentielle Integrität zu gefährden.</p>
<p>Die beiden bisher beschriebenen Arten von Graphen lassen sich noch um den Zusatz <em>gewichtet</em> erweitern. Bei einem <em>gewichteten Graphen</em> existiert eine Gewichtsfunktion f: E → R, die jeder Kante einen (reellen) Wert zuordnet, wie der folgende gewichtete Graph zeigt:</p>
<div id="attachment_3725" class="wp-caption aligncenter" style="width: 246px"><img class="size-full wp-image-3725" src="http://www.phphatesme.com/upload/2009/08/Graph3.jpg" alt="Ungerichteter gewichteter Graph" width="236" height="419" /><p class="wp-caption-text">Ungerichteter gewichteter Graph</p></div>
<p>Das Erkennen der Knoten- bzw. Kantenmenge sollte keine Probleme mehr bereiten, deshalb wird lediglich gezeigt, wie die Gewichtsfunktion f definiert ist:</p>
<ul>
<li>f({1,2}) = f({2,1}) = 2</li>
<li>f({1,4}) = f({4,1}) = 5</li>
<li>f({2,3}) = f({3,2}) = 14</li>
<li>f({2,4}) = f({4,2}) = 5</li>
<li>f({2,5}) = f({5,2}) = 4</li>
<li>f({3,5}) = f({5,3}) = 34</li>
<li>f({4,5}) = f({5,4}) = 58</li>
</ul>
<p>Ein häufiges Einführungsbeispiel sind Straßennetze, die graphentheoretisch modelliert werden können. Jeder Knoten entspricht dabei einer Kreuzung und eine Kante zwischen zwei Kreuzungen existiert genau dann, wenn diese Kreuzungen direkt durch eine Straße miteinander verbunden sind. Um Einbahnstraßen zu berücksichtigen wäre hierbei ein gerichteter Graph zweckmäßig. Außerdem erhält jede Kante ein Gewicht, welche die Länge des Straßenabschnitts angibt. Um den kürzesten Weg zu finden, wie es beispielsweise Routenplaner tun, lässt sich auf diesem Graphen ein Algorithmus zur Bestimmung des kürzesten Pfads auf gewichteten Graphen anwenden, wie z.B. der bereits genannte Dijkstra-Algorithmus oder der besser geeignete <a href="http://de.wikipedia.org/wiki/A*-Algorithmus" target="_blank">A*-Algorithmus</a>.</p>
<p>Da jetzt die grundlegendsten Typen von Graphen geklärt sind, können wir zur Darstellung von Graphen innerhalb eines Programms kommen. Die wahrscheinlich bekannteste Darstellung ist die Adjazenzliste (wörtlich Nachbarschaftsliste), in der jedem Knoten eine Liste von benachbarten Knoten zugeordet wird. In PHP lässt sich dies mithilfe von Arrays sehr einfach lösen. Der gerichtete Graph ließe sich folgendermaßen in PHP-Code denotieren:</p>
<pre><code>
$adj = array(
	'A' =&gt; array('B'),
	'B' =&gt; array('C'),
	'C' =&gt; array('E'),
	'D' =&gt; array('B'),
	'E' =&gt; array('D','F'),
	'F' =&gt; array()
);
</code></pre>
<p>Über $adj[$x] erhält man die Nachbarn des Knotens $x. Ungerichtete Graphen lassen sich analog definieren: Existiert zwischen x und y eine Kante, so ist y in der Nachbarschaftsliste von x und x ist in der Nachbarschaftsliste von y. Der erste ungerichtete Graph wäre somit folgendermaßen denotiert:</p>
<pre><code>
$adj = array(
	1 =&gt; array(2,5),
	2 =&gt; array(1,3,5),
	3 =&gt; array(2,4),
	4 =&gt; array(3,5,6),
	5 =&gt; array(1,2,4),
	6 =&gt; array(4)
);
</code></pre>
<p>Eine solche Darstellung eignet sich für viele Algorithmen. Allerdings gibt es eine weitere Darstellung, die vor allem mit der objektorientierten Programmierung assoziiert ist. Das Prinzip ist sehr ähnlich zu dem von Adjazenzlisten, aber der große Unterschied besteht darin, dass jeder Knoten durch ein Objekt repräsentiert wird und jeder Knoten in der Lage ist, seine Nachbarn zurückzugeben. Diese Variante ist vor allem dann nützlich, wenn ein Knoten mehr als nur ein primitiver Wert ist. Folgender PHP-Code, der den gerichteten Graphen darstellt, sollte diese Idee verdeutlichen:</p>
<pre><code>
class Node {
	private $identity = null;
	private $neighbours = array();

	public function __construct ($identity) {
		$this-&gt;identity = $identity;
	}

	public function connect (Node $node) {
		$this-&gt;neighbours[] = $node;
		return $this;
	}

	// Getter &amp; Setter ...
}
$a = new Node('A');
$b = new Node('B');
$c = new Node('C');
$d = new Node('D');
$e = new Node('E');
$f = new Node('F');
$a-&gt;connect($b);
$b-&gt;connect($c);
$c-&gt;connect($e);
$d-&gt;connect($b);
$e-&gt;connect($d)-&gt;connect($f);
</code></pre>
<p>Da wir nun computerinterne Darstellungen von Graphen kennen, können wir auf diesen auch Algorithmen ausführen, die allerdings in weiteren Artikeln weitergeführt werden müssen, da der Rahmen keine ausführliche Beschreibung von grundlegenden Algorithmen mehr zulässt. Wer Vorschläge in Richtung bestimmter Themen hat, darf sich natürlich gerne in den Kommentaren äußern.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.phphatesme.com/blog/akademischer-tag/einfuehrung-in-die-graphentheorie/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
	</channel>
</rss>

