# FIFO
В Пайтоні (і в C#, і в кількох інших мовах) існує поняття "генератор" та "ітератор". Вони були придумані, щоб змінити спосіб, яким пишуться програми.
Інтро, який я зроблю, буде основуватись на Факторіо, бо механізми конвеєрів дуже схожі на те, як працюють генератори "under the hood"
## Факторіо, генератори та пайплайни
### Producer, або початковий генератор

Тут зображено механізм, який постійно викладає на конвеєр ресурси для сайенсу. В пайтоні ми би записали це як:
```python=
import time
while True:
print("Copper wire")
time.sleep(0.5)
print("Iron plate")
time.sleep(1)
```
Але що, якщо ми хочемо потім ці елементи якось обробляти? Давайте замість виводу на екран будемо додавати їх у список, і виділимо код у функцію:
```python=
import time
queue = []
def generate_resources():
while True:
queue.append("Copper wire")
time.sleep(0.5)
queue.append("Iron plate")
time.sleep(1)
generate_resources()
```
Як тепер використати цю `queue` для постпроцесингу? Ніяк, бо функція зайшла у вічний цикл.
Для вирішення цієї задачі потрібно змінити спосіб, яким уторвююється черга. Замість `queue.append` потрібно написати `yield`:
```python=
```python=
import time
def generate_resources():
while True:
yield "Copper wire"
time.sleep(0.5)
yield "Iron plate"
time.sleep(1)
gen = generate_resources()
```
Після цього, через функцію `next` ми можемо отримувати елементи для черги:
```python
[ins] In [537]: gen
Out[537]: <generator object generate_resources at 0x7fb2d103b040>
[ins] In [538]: next(gen)
Out[538]: 'Copper wire'
[ins] In [539]: next(gen)
Out[539]: 'Iron plate'
[ins] In [540]: next(gen)
Out[540]: 'Copper wire'
```
Тобто, `yield` працює як `return` з функції, але який після `return` не закінчує функцію, а ставить "на паузу", і продовжує свою роботу після запуску `next(generator)`.
## Трансформер

Якщо використовувати генератори, то цей механізм з Факторіо можна записати так:
```python=
import time
def generate_items():
while True:
yield "Iron gear"
time.sleep(0.5)
yield "Copper plate"
time.sleep(1)
def produce_science(items_queue):
has_gear = False
has_copper = False
while True:
res = next(items_queue)
if res == "Iron gear":
has_gear = True
if res == "Copper plate":
has_copper = True
if has_gear and has_copper:
yield "Red science pack"
has_copper = False
has_gear = False
items = generate_items()
science = produce_science(items)
for item in science:
print(item)
```
Хоч в кожній функції записано вічний цикл, за рахунок yield ми можемо управляти паузами в цих циклах.
Цикл `for` в кінці буде проходити по всім елементам генератора і буде запускати всередині себе функцію `next()`.
## `yield from`
Цей код
```python=
def produce_list():
arr = list("hello world")
for item in arr:
yield item
```
можна записати коротше:
```python=
def produce_list():
yield from list("hello world")
```
## Завдання
### Генератор випадкових чисел `def gen_rand()`
Потрібно зробити генератор (через yield), який буде постійно видавати випадкові числа.
### Генератор слів `def string_words(s)`
Потрібно зробити генератор, який приймає на вхід рядок, а на вихід генерує (через yield) слова по порядку з цього рядку.
### Генератор рядків `def gen_file_lines(filename)`
Потрібно зробити генератор, який приймає на вхід назву файлу, а на вихід генерує рядки з цього файлу, по одному рядку на yield.
### `def cycle(lst)`
'cycle' --- це операція, яка отримує на вхід список, наприклад, `[1,2,3]`, а на вихід видає числа з цього списку по кругу, `1, 2, 3, 1, 2, 3, 1, 2, 3, 1, ...`
### Батчинг `def batcher(gen, batch_size)`
Потрібно написати генератор, який приймає на вхід генератор і число. Число --- це розмір батчу. Ця функція повинна збирати з генератора дані і пакувати їх у список (батч), і коли розмір батчу досягає відповідного числа, видавати на вихід (через yield) весь список.
### Перебір паролів `def brute_force(passlen)`
Написати генератор, який буде перебирати всі паролі заданої довжини, і видавати їх на вихід через yield.
### Проксі принтер `def print_proxy(gen)`
Написати функцію, яка отримує на вхід генератор, і все з нього виводить на екран. Але після виводу на екран вона вертає цей елемент через yield, щоб інші генератори могли підключатись до пайплайну.
### Прозорий запис у файл (проксі) `def file_proxy(gen, filename)`
Написати функцію, яка отримує на вхід генератор, і записує все з нього у файл. Ця функція, хоч і зчитує з генератора, на вихід повинна вертати цей самий об'єкт, щоб інші фільтри і генератори могли підключатись до неї.
Proxy --- це функція, яка прозоро працює всередині якогось пайплайну.
### Контент з папки `def gen_content(pathname)`
Написати генератор, який приймає на вхід папку, а на вихід видає контент з цих файлів, по одному рядку на yield. ОБОВ'ЯЗКОВО використати попередньо написаний генератор рядків з файлу.
### Фільтр `def grep(gen, func)`
Написати фільтр генератор, який буде залишати тільки ті об'єкти з генератора, які відповідають умові. Умова задається через `func` --- лямбда або окрема функція.
### Сокет проксі `def socket_proxy(gen, ip, port)`
По прикладу файл проксі написати проксі генератор, який буде відправляти контент з генератору на віповідну IP адресу через механізм сокетів.
### Сокет сервер `def gen_socket(port)`
Написати генератор, який буде видавати всі дані, які приходять на відповідний сокет порт з мережі.
### Moving average 'def moving_average(gen, lastN)'
Написати генератор, який приймає на вхід числовий генератор, а на вихід видає через yield середнє значення останніх `lastN` чисел, згладжуючи результат.
Параметр `lastN` називається "розмір вікна", а елементи з яких робиться середнє, називаються "вікном".
### `yield from` з затримкою `def gen_schedule(gen, frequency)`
Потрібно зробити генератора, який буде видавати елементи з списку (або з іншого генератора) з заданою частотою. Тобто, ми можемо пинати цей генератор через `next()`, але він буде повертати `None`, і тільки якщо пройшов час генерації, то ми отримаємо на вихід значення.
Всередині генератора не повинно бути `time.sleep()`, але можна робити часу заміри через `time.time()`.
### `@contenxtmanager`
Прочитати про цей декоратор https://book.pythontips.com/en/latest/context_managers.html

### Ознайомитись з `itertools`
Ознайомитись з стандартними функціями над ітераторами і генераторами: https://docs.python.org/3/library/itertools.html
## Advanced
Ті, хто дійшов до цього моменту і зробив попередні завдання, ласкаво прошу до advanced рівня використання генераторів

https://speakerdeck.com/dabeaz/generators-the-final-frontier