"""Python Cookbook

Chapter 9, Examples from the text.

•	Writing generator functions with the yield statement
•	Applying transformations to a collection
•	Using stacked generator expressions
•	Picking a subset – three ways to filter
•	Summarizing a collection – how to reduce
•	Combining map and reduce transformations
•	Implementing "there exists" processing
•	Creating a partial function
•	Simplifying complex algorithms with immutable data structures
•	Writing recursive generator functions with the yield from statement

Note: Output from this is used in Chapter 4 examples.
"""

>>> from Chapter_09.ch09_r01 import RawLog

>>> data = [
...    RawLog('2016-04-24 11:05:01,462', 'INFO', 'module1', 'Sample Message One'),
...    RawLog('2016-04-24 11:06:02,624', 'DEBUG', 'module2', 'Debugging'),
...    RawLog('2016-04-24 11:07:03,246', 'WARNING', 'module1', 'Something might have gone wrong')
... ]

>>> from pprint import pprint
>>> from Chapter_09.ch09_r01 import parse_date_iter
>>> for item in parse_date_iter(data):
...     pprint(item)
DatedLog(date=datetime.datetime(2016, 4, 24, 11, 5, 1, 462000), level='INFO', module='module1', message='Sample Message One')
DatedLog(date=datetime.datetime(2016, 4, 24, 11, 6, 2, 624000), level='DEBUG', module='module2', message='Debugging')
DatedLog(date=datetime.datetime(2016, 4, 24, 11, 7, 3, 246000), level='WARNING', module='module1', message='Something might have gone wrong')

>>> details = list(parse_date_iter(data))
>>> pprint(details)
[DatedLog(date=datetime.datetime(2016, 4, 24, 11, 5, 1, 462000), level='INFO', module='module1', message='Sample Message One'),
 DatedLog(date=datetime.datetime(2016, 4, 24, 11, 6, 2, 624000), level='DEBUG', module='module2', message='Debugging'),
 DatedLog(date=datetime.datetime(2016, 4, 24, 11, 7, 3, 246000), level='WARNING', module='module1', message='Something might have gone wrong')]

>>> parse_date_iter(data)  # doctest: +ELLIPSIS
<generator object parse_date_iter at 0x...>

>>> def gen_func():
...     print("pre-yield")
...     yield 1
...     print("post-yield")
...     yield 2

>>> y = gen_func()
>>> next(y)
pre-yield
1
>>> next(y)
post-yield
2

>>> next(y)
Traceback (most recent call last):
  File "/Users/slott/miniconda3/envs/cookbook/lib/python3.8/doctest.py", line 1328, in __run
    compileflags, 1), test.globs)
  File "<doctest examples.txt[10]>", line 1, in <module>
    next(y)
StopIteration

# Applying transformations to a collection

>>> data = [
...    ('2016-04-24 11:05:01,462', 'INFO', 'module1', 'Sample Message One'),
...    ('2016-04-24 11:06:02,624', 'DEBUG', 'module2', 'Debugging'),
...    ('2016-04-24 11:07:03,246', 'WARNING', 'module1', 'Something might have gone wrong')
... ]

>>> def mul(a, b):
...    return a*b

>>> list_1 = [2, 3, 5, 7]
>>> list_2 = [11, 13, 17, 23]
>>> list(map(mul, list_1, list_2))
[22, 39, 85, 161]

>>> def bundle(*args):
...     return args
>>> list(map(bundle, list_1, list_2))
[(2, 11), (3, 13), (5, 17), (7, 23)]
>>> list(zip(list_1, list_2))
[(2, 11), (3, 13), (5, 17), (7, 23)]
>>> list(map(lambda *args: args, list_1, list_2))
[(2, 11), (3, 13), (5, 17), (7, 23)]

# Using stacked generator expressions

>>> from pathlib import Path
>>> import csv
>>> with Path('data/fuel.csv').open() as source_file:
...    reader = csv.reader(source_file)
...    log_rows = list(reader)

>>> log_rows[0]
['date', 'engine on', 'fuel height']
>>> log_rows[-1]
['', "choppy -- anchor in jackson's creek", '']

>>> from Chapter_09.ch09_r03 import row_merge, skip_header_date, convert_datetime
>>> row_gen = row_merge(log_rows)
>>> tail_gen = skip_header_date(row_gen)
>>> datetime_gen = (convert_datetime(row) for row in tail_gen)
>>> for row in datetime_gen:
...     print(f"{row.date}: duration {row.engine_off-row.engine_on}")
2013-10-25: duration 4:51:00
2013-10-26: duration 9:13:00

# Picking a subset - three ways to filter

>>> from Chapter_09.ch09_r03 import row_merge, log_rows
>>> pprint(list(row_merge(log_rows)))
[CombinedRow(date='date', engine_on_time='engine on', engine_on_fuel_height='fuel height', filler_1='', engine_off_time='engine off', engine_off_fuel_height='fuel height', filler_2='', other_notes='Other notes', filler_3=''),
 CombinedRow(date='10/25/13', engine_on_time='08:24:00 AM', engine_on_fuel_height='29', filler_1='', engine_off_time='01:15:00 PM', engine_off_fuel_height='27', filler_2='', other_notes="calm seas -- anchor solomon's island", filler_3=''),
 CombinedRow(date='10/26/13', engine_on_time='09:12:00 AM', engine_on_fuel_height='27', filler_1='', engine_off_time='06:25:00 PM', engine_off_fuel_height='22', filler_2='', other_notes="choppy -- anchor in jackson's creek", filler_3='')]

# Summarizing a collection – how to reduce

# Combining map and reduce transformations

>>> typical_iterator = iter([0, 1, 2, 3, 4])
>>> sum(typical_iterator)
10
>>> sum(typical_iterator)
0

>>> from Chapter_09.ch09_r06 import (
... clean_data_iter, row_merge, total_fuel, avg_fuel_per_hour)

>>> round(
...     total_fuel(clean_data_iter(row_merge(log_rows))),
...     3)
7.0

>>> round(avg_fuel_per_hour(
...    clean_data_iter(row_merge(log_rows))), 3)
0.48

# Implementing "there exists" processing

>>> from itertools import takewhile
>>> n = 13
>>> list(takewhile(lambda i: n % i != 0, range(2, 4)))
[2, 3]
>>> n = 15
>>> list(takewhile(lambda i: n % i != 0, range(2, 4)))
[2]

# Creating a partial function

>>> from operator import mul
>>> from functools import reduce
>>> prod = lambda x: reduce(mul, x, 1)


>>> factorial = lambda x: prod(range(2,x+1))
>>> factorial(5)
120

>>> from functools import reduce, partial
>>> from operator import mul
>>> prod = partial(reduce(mul, initializer=1))
Traceback (most recent call last):
   ...
TypeError: reduce() takes no keyword arguments

>>> prod = lambda x: reduce(mul, x, 1)
>>> factorial = lambda x: prod(range(2, x+1))
>>> factorial(5)
120


# Simplifying complex algorithms with immutable data structures

>>> from Chapter_09.ch09_r09 import rank_by_y, cleanse, text_parse, text_1

>>> data = rank_by_y(cleanse(text_parse(text_1)))
>>> pprint(list(data))
[RankYDataPair(y_rank=1, pair=DataPair(x=4.0, y=4.26)),
 RankYDataPair(y_rank=2, pair=DataPair(x=7.0, y=4.82)),
 RankYDataPair(y_rank=3, pair=DataPair(x=5.0, y=5.68)),
 RankYDataPair(y_rank=4, pair=DataPair(x=8.0, y=6.95)),
 RankYDataPair(y_rank=5, pair=DataPair(x=6.0, y=7.24)),
 RankYDataPair(y_rank=6, pair=DataPair(x=13.0, y=7.58)),
 RankYDataPair(y_rank=7, pair=DataPair(x=10.0, y=8.04)),
 RankYDataPair(y_rank=8, pair=DataPair(x=11.0, y=8.33)),
 RankYDataPair(y_rank=9, pair=DataPair(x=9.0, y=8.81)),
 RankYDataPair(y_rank=10, pair=DataPair(x=14.0, y=9.96)),
 RankYDataPair(y_rank=11, pair=DataPair(x=12.0, y=10.84))]

# Writing recursive generator functions with the yield from statement

>>> from Chapter_09.ch09_r10 import find_path, document

>>> list(find_path('array_item_value2', document))
[['array', 1, 'array_item_key2']]

>>> list(find_path('value', document))
[['array', 0, 'array_item_key1'], ['field2'], ['object', 'attribute1']]

