Skip to content
Snippets Groups Projects
Commit e886a50b authored by Ulrich Kerzel's avatar Ulrich Kerzel
Browse files

start on iterators/generators

move solutins to separate directory
parent 080351bb
Branches
Tags
No related merge requests found
%% Cell type:markdown id: tags:
# Iterators
We have previously seen that we can iterate over items in, for example, a dictionary or a list.
For example:
%% Cell type:code id: tags:
``` python
my_list = ['cat', 'dog', 'bird']
for item in my_list:
print(item)
```
%% Output
cat
dog
bird
%% Cell type:markdown id: tags:
This loop iterates over all elements in the list, gives us access to each element in turn and then stops once we reach the end of the list.
We could do this manually by creating an *iterator* and then use this to traverse the list, until no more elements are left over and an ```StopIteration``` exception is raised.
We define the iterator with the keyword ```my_iter = iter(my_object)``` and then proceed to the next item with ```next(my_iter)```.
%% Cell type:code id: tags:
``` python
my_list = ['cat', 'dog', 'bird']
my_iter = iter(my_list)
try:
while True:
print(next(my_iter))
except StopIteration as e:
print('Reached the end of the list {}'.format(e))
```
%% Output
cat
dog
bird
Reached the end of the list
%% Cell type:markdown id: tags:
Obviously, this is quite a horrible way to loop over a list (though some programming languages are not far off from doing this as the default way...)
One situation where we need to think about iterators is when we need to define a class that we can iterate over.
Then wen need to implement the "magic" (or dunder) functions ```__iter__()``` and ```__next__()```. Remember that the double underscores before and after the keywords indicate that we should not call these methods ourselves. \
For example, you may need to develop a more fancy list, a counter, an even more powerful dictionary, ...
%% Cell type:code id: tags:
``` python
class Counter:
def __init__(self, start, stop):
# number: our counter we want to iterate over
self.number = start
self.stop = stop
def __iter__(self):
return self
def __next__(self):
# check if we have reached the largest number we want to run over
if self.number > self.stop:
raise StopIteration
else:
current_number = self.number
self.number = self.number + 1
return current_number
my_counter = Counter(0, 5)
for counter in my_counter:
print('Value of the counter is now: {}'.format(counter))
```
%% Output
Value of the counter is now: 0
Value of the counter is now: 1
Value of the counter is now: 2
Value of the counter is now: 3
Value of the counter is now: 4
Value of the counter is now: 5
%% Cell type:markdown id: tags:
# Generators
Generators in python are special functions that remember their state each time you call them. Remeber that with the "normal" function we have seen earlier, we call the function, do our computations or other actions, maybe define local variables, etc. We may return the result of what the function does - but then the fuction is "forgotten".
In some cases, we may want to remember the state of the function. For example, we could read from a very large file: If it's very large, we cannot keep it in memory, but need to parse the content one line at the time. However, it would be very cumbersome to open a file, read one line, remember which line we have read, close the file, open the file, jump to the appropriate place, etc. \
Another use-case is we want to compute a long list of elements - but we do not know how many to start with. We could do this with a ```for``` or ```while``` loop (as we have done previously) and define the start and stop conditions.
Generators provide a neat way to do this without having to decide on start or stop condition in the function and, instead, focus on the function itself.
Generators are defined very similarly to normal functions, the main difference is the keyword ```yield```. When we encounter the ```yield``` statement, the execution of the function is stopped, we return a generator object (instead of the value), and the current state of the function is kept
**Example** \
Infinite sum - we want to obtain the sum of all integers until we decide to stop.
%% Cell type:code id: tags:
``` python
def my_infinite_sum():
sum = 0
while True:
yield sum
sum = sum + 1
# Then we can use this and print a few elements
my_generator = my_infinite_sum()
print(next(my_generator))
print(next(my_generator))
print(next(my_generator))
# we can also loop over this
for i in range(0, 5):
print(next(my_generator))
```
%% Output
0
1
2
3
4
5
6
7
%% Cell type:markdown id: tags:
We can also introduce a stop criterion that prevents us from reaching the ```yield``` statement. Then, as with the iterators above, a ```StopIteration``` exception to signal the end.
%% Cell type:code id: tags:
``` python
def my_infinite_sum(stop):
sum = 0
while sum < stop:
yield sum
sum = sum + 1
my_generator = my_infinite_sum(5)
for i in my_generator:
print(i)
```
%% Output
0
1
2
3
4
%% Cell type:markdown id: tags:
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment