Iterator (#17)

* doc: add documentation

* doc: update tocs

* add code example
This commit is contained in:
Ruidy 2020-10-02 15:24:36 +02:00 committed by GitHub
parent 459ddde228
commit a4cc9340b7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 121 additions and 0 deletions

View file

@ -43,3 +43,4 @@ and divided into three groups.
- [Chain of Responsibility](behavioral/chain_responsibility/README.md)
- [Command](behavioral/command/README.md)
- [Iterator](behavioral/iterator/README.md)

View file

@ -6,3 +6,4 @@ Behavioral design patterns are concerned with algorithms and the assignment of r
- [Chain of Responsibility](chain_responsibility/README.md)
- [Command](command/README.md)
- [Iterator](iterator/README.md)

View file

@ -0,0 +1,38 @@
# Iterator
Iterator is a behavioral design pattern that lets you traverse elements of a collection without exposing its underlying representation (list, stack, tree, etc.).
## Problem
Most collections store their elements in simple lists. However, some of them are based on stacks, trees, graphs and other complex data structures.
But no matter how a collection is structured, it must provide some way of accessing its elements so that other code can use these elements. There should be a way to go through each element of the collection without accessing the same elements over and over.
This may sound like an easy job if you have a collection based on a list. You just loop over all of the elements. But how do you sequentially traverse elements of a complex data structure, such as a tree? For example, one day you might be just fine with depth-first traversal of a tree. Yet the next day you might require breadth-first traversal. And the next week, you might need something else, like random access to the tree elements.
Adding more and more traversal algorithms to the collection gradually blurs its primary responsibility, which is efficient data storage. Additionally, some algorithms might be tailored for a specific application, so including them into a generic collection class would be weird.
On the other hand, the client code thats supposed to work with various collections may not even care how they store their elements. However, since collections all provide different ways of accessing their elements, you have no option other than to couple your code to the specific collection classes.
## Solution
The main idea of the Iterator pattern is to extract the traversal behavior of a collection into a separate object called an iterator.
In addition to implementing the algorithm itself, an iterator object encapsulates all of the traversal details, such as the current position and how many elements are left till the end. Because of this, several iterators can go through the same collection at the same time, independently of each other.
Usually, iterators provide one primary method for fetching elements of the collection. The client can keep running this method until it doesn't return anything, which means that the iterator has traversed all of the elements.
All iterators must implement the same interface. This makes the client code compatible with any collection type or any traversal algorithm as long as theres a proper iterator. If you need a special way to traverse a collection, you just create a new iterator class, without having to change the collection or the client.
## How to Implement
1. Declare the iterator interface. At the very least, it must have a method for fetching the next element from a
collection. But for the sake of convenience you can add a couple of other methods, such as fetching the previous element, tracking the current position, and checking the end of the iteration.
1. Declare the collection interface and describe a method for fetching iterators. The return type should be equal to that of the iterator interface. You may declare similar methods if you plan to have several distinct groups of iterators.
1. Implement concrete iterator classes for the collections that you want to be traversable with iterators. An iterator object must be linked with a single collection instance. Usually, this link is established via the iterators constructor.
1. Implement the collection interface in your collection classes. The main idea is to provide the client with a shortcut for creating iterators, tailored for a particular collection class. The collection object must pass itself to the iterators constructor to establish a link between them.
1. Go over the client code to replace all of the collection traversal code with the use of iterators. The client fetches a new iterator object each time it needs to iterate over the collection elements.

View file

View file

@ -0,0 +1,64 @@
from __future__ import annotations
from typing import Iterator, Iterable, List, Any
class WordCollection(Iterable):
"""
Concrete Collections provide one or several methods for retrieving fresh
iterator instances, compatible with the collection class.
"""
def __init__(self, collection: List[Any] = []) -> None:
self._collection = collection
def __iter__(self) -> AlphabeticalOrderIterator:
"""
The __iter__() method returns the iterator object itself, by default we
return the iterator in ascending order.
"""
return AlphabeticalOrderIterator(self._collection)
def get_reverse_iterator(self) -> AlphabeticalOrderIterator:
return AlphabeticalOrderIterator(self._collection, reverse=True)
def add_item(self, item: Any) -> None:
self._collection.append(item)
class AlphabeticalOrderIterator(Iterator):
"""
Concrete Iterators implement various traversal algorithms. These classes
store the current traversal position at all times.
"""
"""
`_position` attribute stores the current traversal position. An iterator may
have a lot of other fields for storing iteration state, especially when it
is supposed to work with a particular kind of collection.
"""
_position: int = None
"""
This attribute indicates the traversal direction.
"""
_reverse: bool = False
def __init__(self, collection: WordCollection, reverse: bool = False) -> None:
self._collection = collection
self._reverse = reverse
self._position = -1 if reverse else 0
def __next__(self):
"""
The __next__() method must return the next item in the sequence. On
reaching the end, and in subsequent calls, it must raise StopIteration.
"""
try:
value = self._collection[self._position]
self._position += -1 if self._reverse else 1
except IndexError:
raise StopIteration()
return value

View file

@ -0,0 +1,17 @@
"""
The client code may or may not know about the Concrete Iterator or Collection
classes, depending on the level of indirection you want to keep in your program.
"""
from behavioral.iterator.alpha_order_iterator import WordCollection
collection = WordCollection()
collection.add_item("First")
collection.add_item("Second")
collection.add_item("Third")
print("Straight traversal:")
print("\n".join(collection))
print("")
print("Reverse traversal:")
print("\n".join(collection.get_reverse_iterator()), end="")