ASCII Iterator
1) Was sind Iteratoren?
Iteratoren sind ein in PHP 5.0 eingebautes ein Feature in PHP, mit welchem man innerhalb einer Schleife ein Objekt durchlaufen kann, was gerade bei sehr großen Listen Performancevorteile im Vergleich zu Arrays bringt.
Es gibt in PHP zwei Interfaces für Iteratoren:
- Iterator
- IteratorAggregate
- Fertig Iterator Implementierungen in PHP
Iterator
Das Iterator Interface besitzt 5 Methoden:
- rewind()
- valid()
- next()
- key()
- current()
Die rewind(), valid() und next() Methoden lassen sich anhand einer for()-Schleife erklären:
$i = 0 setzt den Iterator auf 0 - genau wie rewind().
$i < 10; prüft, ob die Iterationsrunde durchgeführt wurde. Wenn $i größer 10 ist, dann wird die Runde nicht mehr durchgeführt - genau diese Prüfung führt valid().
$i++ setzt $i um eins hoch, bevor die nächste Iterationsrunde durchgeführt wird - genau wie next()
Im Prinzip könnte man also einen Iterator manuell via for()-Schleife iterieren:
current() gibt den aktuellen Wert zurück und key() den Schlüssel.
Hier mal ein kleines Beispiel:
Die Ausgabe sähe dann etwa so aus:
Allerdings ist für Anwendungsfälle in einem derartigem Größenbereich ein Iterator der totale Overkill - da würden auch normale arrays ausreichen.
IteratorAggregate
Das Iterator Interface eignet sich hervorragend, wenn während der Iteration die eigentlichen Werte erst errechnet werden müssen, wie zum Beispiel bei dem unten gezeigtem ASCII Weihnachtsbaum Iterator. Wenn man aber nur sehr große Wertelisten hat, dann eignet sich IteratorAggregate besser, da man einfach einen ArrayIterator returnen kann. Dieses Interface besitzt eine Methode:
- getIterator()
Diese Methode returned einen Iterator mit den Werten, über die dann iteriert werden kann.
iterator_to_array()
Iteratoren haben aber im Vergleich zu Arrays einen entscheidenden Nachteil: man kann die array_* Funktionen nicht nutzen, da diese auf Arrays einen Type Hint setzen, aber nicht auf ein Iterator oder IteratorAggregate interface. Falls man also an einer Code Stelle die array Funktionen braucht, aber einen Iterator hat, müsste man jetzt über einen Iterator drüberiterieren und diesen in einen Array schreiben. Zum Glück stellt PHP aber die Funktion iterator_to_array für solche Probleme zur Verfügung.
Diese Methode erwartet ein Traversable Interface. Sowohl Iterator, als auch IteratorAggregate erben von diesem Interface und folglich können alle Klassen, die eines der beiden Interfaces besitzen, bei IteratorAggregate den Rückgabewert von getIterator() oder das Resultat von Iterator, zu einem Array konvertiert.
yield (Generatoren)
Seit PHP 5.5 hat PHP ein neues Feature, die Generatoren, eingeführt. Diese tun im Prinzip das gleiche, wie das Iterator Interface, bloß muss man nicht eine ganze Klasse implementieren, sondern nur yield in der Funktion nutzen. Hier ein Beispiel mit yield, das die gleiche Ausgabe wie im obigen Beispiel erziehlt:
Wie man erkennen kann, tut yield im Prinzip nichts anderes, irgendwelche Werte in den Generator zu packen. Dadurch wird die Funktion zu einem Generator, über welchen man iterieren kann, obwohl es keinen Rückgabewert gibt.
2) ASCII Weihnachtsbaum
Nach dieser Einführung für Iteratoren hier mal ein Beispiel, bei dem mit Hilfe eines Iterators ein Weihnachtsbaum generiert wird. beispielhafte Ausgabe:
Und hier der Code:
1) Was sind Iteratoren?
Iteratoren sind ein in PHP 5.0 eingebautes ein Feature in PHP, mit welchem man innerhalb einer Schleife ein Objekt durchlaufen kann, was gerade bei sehr großen Listen Performancevorteile im Vergleich zu Arrays bringt.
Es gibt in PHP zwei Interfaces für Iteratoren:
- Iterator
- IteratorAggregate
- Fertig Iterator Implementierungen in PHP
Iterator
Das Iterator Interface besitzt 5 Methoden:
- rewind()
- valid()
- next()
- key()
- current()
Die rewind(), valid() und next() Methoden lassen sich anhand einer for()-Schleife erklären:
PHP-Code:
for ($i = 0; $i < 10; $i++) {}
$i < 10; prüft, ob die Iterationsrunde durchgeführt wurde. Wenn $i größer 10 ist, dann wird die Runde nicht mehr durchgeführt - genau diese Prüfung führt valid().
$i++ setzt $i um eins hoch, bevor die nächste Iterationsrunde durchgeführt wird - genau wie next()
Im Prinzip könnte man also einen Iterator manuell via for()-Schleife iterieren:
PHP-Code:
for ($iterator->rewind(); $iterator->valid(); $iterator->next() {}
Hier mal ein kleines Beispiel:
PHP-Code:
<?php
class ExampleIterator implements Iterator {
private $mockValues = ['foo', 'bar'];
private $row = 0;
public function rewind() {
$this->row = 0;
}
public function valid() {
return $this->row <= count($this->mockValues);
}
public function next() {
$this->row++;
}
public function key() {
return $this->row;
}
public function current() {
return $this->mockValues[$this->row];
}
}
foreach (new ExampleIterator as $key => $value) {
echo $key . ' => ' . $value . PHP_EOL;
}
Code:
0 => foo 1 => bar
IteratorAggregate
Das Iterator Interface eignet sich hervorragend, wenn während der Iteration die eigentlichen Werte erst errechnet werden müssen, wie zum Beispiel bei dem unten gezeigtem ASCII Weihnachtsbaum Iterator. Wenn man aber nur sehr große Wertelisten hat, dann eignet sich IteratorAggregate besser, da man einfach einen ArrayIterator returnen kann. Dieses Interface besitzt eine Methode:
- getIterator()
Diese Methode returned einen Iterator mit den Werten, über die dann iteriert werden kann.
iterator_to_array()
Iteratoren haben aber im Vergleich zu Arrays einen entscheidenden Nachteil: man kann die array_* Funktionen nicht nutzen, da diese auf Arrays einen Type Hint setzen, aber nicht auf ein Iterator oder IteratorAggregate interface. Falls man also an einer Code Stelle die array Funktionen braucht, aber einen Iterator hat, müsste man jetzt über einen Iterator drüberiterieren und diesen in einen Array schreiben. Zum Glück stellt PHP aber die Funktion iterator_to_array für solche Probleme zur Verfügung.
Diese Methode erwartet ein Traversable Interface. Sowohl Iterator, als auch IteratorAggregate erben von diesem Interface und folglich können alle Klassen, die eines der beiden Interfaces besitzen, bei IteratorAggregate den Rückgabewert von getIterator() oder das Resultat von Iterator, zu einem Array konvertiert.
yield (Generatoren)
Seit PHP 5.5 hat PHP ein neues Feature, die Generatoren, eingeführt. Diese tun im Prinzip das gleiche, wie das Iterator Interface, bloß muss man nicht eine ganze Klasse implementieren, sondern nur yield in der Funktion nutzen. Hier ein Beispiel mit yield, das die gleiche Ausgabe wie im obigen Beispiel erziehlt:
PHP-Code:
<?php
function generator()
{
$mockValues = ['foo', 'bar'];
foreach ($mockValues as $value) {
yield $value;
}
}
foreach (generator() as $key => $value) {
echo $key . ' => ' . $value . PHP_EOL;
}
2) ASCII Weihnachtsbaum
Nach dieser Einführung für Iteratoren hier mal ein Beispiel, bei dem mit Hilfe eines Iterators ein Weihnachtsbaum generiert wird. beispielhafte Ausgabe:
Code:
X XXX XXXXX XXXXXXX XXXXXXXXX X XXX XXXXX XXXXXXX XXXXXXXXX X XXX XXXXX XXXXXXX XXXXXXXXX XXX XXX
PHP-Code:
<?php
class ASCIIChristmasTreeIterator implements Iterator
{
private $row;
private $char;
private $pineConeLength;
private $pineConeCount;
private $rootHeight;
public function __construct($char, $pineConeCount = 3, $pineConeLength = 9, $rootHeight = 2)
{
if ($pineConeLength % 2 === 0) {
throw new InvalidArgumentException('Pine cone length must be an odd number');
}
$this->pineConeLength = (integer) $pineConeLength;
$this->pineConeCount = (integer) $pineConeCount;
$this->rootHeight = (integer) $rootHeight;
$this->char = (string) $char;
}
public function rewind()
{
$this->row = 0;
}
public function valid()
{
$middle = ($this->pineConeLength / 2) * $this->pineConeCount + $this->rootHeight;
return $this->row <= floor($middle) || $this->row <= ceil($middle);
}
public function next()
{
$this->row++;
}
public function key()
{
return $this->row;
}
public function current()
{
static $height = 1;
static $count = 1;
if ($height > $this->pineConeLength) {
$height = 1;
$count++;
}
if ($count > $this->pineConeCount) {
return [
'indent' => $this->calculateIndent(3),
'content' => str_repeat($this->char, 3)
];
}
$data = [
'content' => str_repeat($this->char, $height),
'indent' => $this->calculateIndent($height)
];
$height = $height + 2;
return $data;
}
private function calculateIndent($content)
{
return ($this->pineConeLength - $content) / 2;
}
}
foreach (new ASCIIChristmasTreeIterator('X') as $pineCone) {
echo str_repeat(' ', $pineCone['indent']) . $pineCone['content'] . PHP_EOL;
}
Kommentar