Iterator - Iterator

In der Computerprogrammierung ist ein Iterator ein Objekt , das es einem Programmierer ermöglicht, einen Container , insbesondere Listen, zu durchlaufen . Über die Schnittstelle eines Containers werden häufig verschiedene Arten von Iteratoren bereitgestellt . Obwohl die Schnittstelle und die Semantik eines bestimmten Iterators festgelegt sind, werden Iteratoren oft in Bezug auf die einer Containerimplementierung zugrunde liegenden Strukturen implementiert und sind oft eng an den Container gekoppelt , um die operative Semantik des Iterators zu ermöglichen. Ein Iterator führt eine Durchquerung durch und gewährt auch Zugriff auf Datenelemente in einem Container, führt jedoch selbst keine Iteration durch (dh nicht ohne einige erhebliche Freiheiten mit diesem Konzept oder mit trivialer Verwendung der Terminologie). Ein Iterator ähnelt im Verhalten einem Datenbankcursor . Iteratoren stammen aus der Programmiersprache CLU aus dem Jahr 1974.

Beschreibung

Interne Iteratoren

Interne Iteratoren sind Funktionen höherer Ordnung (oft mit anonymen Funktionen ) wie map , Reduce usw., die die Durchquerung eines Containers implementieren und die gegebene Funktion der Reihe nach auf jedes Element anwenden.

Externe Iteratoren und das Iteratormuster

Ein externer Iterator kann man sich als eine Art Zeiger vorstellen , der zwei Hauptoperationen hat: auf ein bestimmtes Element in der Objektsammlung verweisen ( Elementzugriff genannt ) und sich selbst so ändern, dass er auf das nächste Element zeigt ( Elementtraversal genannt ). Es muss auch eine Möglichkeit geben, einen Iterator so zu erstellen, dass er auf ein erstes Element zeigt, sowie eine Möglichkeit zu bestimmen, wann der Iterator alle Elemente im Container erschöpft hat. Je nach Sprache und Verwendungszweck können Iteratoren auch zusätzliche Operationen bereitstellen oder unterschiedliche Verhaltensweisen aufweisen.

Der Hauptzweck eines Iterators besteht darin, einem Benutzer zu ermöglichen, jedes Element eines Containers zu verarbeiten, während der Benutzer von der internen Struktur des Containers isoliert wird. Dies ermöglicht es dem Container, Elemente auf jede gewünschte Weise zu speichern, während der Benutzer sie wie eine einfache Sequenz oder Liste behandeln kann. Eine Iteratorklasse wird normalerweise in enger Abstimmung mit der entsprechenden Containerklasse entworfen. Normalerweise stellt der Container die Methoden zum Erstellen von Iteratoren bereit.

Ein Schleifenzähler wird manchmal auch als Schleifeniterator bezeichnet. Ein Schleifenzähler stellt jedoch nur die Durchquerungsfunktionalität und nicht die Elementzugriffsfunktionalität bereit.

Generatoren

Eine Möglichkeit, Iteratoren zu implementieren, besteht darin, eine eingeschränkte Form von Coroutine zu verwenden , die als Generator bekannt ist . Im Gegensatz zu einem Unterprogramm kann ein Generator Koroutine ergibt Werte an seinen Aufrufer mehrmals, anstatt nur einmal zurückzukehren. Die meisten Iteratoren lassen sich natürlich als Generatoren ausdrücken, aber da Generatoren ihren lokalen Zustand zwischen Aufrufen beibehalten, eignen sie sich besonders gut für komplizierte, zustandsbehaftete Iteratoren wie Baumtraverser . Es gibt feine Unterschiede und Unterschiede in der Verwendung der Begriffe "Generator" und "Iterator", die je nach Autor und Sprache variieren. In Python , ist ein Generator ein Iterator Konstruktor : eine Funktion , dass gibt einen Iterator. Ein Beispiel für einen Python-Generator, der mithilfe der Python- Anweisung einen Iterator für die Fibonacci-Zahlen zurückgibt, yieldfolgt:

def fibonacci(limit):
    a, b = 0, 1
    for _ in range(limit):
        yield a
        a, b = b, a + b

for number in fibonacci(100): # The generator constructs an iterator
    print(number)

Implizite Iteratoren

Einige objektorientierte Sprachen wie C# , C++ (spätere Versionen), Delphi (spätere Versionen), Go , Java (spätere Versionen), Lua , Perl , Python , Ruby bieten eine intrinsische Möglichkeit zum Durchlaufen der Elemente eines Container-Objekts ohne die Einführung eines expliziten Iteratorobjekts. Ein tatsächliches Iteratorobjekt kann in der Realität existieren, aber wenn dies der Fall ist, wird es nicht im Quellcode der Sprache verfügbar gemacht.

Implizite Iteratoren werden oft durch eine " foreach "-Anweisung (oder ein Äquivalent) manifestiert , wie im folgenden Python-Beispiel:

for value in iterable:
    print(value)

In Python ist ein Iterable ein Objekt, das in einen Iterator umgewandelt werden kann, der dann während der for-Schleife durchlaufen wird; dies geschieht implizit.

In anderen Fällen können sie vom Sammlungsobjekt selbst erstellt werden, wie in diesem Ruby-Beispiel:

iterable.each do |value|
  puts value
end

Dieser Iterationsstil wird manchmal als "interne Iteration" bezeichnet, da sein Code vollständig im Kontext des iterierbaren Objekts ausgeführt wird (das alle Aspekte der Iteration steuert) und der Programmierer nur die Operation bereitstellt, die bei jedem Schritt ausgeführt werden soll (mit einer anonymen Funktion ).

Sprachen, die Listenverständnisse oder ähnliche Konstrukte unterstützen, können bei der Erstellung der Ergebnisliste auch implizite Iteratoren verwenden, wie in Python:

names = [person.name for person in roster if person.male]

Manchmal ist die implizite verborgene Natur nur teilweise. Die Sprache C++ verfügt über einige Funktionsvorlagen für die implizite Iteration, wie z for_each(). Diese Funktionen erfordern immer noch explizite Iteratorobjekte als ihre anfängliche Eingabe, aber die nachfolgende Iteration stellt dem Benutzer kein Iteratorobjekt zur Verfügung.

Streams

Iteratoren sind eine nützliche Abstraktion von Eingabeströmen – sie stellen ein potenziell unendliches iterierbares (aber nicht unbedingt indiziertes) Objekt bereit. Mehrere Sprachen wie Perl und Python implementieren Streams als Iteratoren. In Python sind Iteratoren Objekte, die Datenströme darstellen. Alternative Implementierungen von stream umfassen datengesteuerte Sprachen wie AWK und sed .

Kontrast mit Indexierung

In prozeduralen Sprachen ist es üblich, den Indexoperator und einen Schleifenzähler zu verwenden, um alle Elemente in einer Sequenz wie einem Array zu durchlaufen. Obwohl die Indizierung auch bei einigen objektorientierten Containern verwendet werden kann, kann die Verwendung von Iteratoren einige Vorteile haben:

  • Zählschleifen sind nicht für alle Datenstrukturen geeignet, insbesondere für Datenstrukturen ohne oder mit langsamem Direktzugriff , wie Listen oder Bäume .
  • Iteratoren können eine konsistente Möglichkeit bieten, Datenstrukturen aller Art zu iterieren und den Code daher lesbarer, wiederverwendbarer und weniger anfällig für Änderungen in der Datenstruktur zu machen.
  • Ein Iterator kann zusätzliche Zugriffsbeschränkungen erzwingen, z. B. sicherstellen, dass Elemente nicht übersprungen werden oder dass auf ein zuvor besuchtes Element kein zweites Mal zugegriffen werden kann.
  • Ein Iterator kann zulassen, dass das Containerobjekt modifiziert wird, ohne den Iterator ungültig zu machen. Wenn beispielsweise ein Iterator über das erste Element hinaus fortgeschritten ist, kann es möglich sein, zusätzliche Elemente mit vorhersehbaren Ergebnissen am Anfang des Containers einzufügen. Bei der Indexierung ist dies problematisch, da sich die Indexnummern ändern müssen.

Die Möglichkeit, einen Container beim Durchlaufen seiner Elemente zu modifizieren, ist in der modernen objektorientierten Programmierung notwendig geworden , wo die Wechselbeziehungen zwischen Objekten und die Auswirkungen von Operationen möglicherweise nicht offensichtlich sind. Durch die Verwendung eines Iterators ist man von solchen Konsequenzen isoliert. Diese Behauptung ist jedoch mit Vorsicht zu genießen, da die Iteratorimplementierung aus Effizienzgründen in den meisten Fällen so eng an den Container gebunden ist, dass sie eine Modifikation des zugrunde liegenden Containers ausschließt, ohne sich selbst ungültig zu machen.

Für Container, die ihre Daten im Speicher verschieben können, besteht die einzige Möglichkeit, den Iterator nicht ungültig zu machen, darin, dass der Container irgendwie alle derzeit aktiven Iteratoren verfolgt und sie im laufenden Betrieb aktualisiert. Da die Anzahl der Iteratoren zu einem bestimmten Zeitpunkt im Vergleich zur Größe des gebundenen Containers beliebig groß sein kann, wird die Aktualisierung aller Iteratoren die Komplexitätsgarantie für die Operationen des Containers drastisch beeinträchtigen.

Eine alternative Möglichkeit, die Anzahl der Aktualisierungen relativ an die Containergröße gebunden zu halten, besteht darin, eine Art Handle-Mechanismus zu verwenden, d. h. eine Sammlung indirekter Zeiger auf die Elemente des Containers, die mit dem Container aktualisiert werden müssen, und die Iteratoren auf zeigen zu lassen diese Handles statt direkt auf die Datenelemente. Dieser Ansatz wird sich jedoch negativ auf die Iteratorleistung auswirken, da er eine doppelte Zeigerfolge bewirken muss, um auf das tatsächliche Datenelement zuzugreifen. Dies ist normalerweise nicht wünschenswert, da viele Algorithmen, die die Iteratoren verwenden, die Datenzugriffsoperation der Iteratoren häufiger aufrufen als das Advance-Verfahren. Daher ist es besonders wichtig, Iteratoren mit sehr effizientem Datenzugriff zu haben.

Alles in allem ist dies immer ein Kompromiss zwischen Sicherheit (Iteratoren bleiben immer gültig) und Effizienz. Meistens ist die zusätzliche Sicherheit den Effizienzpreis nicht wert, den man dafür bezahlen muss. Die Verwendung eines alternativen Containers (zum Beispiel eine einfach verkettete Liste anstelle eines Vektors) wäre die bessere Wahl (global effizienter), wenn die Stabilität der Iteratoren benötigt wird.

Klassifizieren von Iteratoren

Iteratorkategorien

Iteratoren können nach ihrer Funktionalität kategorisiert werden. Hier ist eine (nicht erschöpfende) Liste der Iteratorkategorien:

Kategorie Sprachen
Bidirektionaler Iterator C++
Vorwärts-Iterator C++
Eingabeiterator C++
Ausgabe-Iterator C++
Iterator mit wahlfreiem Zugriff C++
Trivialer Iterator C++ (alte STL )

Iteratortypen

Verschiedene Sprachen oder Bibliotheken, die mit diesen Sprachen verwendet werden, definieren Iteratortypen. Einige von ihnen sind

Typ Sprachen
Array-Iterator PHP , R
Caching-Iterator PHP
Konstanter Iterator C++ , PHP
Verzeichnis-Iterator PHP, Python
Iterator filtern PHP, R
Iterator begrenzen PHP
Listen-Iterator Java , R
Rekursiver Array-Iterator PHP
XML-Iterator PHP

In verschiedenen Programmiersprachen

C# und andere .NET-Sprachen

Iteratoren in .NET Framework werden als "Enumeratoren" bezeichnet und durch die IEnumeratorSchnittstelle dargestellt. IEnumeratorstellt eine MoveNext()Methode bereit , die zum nächsten Element vorrückt und anzeigt, ob das Ende der Sammlung erreicht wurde; eine CurrentEigenschaft, um den Wert des Elements zu erhalten, auf das gerade gezeigt wird; und eine optionale Reset()Methode, um den Enumerator zurück zu seiner Anfangsposition zu spulen. Der Enumerator zeigt zunächst auf einen speziellen Wert vor dem ersten Element, sodass ein Aufruf von MoveNext()erforderlich ist, um mit der Iteration zu beginnen.

Enumeratoren werden normalerweise durch Aufrufen der GetEnumerator()Methode eines Objekts abgerufen, das die IEnumerableSchnittstelle implementiert . Containerklassen implementieren diese Schnittstelle normalerweise. Die foreach- Anweisung in C# kann jedoch auf jedes Objekt angewendet werden, das eine solche Methode bereitstellt, auch wenn sie nicht implementiert ist IEnumerable( duck typing ). Beide Schnittstellen wurden in .NET 2.0 zu generischen Versionen erweitert .

Das Folgende zeigt eine einfache Verwendung von Iteratoren in C# 2.0:

// explicit version
IEnumerator<MyType> iter = list.GetEnumerator();
while (iter.MoveNext())
    Console.WriteLine(iter.Current);

// implicit version
foreach (MyType value in list)
    Console.WriteLine(value);

C# 2.0 unterstützt auch Generatoren : Eine Methode, die als return IEnumerator(oder IEnumerable) deklariert ist , aber die yield returnAnweisung " " verwendet, um eine Sequenz von Elementen zu erzeugen, anstatt eine Objektinstanz zurückzugeben, wird vom Compiler in eine neue Klasse umgewandelt, die die entsprechende Schnittstelle implementiert .

C++

Die Sprache C++ verwendet in ihrer Standardbibliothek in großem Umfang Iteratoren und beschreibt mehrere Kategorien von Iteratoren, die sich im Repertoire an Operationen unterscheiden, die sie erlauben. Dazu gehören Vorwärts-Iteratoren , bidirektionale Iteratoren und Random-Access-Iteratoren in der Reihenfolge steigender Möglichkeiten. Alle Standard-Containervorlagentypen bieten Iteratoren einer dieser Kategorien. Iteratoren verallgemeinern Zeiger auf Elemente eines Arrays (die tatsächlich als Iteratoren verwendet werden können), und ihre Syntax ähnelt der von C- Zeigerarithmetik , wobei die *und ->-Operatoren verwendet werden, um auf das Element zu verweisen, auf das der Iterator zeigt, und Zeigerarithmetikoperatoren like ++werden verwendet, um Iteratoren beim Durchlaufen eines Containers zu ändern.

Die Durchquerung unter Verwendung von Iteratoren umfasst normalerweise einen einzigen variierenden Iterator und zwei feste Iteratoren, die dazu dienen, einen zu durchlaufenden Bereich abzugrenzen. Der Abstand zwischen den begrenzenden Iteratoren in Bezug auf die Anzahl der Anwendungen des Operators, die ++erforderlich sind, um die untere Grenze in die obere umzuwandeln, entspricht der Anzahl der Elemente im angegebenen Bereich; die Anzahl der beteiligten unterschiedlichen Iteratorwerte ist eins mehr. Konventionell "zeigt" der untere begrenzende Iterator auf das erste Element im Bereich, während der obere begrenzende Iterator auf kein Element im Bereich zeigt, sondern direkt hinter das Ende des Bereichs. Für die Durchquerung eines gesamten Containers stellt das begin()Verfahren die Untergrenze und end()die Obergrenze bereit . Letztere referenziert überhaupt kein Element des Containers, sondern ist ein gültiger Iteratorwert, mit dem verglichen werden kann.

Das folgende Beispiel zeigt eine typische Verwendung eines Iterators.

std::vector<int> items;
items.push_back(5); // Append integer value '5' to vector 'items'.
items.push_back(2); // Append integer value '2' to vector 'items'.
items.push_back(9); // Append integer value '9' to vector 'items'.

for (auto it = items.begin(); it != items.end(); ++it) { // Iterate through 'items'.
  std::cout << *it; // And print value of 'items' for current index.
}
// In C++11, the same can be done without using any iterators:
for (auto x : items) {
  std::cout << x; // Print value of each element 'x' of 'items'.
}

// Each loops print "529".

Iteratortypen sind von den Containertypen getrennt, mit denen sie verwendet werden, obwohl die beiden oft gemeinsam verwendet werden. Die Kategorie des Iterators (und damit die dafür definierten Operationen) hängt normalerweise von der Art des Containers ab, wobei beispielsweise Arrays oder Vektoren Random Access-Iteratoren bereitstellen, Sets (die eine verknüpfte Struktur als Implementierung verwenden) jedoch nur bidirektionale Iteratoren bereitstellen. Ein Containertyp kann mehr als einen zugehörigen Iteratortyp haben; zum Beispiel std::vector<T>erlaubt der Containertyp das Durchlaufen entweder mit (rohen) Zeigern auf seine Elemente (vom Typ *<T>) oder Werten eines speziellen Typs std::vector<T>::iterator, und noch ein anderer Typ wird für "umgekehrte Iteratoren" bereitgestellt, deren Operationen so definiert sind, dass an Algorithmus, der eine übliche (vorwärts) Durchquerung durchführt, wird die Durchquerung tatsächlich in umgekehrter Reihenfolge durchführen, wenn er mit umgekehrten Iteratoren aufgerufen wird. Die meisten Container stellen auch einen separaten const_iteratorTyp bereit , für den Operationen, die das Ändern der Werte, auf die gezeigt wird, ermöglichen würden, absichtlich nicht definiert sind.

Ein einfaches Durchlaufen eines Containerobjekts oder eines Bereichs seiner Elemente (einschließlich der Änderung dieser Elemente, es sei denn, a const_iteratorwird verwendet) kann allein mit Iteratoren durchgeführt werden. Containertypen können jedoch auch Methoden bereitstellen, wie insertoder erasedie die Struktur des Containers selbst ändern; dies sind Methoden der Containerklasse, benötigen aber zusätzlich einen oder mehrere Iteratorwerte, um die gewünschte Operation anzugeben. Während mehrere Iteratoren gleichzeitig auf denselben Container zeigen können, können strukturmodifizierende Operationen bestimmte Iteratorwerte ungültig machen (der Standard legt für jeden Fall fest, ob dies der Fall ist); Die Verwendung eines ungültig gemachten Iterators ist ein Fehler, der zu undefiniertem Verhalten führt, und solche Fehler müssen vom Laufzeitsystem nicht signalisiert werden.

Implizite Iteration wird auch teilweise von C++ durch die Verwendung von Standardfunktionsvorlagen wie std::for_each(), std::copy() und unterstützt std::accumulate().

Wenn sie verwendet werden, müssen sie mit vorhandenen Iteratoren initialisiert werden, normalerweise beginund end, die den Bereich definieren, über den die Iteration erfolgt. Im weiteren Verlauf der Iteration wird jedoch kein explizites Iteratorobjekt offengelegt. Dieses Beispiel zeigt die Verwendung von for_each.

ContainerType<ItemType> c; // Any standard container type of ItemType elements.

void ProcessItem(const ItemType& i) { // Function that will process each item of the collection.
  std::cout << i << std::endl;
}

std::for_each(c.begin(), c.end(), ProcessItem); // A for-each iteration loop.

Das gleiche kann erreicht werden std::copy, indem man einen std::ostream_iteratorWert als dritten Iterator übergibt :

std::copy(c.begin(), c.end(), std::ostream_iterator<ItemType>(std::cout, "\n"));

Seit C++11 kann die Lambda-Funktionssyntax verwendet werden, um eine Operation anzugeben, die inline wiederholt werden soll, ohne dass eine benannte Funktion definiert werden muss. Hier ist ein Beispiel für jede Iteration mit einer Lambda-Funktion:

ContainerType<ItemType> c; // Any standard container type of ItemType elements.

// A for-each iteration loop with a lambda function.
std::for_each(c.begin(), c.end(), [](const ItemType& i) { std::cout << i << std::endl; });

Java

Die Schnittstelle wurde im Java JDK 1.2-Release eingeführt und java.util.Iteratorermöglicht die Iteration von Containerklassen. Jedes Iteratorstellt ein next()und ein hasNext()Verfahren bereit und kann optional ein remove()Verfahren unterstützen. Iteratoren werden von der entsprechenden Containerklasse erstellt, normalerweise von einer Methode namens iterator().

Die next()Methode rückt den Iterator vor und gibt den Wert zurück, auf den der Iterator zeigt. Das erste Element wird beim ersten Aufruf von abgerufen next(). Um festzustellen, wann alle Elemente im Container besucht wurden, wird die hasNext()Testmethode verwendet. Das folgende Beispiel zeigt eine einfache Verwendung von Iteratoren:

Iterator iter = list.iterator();
// Iterator<MyType> iter = list.iterator(); // in J2SE 5.0
while (iter.hasNext()) {
    System.out.print(iter.next());
    if (iter.hasNext())
        System.out.print(", ");
}

Um zu zeigen, dass hasNext()dies wiederholt aufgerufen werden kann, verwenden wir es, um Kommas zwischen den Elementen einzufügen, aber nicht nach dem letzten Element.

Dieser Ansatz trennt den Vorlaufvorgang nicht richtig vom eigentlichen Datenzugriff. Wenn das Datenelement bei jedem Vorrücken mehr als einmal verwendet werden muss, muss es in einer temporären Variablen gespeichert werden. Wenn ein Vorrücken ohne Datenzugriff erforderlich ist (dh um ein bestimmtes Datenelement zu überspringen), wird der Zugriff dennoch ausgeführt, obwohl der zurückgegebene Wert in diesem Fall ignoriert wird.

Bei Sammlungstypen, die dies unterstützen, entfernt die remove()Methode des Iterators das zuletzt besuchte Element aus dem Container, während der Iterator verwendbar bleibt. Das Hinzufügen oder Entfernen von Elementen durch Aufrufen der Methoden des Containers (auch aus demselben Thread ) macht den Iterator unbrauchbar. Ein Versuch, das nächste Element abzurufen, löst die Ausnahme aus. Eine Ausnahme wird auch geworfen, wenn keine Elemente mehr übrig sind ( hasNext()hat zuvor false zurückgegeben).

Darüber hinaus java.util.Listgibt es eine java.util.ListIteratormit einer ähnlichen API, die jedoch Vorwärts- und Rückwärtsiteration ermöglicht, ihren aktuellen Index in der Liste bereitstellt und das Setzen des Listenelements an seiner Position ermöglicht.

Die J2SE 5.0-Version von Java führte die IterableSchnittstelle ein, um eine erweiterte for( foreach ) Schleife zum Iterieren über Sammlungen und Arrays zu unterstützen. Iterabledefiniert die iterator()Methode, die eine Iterator. Mit der erweiterten forSchleife kann das obige Beispiel umgeschrieben werden als

for (MyType obj : list) {
    System.out.print(obj);
}

Einige Container verwenden auch die ältere (seit 1.0) EnumerationKlasse. Es stellt hasMoreElements()und nextElement()-Methoden bereit , hat jedoch keine Methoden zum Ändern des Containers.

Scala

In Scala verfügen Iteratoren über eine Vielzahl von Methoden, die Sammlungen ähneln, und können direkt in for-Schleifen verwendet werden. Tatsächlich erben sowohl Iteratoren als auch Sammlungen von einem gemeinsamen Basismerkmal - scala.collection.TraversableOnce. Aufgrund der umfangreichen Methodenpalette, die in der Scala-Sammlungsbibliothek zur Verfügung steht, wie map, collect, filterusw., ist es jedoch oft nicht erforderlich, sich beim Programmieren in Scala direkt mit Iteratoren zu befassen.

Java-Iteratoren und -Sammlungen können automatisch in Scala-Iteratoren bzw. -Sammlungen umgewandelt werden, indem einfach eine einzelne Zeile hinzugefügt wird

import scala.collection.JavaConversions._

zur Datei. JavaConversionsDazu stellt das Objekt implizite Konvertierungen bereit. Implizite Konvertierungen sind eine Funktion von Scala: Methoden, die, wenn sie im aktuellen Gültigkeitsbereich sichtbar sind, automatisch Aufrufe an sich selbst in relevante Ausdrücke an der entsprechenden Stelle einfügen, damit sie eine Typprüfung durchführen, wenn dies sonst nicht der Fall wäre.

MATLAB

MATLAB unterstützt sowohl externe als auch interne implizite Iteration unter Verwendung von entweder "nativen" Arrays oder cellArrays. Im Fall einer externen Iteration, bei der es beim Benutzer liegt, die Durchquerung voranzutreiben und die nächsten Elemente anzufordern, kann man einen Satz von Elementen innerhalb einer Array-Speicherstruktur definieren und die Elemente unter Verwendung des for-Loop-Konstrukts durchlaufen. Beispielsweise,

% Define an array of integers
myArray = [1,3,5,7,11,13];

for n = myArray
   % ... do something with n
   disp(n)  % Echo integer to Command Window
end

durchläuft ein Array von ganzen Zahlen mit dem forSchlüsselwort.

Im Fall einer internen Iteration, bei der der Benutzer dem Iterator eine Operation übergeben kann, um jedes Element einer Sammlung auszuführen, werden viele eingebaute Operatoren und MATLAB-Funktionen überladen, um jedes Element eines Arrays auszuführen und implizit ein entsprechendes Ausgabearray zurückzugeben . Darüber hinaus können die Funktionen arrayfunund cellfunzum Durchführen von benutzerdefinierten oder benutzerdefinierten Operationen über "native" Arrays bzw. cellArrays genutzt werden. Beispielsweise,

function simpleFun
% Define an array of integers
myArray = [1,3,5,7,11,13];

% Perform a custom operation over each element 
myNewArray = arrayfun(@(a)myCustomFun(a),myArray);

% Echo resulting array to Command Window
myNewArray

function outScalar = myCustomFun(inScalar)
% Simply multiply by 2
outScalar = 2*inScalar;

definiert eine primäre Funktion simpleFun, die implizit eine benutzerdefinierte Unterfunktion myCustomFunauf jedes Element eines Arrays anwendet, indem die integrierte Funktion verwendet wird arrayfun.

Alternativ kann es wünschenswert sein, die Mechanismen des Array-Speichercontainers vom Benutzer zu abstrahieren, indem eine benutzerdefinierte objektorientierte MATLAB-Implementierung des Iteratormusters definiert wird. Eine solche Implementierung, die externe Iteration unterstützt, wird in MATLAB Central File Exchange item Design Pattern: Iterator (Behavioral) demonstriert . Dies ist in der neuen Klassendefinitionssyntax geschrieben, die mit der MATLAB-Softwareversion 7.6 (R2008a) eingeführt wurde, und bietet eine eindimensionale cellArray-Realisierung des List Abstract Data Type (ADT) als Mechanismus zum Speichern eines heterogenen (im Datentyp) Satzes von Elemente. Es stellt die Funktionalität für die explizite Vorwärtsdurchquerung von Listen mit den Methoden hasNext(), next()und reset()zur Verwendung in einer while-Schleife bereit.

PHP

Die foreachSchleife von PHP wurde in Version 4.0 eingeführt und in 4.0 Beta 4 als Werte mit Objekten kompatibel gemacht. Durch die Einführung der internen TraversableSchnittstelle wurde jedoch in PHP 5 die Unterstützung für Iteratoren hinzugefügt . Die beiden Hauptschnittstellen für die Implementierung in PHP-Skripten, die das Iterieren von Objekten über die foreachSchleife ermöglichen, sind Iteratorund IteratorAggregate. Letzteres erfordert nicht, dass die implementierende Klasse alle erforderlichen Methoden deklariert, sondern implementiert stattdessen eine Accessormethode ( getIterator), die eine Instanz von zurückgibt Traversable. Die Standard-PHP-Bibliothek bietet mehrere Klassen, um mit speziellen Iteratoren zu arbeiten. PHP unterstützt seit 5.5 auch Generatoren .

Die einfachste Implementierung besteht darin, ein Array zu umschließen. Dies kann für Typhinting und Information Hiding nützlich sein .

namespace Wikipedia\Iterator;

final class ArrayIterator extends \Iterator
{
    private array $array;

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

    public function rewind(): void
    {
        echo 'rewinding' , PHP_EOL;
        reset($this->array);
    }

    public function current()
    {
        $value = current($this->array);
        echo "current: {$value}", PHP_EOL;
        return $value;
    }

    public function key()
    {
        $key = key($this->array);
        echo "key: {$key}", PHP_EOL;
        return $key;
    }

    public function next()
    {
        $value = next($this->array);
        echo "next: {$value}", PHP_EOL;
        return $value;
    }

    public function valid(): bool
    {
        $valid = $this->current() !== false;
        echo 'valid: ', ($valid ? 'true' : 'false'), PHP_EOL;
        return $valid;
    }
}

Bei der Ausführung einer kompletten foreach-Schleife ( foreach ($iterator as $key => $current) {}) werden alle Methoden der Beispielklasse verwendet . Die Methoden des Iterators werden in der folgenden Reihenfolge ausgeführt:

  1. $iterator->rewind() sorgt dafür, dass die interne Struktur von vorne beginnt.
  2. $iterator->valid()gibt in diesem Beispiel true zurück.
  3. $iterator->current()Der zurückgegebene Wert wird in gespeichert $value.
  4. $iterator->key()Der zurückgegebene Wert wird in gespeichert $key.
  5. $iterator->next() geht zum nächsten Element in der internen Struktur.
  6. $iterator->valid()gibt false zurück und die Schleife wird abgebrochen.

Das nächste Beispiel veranschaulicht eine PHP-Klasse, die die TraversableSchnittstelle implementiert , die in eine IteratorIteratorKlasse eingeschlossen werden könnte, um auf die Daten zu reagieren, bevor sie an die foreachSchleife zurückgegeben werden. Die Verwendung zusammen mit der MYSQLI_USE_RESULTKonstante ermöglicht es PHP-Skripten, Ergebnismengen mit Milliarden von Zeilen mit sehr geringem Speicherverbrauch zu iterieren. Diese Funktionen sind weder für PHP noch für seine MySQL-Klassenimplementierungen exklusiv (zB PDOStatementimplementiert die Klasse auch die TraversableSchnittstelle).

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new \mysqli('host.example.com', 'username', 'password', 'database_name');

// The \mysqli_result class that is returned by the method call implements the internal Traversable interface.
foreach ($mysqli->query('SELECT `a`, `b`, `c` FROM `table`', MYSQLI_USE_RESULT) as $row) {
    // Act on the returned row, which is an associative array.
}

Python

Iteratoren in Python sind ein grundlegender Bestandteil der Sprache und bleiben in vielen Fällen unsichtbar, da sie implizit in der for( foreach )-Anweisung, in Listenverständnissen und in Generatorausdrücken verwendet werden . Alle standardmäßigen integrierten Sammlungstypen von Python unterstützen Iteration sowie viele Klassen, die Teil der Standardbibliothek sind. Das folgende Beispiel zeigt eine typische implizite Iteration über eine Sequenz:

for value in sequence:
    print(value)

Python-Wörterbücher (eine Form eines assoziativen Arrays ) können auch direkt durchlaufen werden, wenn die Wörterbuchschlüssel zurückgegeben werden; oder die items()Methode eines Wörterbuchs kann iteriert werden, wo sie entsprechende Schlüssel-Wert-Paare als Tupel liefert:

for key in dictionary:
    value = dictionary[key]
    print(key, value)
for key, value in dictionary.items():
    print(key, value)

Iteratoren können jedoch explizit verwendet und definiert werden. Für jeden iterierbaren Sequenztyp oder jede Klasse wird die integrierte Funktion iter()verwendet, um ein Iteratorobjekt zu erstellen. Das Iterator-Objekt kann dann mit der next()Funktion iteriert werden, die __next__()intern die Methode verwendet, die das nächste Element im Container zurückgibt. (Die vorherige Anweisung gilt für Python 3.x. In Python 2.x ist die next()Methode äquivalent.) Eine StopIterationAusnahme wird ausgelöst, wenn keine Elemente mehr übrig sind. Das folgende Beispiel zeigt eine äquivalente Iteration über eine Sequenz mit expliziten Iteratoren:

it = iter(sequence)
while True:
    try:
        value = it.next() # in Python 2.x
        value = next(it) # in Python 3.x
    except StopIteration:
        break
    print(value)

Jede benutzerdefinierte Klasse kann Standarditeration (entweder implizit oder explizit) unterstützen, indem sie eine __iter__()Methode definiert , die ein Iteratorobjekt zurückgibt. Das Iteratorobjekt muss dann eine __next__()Methode definieren , die das nächste Element zurückgibt.

Python - Generatoren implementieren diese Iteration Protokoll .

Raku

Iteratoren in Raku sind ein grundlegender Bestandteil der Sprache, obwohl sich Benutzer normalerweise nicht um Iteratoren kümmern müssen. Ihre Verwendung ist hinter Iterations-APIs wie der forAnweisung, map, grep, Listenindexierung mit .[$idx]usw. verborgen .

Das folgende Beispiel zeigt eine typische implizite Iteration über eine Sammlung von Werten:

my @values = 1, 2, 3;
for @values -> $value {
    say $value
}
# OUTPUT:
# 1
# 2
# 3

Raku-Hashes können auch direkt iteriert werden; Dies ergibt Schlüsselwertobjekte Pair. Die kvMethode kann für den Hash aufgerufen werden, um über den Schlüssel und die Werte zu iterieren; die keysMethode zum Durchlaufen der Hash-Schlüssel; und die valuesMethode zum Iterieren über die Hashwerte.

my %word-to-number = 'one' => 1, 'two' => 2, 'three' => 3;
for %word-to-number -> $pair {
    say $pair;
}
# OUTPUT:
# three => 3
# one => 1
# two => 2

for %word-to-number.kv -> $key, $value {
    say "$key: $value" 
}
# OUTPUT:
# three: 3
# one: 1
# two: 2

for %word-to-number.keys -> $key {
    say "$key => " ~ %word-to-number{$key};
}
# OUTPUT:
# three => 3
# one => 1
# two => 2

Iteratoren können jedoch explizit verwendet und definiert werden. Für jeden iterierbaren Typ gibt es mehrere Methoden, die verschiedene Aspekte des Iterationsprozesses steuern. Beispielsweise soll die iteratorMethode ein IteratorObjekt zurückgeben, und die pull-oneMethode soll nach Möglichkeit den nächsten Wert erzeugen und zurückgeben oder den Sentinel-Wert zurückgeben, IterationEndwenn keine weiteren Werte erzeugt werden konnten. Das folgende Beispiel zeigt eine äquivalente Iteration über eine Sammlung mit expliziten Iteratoren:

my @values = 1, 2, 3;
my $it := @values.iterator;          # grab iterator for @values

loop {
    my $value := $it.pull-one;       # grab iteration's next value
    last if $value =:= IterationEnd; # stop if we reached iteration's end
    say $value;
}
# OUTPUT:
# 1
# 2
# 3

Alle iterierbaren Typen in Raku bilden die IterableRolle, IteratorRolle oder beides. Das Iterableist recht einfach und erfordert nur, dass das iteratorvon der komponierenden Klasse implementiert wird. Das Iteratorist komplexer und bietet eine Reihe von Methoden wie pull-one, die eine feinere Iterationsoperation in mehreren Kontexten ermöglichen, wie das Hinzufügen oder Entfernen von Elementen oder das Überspringen von Elementen, um auf andere Elemente zuzugreifen. Somit kann jede benutzerdefinierte Klasse die Standarditeration unterstützen, indem sie diese Rollen zusammensetzt und die Methoden iteratorund/oder implementiert pull-one.

Die DNAKlasse repräsentiert einen DNA-Strang und implementiert dies, iteratorindem sie die IterableRolle komponiert . Der DNA-Strang wird beim Iterieren in eine Gruppe von Trinukleotiden aufgespalten:

subset Strand of Str where { .match(/^^ <[ACGT]>+ $$/) and .chars %% 3 };
class DNA does Iterable {
    has $.chain;
    method new(Strand:D $chain) {
        self.bless: :$chain
    }
 
    method iterator(DNA:D:){ $.chain.comb.rotor(3).iterator }
};

for DNA.new('GATTACATA') {
    .say
}
# OUTPUT:
# (G A T)
# (T A C)
# (A T A)

say DNA.new('GATTACATA').map(*.join).join('-');
# OUTPUT:
# GAT-TAC-ATA

Die RepeaterKlasse setzt sich aus den Iterableund den IteratorRollen zusammen:

class Repeater does Iterable does Iterator {
    has Any $.item  is required;
    has Int $.times is required;
    has Int $!count = 1;
    
    multi method new($item, $times) {
        self.bless: :$item, :$times;
    }
    
    method iterator { self }
    method pull-one(--> Mu){ 
        if $!count <= $!times {
            $!count += 1;
            return $!item
        }
        else {
            return IterationEnd
        }
    }
}

for Repeater.new("Hello", 3) {
    .say
}

# OUTPUT:
# Hello
# Hello
# Hello

Rubin

Ruby implementiert Iteratoren ganz anders; alle Iterationen werden durch Übergabe von Callback-Closures an Containermethoden durchgeführt - auf diese Weise implementiert Ruby nicht nur die grundlegende Iteration, sondern auch mehrere Iterationsmuster wie Funktionszuordnung, Filter und Reduzierung. Ruby unterstützt auch eine alternative Syntax für die grundlegende Iterationsmethode each, die folgenden drei Beispiele sind äquivalent:

(0...42).each do |n|
  puts n
end

...und...

for n in 0...42
  puts n
end

oder noch kürzer

42.times do |n|
  puts n
end

Ruby kann auch über feste Listen iterieren, indem er Enumerators verwendet und entweder ihre #nextMethode aufruft oder wie oben für jede davon a ausführt.

Rost

Mit Rust kann man Elemente von Vektoren iterieren oder eigene Iteratoren erstellen. Jeder Iterator hat Adapter ( map, filter, skip, take, ...).

for n in 0..42 {
    println!("{}", n);
}

Darunter gibt die fibonacci()Funktion einen benutzerdefinierten Iterator zurück.

for i in fibonacci().skip(4).take(4) {
    println!("{}", i);
}

Siehe auch

Verweise

Externe Links