# 3. Iterators

## 3.1. Protocol

• `__iter__()`

• `__next__() -> raise StopIteration`

## 3.2. Iterowanie po obiektach

### 3.2.1. Iterowanie po `list()`, `dict()`, `set()`, `tuple()`

```for liczba in [1, 2, 3, 4]:
print(liczba)
```
```for key, value in [('a',1), ('b',2), ('c',3)]:
print(f'{key} -> {value}')

# a -> 1
# b -> 2
# c -> 3
```
```my_dict = {'a': 1, 'b': 2, 'c': 3}

for key in my_dict:
value = my_dict.get(key)
print(value)

# 1
# 2
# 3

for key, value in my_dict.items():
print(f'{key} -> {value}')

# a -> 1
# b -> 2
# c -> 3
```

### 3.2.2. Iterowanie po `str`

```for character in 'hello':
print(character)

# h
# e
# l
# l
# o
```

## 3.3. Own Implementation

```class Parking:
def __init__(self):
self._parked_cars = list()

def park(self, car):
self._parked_cars.append(car)

def __iter__(self):
self._current_element = 0
return self

def __next__(self):
if self._current_element >= len(self._parked_cars):
raise StopIteration

result = self._parked_cars[self._current_element]
self._current_element += 1
return result

parking = Parking()
parking.park('Mercedes')
parking.park('Maluch')
parking.park('Toyota')

for car in parking:
print(car)

# Mercedes
# Maluch
# Toyota
```

## 3.4. `itertools`

### 3.4.1. `chain()`

```from itertools import chain

class Numbers:
def __init__(self, *values):
self.values = values
self._iter_index = 0

def __iter__(self):
self._iter_index = 0
return self

def __next__(self):
if self._iter_index >= len(self.values):
raise StopIteration

element = self.values[self._iter_index]
self._iter_index += 1
return element

class Characters:
def __init__(self, *values):
self.values = values
self._iter_index = 0

def __iter__(self):
self._iter_index = 0
return self

def __next__(self):
if self._iter_index >= len(self.values):
raise StopIteration

element = self.values[self._iter_index]
self._iter_index += 1
return element

num = Numbers(1, 2, 3)
chr = Characters('a', 'b', 'c')

print(chain(num, chr))
# <itertools.chain object at 0x1008ca0f0>

print(list(chain(num, chr)))
# [1, 2, 3, 'a', 'b', 'c']

for x in chain(num, chr):
print(x)

# 1
# 2
# 3
# a
# b
# c
```

### 3.4.2. `cycle()`

```from itertools import cycle

DATA = ['even', 'odd']

for x in cycle(DATA):
print(x)

# even
# odd
# even
# odd
# even
# ...
```
```from itertools import cycle

DATA = ['even', 'odd']

for i, status in enumerate(cycle(DATA)):
print(i, status)

# 0, even
# 1, odd
# 2, even
# ...
```

```def parzyste_f4():
for x in range(0, 30):
if x % 2 == 0:
yield float(x)

for number in DATA:
print(number)

try:

number = DATA.__next__()
print(number)

number = DATA.__next__()
print(number)

number = DATA.__next__()
print(number)

number = DATA.__next__()
print(number)

except StopIteration:
pass
```

## 3.6. Assignments

### 3.6.1. Range

1. Zaimplementuj własne rozwiązanie `range()` wykorzystując iterator.

2. Początek, koniec, krok (step)

• Complexity level: easy

• Lines of code to write: 20 lines

• Estimated time of completion: 15 min

• Input data: Listing 368.

1. Na podstawie kodu z listingu Listing 368.

2. Zmodyfikuj odpowiednie klasy aby stworzyć iterator

Listing 368. Struktury danych książki adresowej
```from dataclasses import dataclass

@dataclass
class Contact:
first_name: str
last_name: str

@dataclass
center: str
location: str