├── Chapter15
├── demo
├── __init__.py
├── ch15_ex2.py
├── ch15_ex1.py
├── ch15_ex5.py
├── ch15_ex3.py
└── ch15_ex4.py
├── __init__.py
├── Chapter09
├── __init__.py
└── ch09_ex2.py
├── Chapter10
├── __init__.py
├── ch10_flatten.py
├── ch10_ex3.py
├── ch10_ex1.py
├── ch10_ex5.py
├── ch10_ex4.py
└── ch10_ex2.py
├── Chapter11
├── __init__.py
└── ch11_ex2.py
├── Chapter12
├── __init__.py
└── ch12_ex1.py
├── Chapter13
├── __init__.py
├── ch13_ex2.py
└── ch13_ex1.py
├── Chapter14
├── __init__.py
├── ch14_ex2.py
└── ch14_ex1.py
├── Chapter16
├── __init__.py
├── ch16_ex1.py
├── ch16_generator.py
├── ch16_ex3.py
└── ch16_ex2.py
├── Chapter01
├── __init__.py
├── ch01_ex2.py
└── ch01_ex1.py
├── Chapter02
├── __init__.py
└── ch02_ex1.py
├── Chapter03
├── __init__.py
├── ch03_ex2.py
├── ch03_ex4.py
├── ch03_ex1.py
├── ch03_ex6.py
├── ch03_ex5.py
└── ch03_ex3.py
├── Chapter04
├── __init__.py
├── ch04_ex3.py
├── ch04_ex4.py
└── ch04_ex2.py
├── Chapter05
├── __init__.py
├── ch05_ex3.py
├── ch05_ex2.py
└── ch05_ex1.py
├── Chapter06
├── __init__.py
├── ch06_ex4.py
├── ch06_ex1.py
├── ch06_ex2.py
├── ch06_ex3.py
└── ch06_ex5.py
├── Chapter07
├── __init__.py
├── ch07_ex2.py
├── ch07_ex1.py
└── ch07_ex4.py
├── Chapter08
├── __init__.py
├── ch08_ex1.py
└── ch08_ex2.py
├── mypy.ini
├── Bonus
├── page-layout.css
├── build.sh
├── docutils.conf
├── index.txt
└── pygments-long.css
├── IMG_2705.jpg
├── example.log.gz
├── Software and Hardware list.pdf
├── contigency.csv
├── .gitattributes
├── Anscombe.txt
├── stubs
├── bs4.pyi
└── PIL.pyi
├── .gitignore
├── LICENSE
├── crayola.gpl
├── test_all.py
├── README.md
└── 1000.txt
/Chapter15/demo:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Chapter09/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Chapter10/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Chapter11/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Chapter12/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Chapter13/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Chapter14/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Chapter15/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Chapter16/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Chapter01/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Chapter02/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Chapter03/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Chapter04/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Chapter05/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Chapter06/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Chapter07/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Chapter08/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/mypy.ini:
--------------------------------------------------------------------------------
1 | [mypy]
2 | dump_inference_stats=False
3 |
--------------------------------------------------------------------------------
/Bonus/page-layout.css:
--------------------------------------------------------------------------------
1 | /* Page layout tweaks */
2 | div.document { width: 6in; }
3 |
--------------------------------------------------------------------------------
/IMG_2705.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Functional-Python-Programming-Second-Edition/HEAD/IMG_2705.jpg
--------------------------------------------------------------------------------
/example.log.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Functional-Python-Programming-Second-Edition/HEAD/example.log.gz
--------------------------------------------------------------------------------
/Software and Hardware list.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Functional-Python-Programming-Second-Edition/HEAD/Software and Hardware list.pdf
--------------------------------------------------------------------------------
/Bonus/build.sh:
--------------------------------------------------------------------------------
1 | python3 -m pylit chi_sq.py
2 | rst2html.py --input-encoding=utf-8 chi_sq.py.txt chi_sq.py.html
3 | python3 -m pylit case_study.py
4 | rst2html.py --input-encoding=utf-8 case_study.py.txt case_study.py.html
5 |
--------------------------------------------------------------------------------
/Bonus/docutils.conf:
--------------------------------------------------------------------------------
1 | # docutils.conf
2 |
3 | [html4css1 writer]
4 | stylesheet-path: /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/docutils/writers/html4css1/html4css1.css,
5 | pygments-long.css,
6 | page-layout.css
7 | syntax-highlight: long
8 |
--------------------------------------------------------------------------------
/contigency.csv:
--------------------------------------------------------------------------------
1 | shift,A,,B,,C,,D,,total
2 | ,obs,exp,obs,exp,obs,exp,obs,exp
3 | 0,15,22.511326860841425,21,20.990291262135923,45,38.93851132686084,13,11.559870550161813,94
4 | 1,26,22.990291262135923,31,21.436893203883496,34,39.76699029126214,5,11.805825242718447,96
5 | 2,33,28.498381877022656,17,26.57281553398058,49,49.29449838187703,20,14.634304207119742,119
6 | total,74,,69,,128,,38,,309
7 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 |
7 | # Standard to msysgit
8 | *.doc diff=astextplain
9 | *.DOC diff=astextplain
10 | *.docx diff=astextplain
11 | *.DOCX diff=astextplain
12 | *.dot diff=astextplain
13 | *.DOT diff=astextplain
14 | *.pdf diff=astextplain
15 | *.PDF diff=astextplain
16 | *.rtf diff=astextplain
17 | *.RTF diff=astextplain
18 |
--------------------------------------------------------------------------------
/Anscombe.txt:
--------------------------------------------------------------------------------
1 | Anscombe's quartet
2 | I II III IV
3 | x y x y x y x y
4 | 10.0 8.04 10.0 9.14 10.0 7.46 8.0 6.58
5 | 8.0 6.95 8.0 8.14 8.0 6.77 8.0 5.76
6 | 13.0 7.58 13.0 8.74 13.0 12.74 8.0 7.71
7 | 9.0 8.81 9.0 8.77 9.0 7.11 8.0 8.84
8 | 11.0 8.33 11.0 9.26 11.0 7.81 8.0 8.47
9 | 14.0 9.96 14.0 8.10 14.0 8.84 8.0 7.04
10 | 6.0 7.24 6.0 6.13 6.0 6.08 8.0 5.25
11 | 4.0 4.26 4.0 3.10 4.0 5.39 19.0 12.50
12 | 12.0 10.84 12.0 9.13 12.0 8.15 8.0 5.56
13 | 7.0 4.82 7.0 7.26 7.0 6.42 8.0 7.91
14 | 5.0 5.68 5.0 4.74 5.0 5.73 8.0 6.89
15 |
--------------------------------------------------------------------------------
/stubs/bs4.pyi:
--------------------------------------------------------------------------------
1 | """
2 | Stub for a few features of the bs4 BeautifulSoup class.
3 |
4 | To use this.
5 |
6 | ::
7 |
8 | export MYPYPATH=/path/to/your/stubs
9 | """
10 |
11 | from typing import *
12 |
13 | class BeautifulSoup(Iterable):
14 | def __init__(self, source: bytes, parser: Optional[str]=None) -> None: ...
15 |
16 | html: BeautifulSoup
17 | body: BeautifulSoup
18 | table: BeautifulSoup
19 | children: BeautifulSoup
20 | text: str
21 |
22 | def __iter__(self) -> Iterator[BeautifulSoup]: ...
23 |
--------------------------------------------------------------------------------
/Chapter15/ch15_ex2.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 15, Example Set 2
5 | """
6 |
7 | from http.server import HTTPServer, SimpleHTTPRequestHandler
8 |
9 | def server_demo():
10 | running = True
11 | httpd = HTTPServer(('localhost', 8080), SimpleHTTPRequestHandler)
12 | while running:
13 | httpd.handle_request()
14 | httpd.shutdown()
15 |
16 | def test():
17 | import doctest
18 | doctest.testmod(verbose=1)
19 |
20 | if __name__ == "__main__":
21 | test()
22 | #server_demo()
23 |
--------------------------------------------------------------------------------
/Chapter10/ch10_flatten.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 10, Flatten some NIST sample data.
5 |
6 | """
7 | import csv
8 | import io
9 | import random
10 |
11 | raw_data = """\
12 | Group 1 6.9 5.4 5.8 4.6 4.0
13 | Group 2 8.3 6.8 7.8 9.2 6.5
14 | Group 3 8.0 10.5 8.1 6.9 9.3
15 | Group 4 5.8 3.8 6.1 5.6 6.2
16 | """
17 |
18 | def row_iter_tab( source ):
19 | rdr= csv.reader( source, delimiter="\t" )
20 | return rdr
21 | def pieces(grouped):
22 | for row in grouped:
23 | yield from ((row[0][-1], float(v)) for v in row[1:])
24 | #for t in ((row[0][-1], float(v)) for v in row[1:]):
25 | # yield t
26 |
27 | if __name__ == "__main__":
28 | grouped= tuple( row_iter_tab(io.StringIO(raw_data)))
29 | data= list(pieces(grouped))
30 | random.shuffle(data)
31 | print( data )
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Windows image file caches
2 | Thumbs.db
3 | ehthumbs.db
4 |
5 | # Folder config file
6 | Desktop.ini
7 |
8 | # Recycle Bin used on file shares
9 | $RECYCLE.BIN/
10 |
11 | # Windows Installer files
12 | *.cab
13 | *.msi
14 | *.msm
15 | *.msp
16 |
17 | # Windows shortcuts
18 | *.lnk
19 |
20 | # =========================
21 | # Operating System Files
22 | # =========================
23 |
24 | # OSX
25 | # =========================
26 |
27 | .DS_Store
28 | .AppleDouble
29 | .LSOverride
30 |
31 | # Thumbnails
32 | ._*
33 |
34 | # Files that might appear in the root of a volume
35 | .DocumentRevisions-V100
36 | .fseventsd
37 | .Spotlight-V100
38 | .TemporaryItems
39 | .Trashes
40 | .VolumeIcon.icns
41 |
42 | # Directories potentially created on remote AFP share
43 | .AppleDB
44 | .AppleDesktop
45 | Network Trash Folder
46 | Temporary Items
47 | .apdisk
48 |
--------------------------------------------------------------------------------
/Bonus/index.txt:
--------------------------------------------------------------------------------
1 |
2 | #############################################
3 | Function Python Programming Bonus Code
4 | #############################################
5 |
6 | © 2018, Steven F. Lott
7 |
8 | .. raw:: html
9 |
10 |
11 |
12 |
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
13 |
14 | Some Bonus Material.
15 |
16 |
17 |
18 | - `build.py.html `_
19 | - `case_study.py.html `_
20 | - `chi_sq.py.html `_
21 |
22 |
23 | Updated 2018-02-24 17:54:39.814749
24 |
--------------------------------------------------------------------------------
/stubs/PIL.pyi:
--------------------------------------------------------------------------------
1 | """
2 | Stub for a few features of the PIL Image class.
3 |
4 | To use this.
5 |
6 | ::
7 |
8 | export MYPYPATH=/path/to/your/stubs
9 |
10 | The `stubs` directory name must be last. Replace `/path/to/your` with
11 | the actual path to the download directory with the stubs.
12 |
13 | NOTE.
14 |
15 | This assumes RGB multi-band images. This is not true in general,
16 | and the proper definition of a pixel should be something more along
17 | the lines of ``Union[int, Tuple]``.
18 | """
19 |
20 | from typing import *
21 |
22 | class Image:
23 | def __init__(self): ...
24 | @property
25 | def size(self) -> Tuple[int, int]: ...
26 | def getpixel(self, coordinate: Tuple[int, int]) -> Tuple[int, int, int]: ...
27 | def putpixel(self, coordinate: Tuple[int, int], value: Tuple[int, int, int]): ...
28 | def copy(self) -> Image: ...
29 | def show(self): ...
30 | @staticmethod
31 | def open(filename: str) -> Image: ...
32 |
--------------------------------------------------------------------------------
/Chapter10/ch10_ex3.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 10, Example Set 3
5 | """
6 | # pylint: disable=wrong-import-position
7 |
8 | from functools import partial
9 |
10 | def performance():
11 | import timeit
12 | f1 = timeit.timeit("""exp2(12)""", setup="""
13 | from functools import partial
14 | exp2 = partial(pow, 2)""")
15 | print("partial", f1)
16 |
17 | f2 = timeit.timeit("""exp2(12)""", """exp2 = lambda y: pow(2, y)""")
18 | print("lambda", f2)
19 |
20 | test_correctness = """
21 | >>> exp2 = partial(pow, 2)
22 | >>> exp2(12)
23 | 4096
24 | >>> exp2(17)-1
25 | 131071
26 | >>> exp2 = lambda y: pow(2, y)
27 | >>> exp2(12)
28 | 4096
29 | >>> exp2(17)-1
30 | 131071
31 | """
32 |
33 | __test__ = {
34 | "test_correctness": test_correctness,
35 | }
36 |
37 | def test():
38 | import doctest
39 | doctest.testmod(verbose=1)
40 |
41 | if __name__ == "__main__":
42 | test()
43 | performance()
44 |
--------------------------------------------------------------------------------
/Chapter13/ch13_ex2.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 13, Example Set 2
5 | """
6 | # pylint: disable=unused-wildcard-import,wrong-import-position,unused-import
7 |
8 | import re
9 | p1 = re.compile(r"(some) pattern")
10 | p2 = re.compile(r"a (different) pattern")
11 |
12 | from typing import Optional, Match
13 | def matcher(text: str) -> Optional[Match[str]]:
14 | patterns = [p1, p2]
15 | matching = (p.search(text) for p in patterns)
16 | try:
17 | good = next(filter(None, matching))
18 | return good
19 | except StopIteration:
20 | pass
21 | return None
22 |
23 | test_matcher = '''
24 | >>> text = "nothing"
25 | >>> matcher(text)
26 | >>> text = "this has some pattern in it"
27 | >>> matcher(text)
28 | <_sre.SRE_Match object; span=(9, 21), match='some pattern'>
29 | '''
30 |
31 | __test__ = {
32 | 'test_matcher': test_matcher
33 | }
34 |
35 | def test():
36 | import doctest
37 | doctest.testmod(verbose=1)
38 |
39 | if __name__ == "__main__":
40 | test()
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Packt
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Chapter11/ch11_ex2.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 11, Example Set 2
5 | """
6 | # pylint: disable=missing-docstring,wrong-import-position,reimported
7 | from functools import wraps
8 |
9 | def comp(func1):
10 | def abstract_decorator(func2):
11 | @wraps(func2)
12 | def composite(*args, **kw):
13 | return func1(func2(*args, **kw))
14 | return composite
15 | return abstract_decorator
16 |
17 | def minus1(y):
18 | return y-1
19 |
20 | @comp(minus1)
21 | def pow2(x):
22 | return 2**x
23 |
24 | example_1 = """
25 | >>> pow2(17)
26 | 131071
27 | """
28 |
29 | from typing import Callable
30 | m1: Callable[[float], float] = lambda x: x-1
31 | p2: Callable[[float], float] = lambda y: 2**y
32 | mersenne: Callable[[float], float] = lambda x: m1(p2(x))
33 |
34 | F_float = Callable[[float], float]
35 |
36 | example_2 = """
37 | >>> mersenne(17)
38 | 131071
39 | """
40 |
41 | __test__ = {
42 | 'example_1': example_1,
43 | }
44 |
45 | def test():
46 | import doctest
47 | doctest.testmod(verbose=1)
48 |
49 | if __name__ == "__main__":
50 | test()
51 |
--------------------------------------------------------------------------------
/Chapter16/ch16_ex1.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 16, Example Set 1
5 | """
6 | from functools import reduce
7 | from operator import mul
8 |
9 | from typing import Callable, Iterable
10 |
11 | prod: Callable[[Iterable[int]], int] = lambda x: reduce(mul, x)
12 |
13 | class Binomial:
14 | """
15 | >>> binom= Binomial()
16 | >>> binom(52,5)
17 | 2598960
18 | """
19 | def __init__(self):
20 | self.fact_cache = {}
21 | self.bin_cache = {}
22 | def fact(self, n: int) -> int:
23 | if n not in self.fact_cache:
24 | self.fact_cache[n] = prod(range(1, n+1))
25 | return self.fact_cache[n]
26 | def __call__(self, n: int, m: int) -> int:
27 | if (n, m) not in self.bin_cache:
28 | self.bin_cache[n, m] = self.fact(n)//(self.fact(m)*self.fact(n-m))
29 | return self.bin_cache[n, m]
30 |
31 | test_example = """
32 | >>> binom= Binomial()
33 | >>> binom(52,5)
34 | 2598960
35 | """
36 |
37 | __test__ = {
38 | "test_example": test_example,
39 | }
40 |
41 | def test():
42 | import doctest
43 | doctest.testmod(verbose=1)
44 |
45 | if __name__ == "__main__":
46 | test()
47 |
--------------------------------------------------------------------------------
/Chapter12/ch12_ex1.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 12, Example Set 1
5 | """
6 | import math
7 |
8 | def some_function(n: float) -> float:
9 | """
10 | An approximation of the gamma function.
11 |
12 | >>> round(some_function(4), 3)
13 | 24.0
14 | """
15 | s = sum(
16 | (
17 | 1,
18 | 1/((2**1)*(6*n)**1),
19 | 1/((2**3)*(6*n)**2),
20 | -139/((2**3)*(2*3*5)*(6*n)**3),
21 | -571/((2**6)*(2*3*5)*(6*n)**4),
22 | )
23 | )
24 | return math.sqrt(2*math.pi*n)*(n/math.e)**n*s
25 |
26 | def test():
27 | import doctest
28 | doctest.testmod()
29 |
30 | def performance():
31 | import dis
32 | dis.disassemble(some_function.__code__)
33 | size = len(some_function.__code__.co_code)
34 | print(f"size {size} bytes")
35 |
36 | import timeit
37 | t = timeit.timeit(
38 | """some_function(4)""",
39 | """from Chapter_12.ch12_ex1 import some_function"""
40 | )
41 |
42 | print(f"total time {t:.3f} sec. for 1,000,000 iterations")
43 | rate = 1_000_000*size/t
44 | print(f"rate {rate:,.0f} bytes/sec")
45 | print(f"rate {rate/1_000_000:,.1f} Mbytes/sec")
46 |
47 |
48 | if __name__ == "__main__":
49 | test()
50 | performance()
51 |
--------------------------------------------------------------------------------
/Chapter15/ch15_ex1.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 15, Example Set 1
5 | """
6 | # pylint: disable=line-too-long,no-member
7 | import http.client
8 | import urllib.request
9 | from contextlib import closing
10 |
11 | def client_demo():
12 | with closing(
13 | http.client.HTTPConnection(
14 | "slott-softwarearchitect.blogspot.com", 80)) as server:
15 | headers = {
16 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
17 | "Accept-Language": "en-us",
18 | "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10) AppleWebKit/600.1.25 (KHTML, like Gecko) Version/8.0 Safari/600.1.25",
19 | }
20 | server.request("GET", "/", headers=headers)
21 | response = server.getresponse()
22 | print(response.status, response.reason)
23 | body = response.read()
24 | print(body)
25 | with open("response.html", "wb") as result:
26 | result.write(body)
27 |
28 | def urllib_demo():
29 | with urllib.request.urlopen(
30 | "http://slott-softwarearchitect.blogspot.com") as response:
31 | print(response.read())
32 |
33 | def test():
34 | import doctest
35 | doctest.testmod(verbose=1)
36 |
37 | if __name__ == "__main__":
38 | test()
39 | #client_demo()
40 | #urllib_demo()
41 |
--------------------------------------------------------------------------------
/Chapter04/ch04_ex3.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 4, Example Set 3
5 | """
6 |
7 |
8 | test_map = """
9 | >>> from Chapter04.ch04_ex1 import (
10 | ... float_from_pair, float_lat_lon, row_iter_kml, limits, haversine, legs
11 | ... )
12 | >>> import urllib.request
13 | >>> with urllib.request.urlopen("file:./Winter%202012-2013.kml") as source:
14 | ... path= tuple(float_from_pair(float_lat_lon(row_iter_kml(source))))
15 | ... trip= tuple( (start, end, round(haversine(start, end),4))
16 | ... for start,end in legs(iter(path)))
17 |
18 | >>> distances1= tuple(map( lambda s_e: (s_e[0], s_e[1], haversine(*s_e)),
19 | ... zip(path, path[1:]) ))
20 |
21 | >>> len(distances1)
22 | 73
23 | >>> distances1[0]
24 | ((37.54901619777347, -76.33029518659048), (37.840832, -76.273834), 17.724564798884984)
25 | >>> distances1[-1]
26 | ((38.330166, -76.458504), (38.976334, -76.473503), 38.801864781785845)
27 |
28 | >>> distances2= tuple(map( lambda s, e: (s, e, haversine(s, e)), path, path[1:] ))
29 |
30 | >>> len(distances2)
31 | 73
32 | >>> distances2[0]
33 | ((37.54901619777347, -76.33029518659048), (37.840832, -76.273834), 17.724564798884984)
34 | >>> distances2[-1]
35 | ((38.330166, -76.458504), (38.976334, -76.473503), 38.801864781785845)
36 |
37 | """
38 |
39 | __test__ = {
40 | "map_tests": test_map,
41 | }
42 |
43 | def test():
44 | import doctest
45 | doctest.testmod(verbose=1)
46 |
47 | if __name__ == "__main__":
48 | test()
49 |
--------------------------------------------------------------------------------
/Chapter06/ch06_ex4.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 6, Example Set 4
5 | """
6 |
7 | from typing import Callable, Any, Iterator
8 |
9 | def syracuse(n: int) -> int:
10 | """The Syracuse function, central to the Collatz conjecture.
11 |
12 | >>> syracuse(6)
13 | 3
14 | >>> syracuse(3)
15 | 10
16 | >>> syracuse(10)
17 | 5
18 | >>> syracuse(5)
19 | 16
20 | >>> syracuse(16)
21 | 8
22 | """
23 | if n % 2 == 0:
24 | return n // 2
25 | return 3*n+1
26 |
27 | def until(
28 | termination: Callable[[Any], bool],
29 | function: Callable[[int], int],
30 | seed: int) -> Iterator[int]:
31 | """Evaluate until a termination condition is true
32 |
33 | >>> list( until(lambda x: x==1, syracuse, 13) )
34 | [13, 40, 20, 10, 5, 16, 8, 4, 2, 1]
35 | """
36 | yield seed
37 | if termination(seed):
38 | return
39 | else:
40 | #for rest in until(termination, function, function(seed) ):
41 | # yield rest
42 | yield from until(termination, function, function(seed))
43 |
44 | test_until = """
45 | >>> for i in range(1, 27):
46 | ... print( i, len( list( until(lambda x: x==1, syracuse, i) ) ) )
47 | 1 1
48 | 2 2
49 | 3 8
50 | 4 3
51 | 5 6
52 | 6 9
53 | 7 17
54 | 8 4
55 | 9 20
56 | 10 7
57 | 11 15
58 | 12 10
59 | 13 10
60 | 14 18
61 | 15 18
62 | 16 5
63 | 17 13
64 | 18 21
65 | 19 21
66 | 20 8
67 | 21 8
68 | 22 16
69 | 23 16
70 | 24 11
71 | 25 24
72 | 26 11
73 | """
74 |
75 | __test__ = {
76 | "test_until": test_until,
77 | }
78 |
79 | if __name__ == "__main__":
80 | import doctest
81 | doctest.testmod(verbose=True)
82 |
--------------------------------------------------------------------------------
/Chapter03/ch03_ex2.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 3, Example Set 2
5 | """
6 |
7 | from decimal import Decimal
8 | from typing import Text, Optional
9 | def clean_decimal_1(text: Text) -> Optional[Decimal]:
10 | """
11 | Remove $ and , from a string, return a Decimal.
12 |
13 | >>> clean_decimal_1("$1,234.56")
14 | Decimal('1234.56')
15 | """
16 | if text is None:
17 | return None
18 | return Decimal(text.replace("$", "").replace(",", ""))
19 |
20 | def replace(text: Text, a: Text, b: Text) -> Text:
21 | """Prefix function for str.replace(a,b)."""
22 | return text.replace(a, b)
23 |
24 | def clean_decimal_2(text: Text) -> Optional[Decimal]:
25 | """
26 | Remove $ and , from a string, return a Decimal.
27 |
28 | >>> clean_decimal_2("$1,234.56")
29 | Decimal('1234.56')
30 | """
31 | if text is None:
32 | return None
33 | return Decimal(replace(replace(text, "$", ""), ",", ""))
34 |
35 |
36 | def remove(text: Text, chars: Text) -> Text:
37 | """Remove all of the given chars from a string."""
38 | if chars:
39 | return remove(
40 | text.replace(chars[0], ""),
41 | chars[1:]
42 | )
43 | return text
44 |
45 | def clean_decimal_3(text: Text) -> Optional[Decimal]:
46 | """
47 | Remove $ and , from a string, return a Decimal.
48 |
49 | >>> clean_decimal_3("$1,234.56")
50 | Decimal('1234.56')
51 | """
52 | if text is None:
53 | return None
54 | return Decimal(remove(text, "$,"))
55 |
56 |
57 | def test(): # pylint: disable=missing-docstring
58 | import doctest
59 | doctest.testmod(verbose=2)
60 |
61 | if __name__ == "__main__":
62 | test()
63 |
--------------------------------------------------------------------------------
/Chapter05/ch05_ex3.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 5, Example Set 3
5 | """
6 | # pylint: disable=wrong-import-position,reimported
7 |
8 | import math
9 | from typing import Callable
10 | from typing import Optional, Any
11 |
12 | class NullAware:
13 | def __init__(self, some_func: Callable[[Any], Any]) -> None:
14 | self.some_func = some_func
15 | def __call__(self, arg: Optional[Any]) -> Optional[Any]:
16 | return None if arg is None else self.some_func(arg)
17 |
18 | null_log_scale = NullAware(math.log)
19 | null_round_4 = NullAware(lambda x: round(x, 4))
20 |
21 | test_NullAware = """
22 | >>> some_data = [ 10, 100, None, 50, 60 ]
23 | >>> scaled = map( null_log_scale, some_data )
24 | >>> [null_round_4(v) for v in scaled]
25 | [2.3026, 4.6052, None, 3.912, 4.0943]
26 | """
27 |
28 | from typing import Callable, Iterable
29 | class Sum_Filter:
30 | __slots__ = ["filter", "function"]
31 | def __init__(self,
32 | filter_f: Callable[[Any], bool],
33 | func: Callable[[Any], float]) -> None:
34 | self.filter = filter_f
35 | self.function = func
36 | def __call__(self, iterable: Iterable) -> float:
37 | return sum(self.function(x) for x in iterable if self.filter(x))
38 |
39 | count_not_none = Sum_Filter(lambda x: x is not None, lambda x: 1)
40 |
41 | test_Sum_Filter = """
42 | >>> some_data = [10, 100, None, 50, 60]
43 | >>> count_not_none(some_data)
44 | 4
45 | """
46 |
47 |
48 | __test__ = {
49 | "test_NullAware": test_NullAware,
50 | "test_Sum_Filter": test_Sum_Filter,
51 | }
52 |
53 | def test():
54 | import doctest
55 | doctest.testmod(verbose=1)
56 |
57 | if __name__ == "__main__":
58 | #performace()
59 | test()
60 |
--------------------------------------------------------------------------------
/Chapter05/ch05_ex2.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 5, Example Set 2
5 | """
6 | import dis
7 | from typing import Callable, Iterable, Iterator
8 |
9 | def mapping1(f: Callable, C: Iterable) -> Iterator:
10 | """
11 | >>> list(mapping1( lambda x: 2**x, range(32) ))
12 | [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608, 16777216, 33554432, 67108864, 134217728, 268435456, 536870912, 1073741824, 2147483648]
13 | """
14 | return (f(a) for a in C)
15 |
16 | def mapping2(f: Callable, C: Iterable) -> Iterator:
17 | """
18 | >>> list(mapping2( lambda x: 2**x, range(32) ))
19 | [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608, 16777216, 33554432, 67108864, 134217728, 268435456, 536870912, 1073741824, 2147483648]
20 | """
21 | for a in C:
22 | yield f(a)
23 |
24 | def performance():
25 | print("Generator Expression")
26 | dis.dis(mapping1)
27 | print(
28 | timeit.timeit(
29 | """list(mapping1( lambda x: 2**x, range(32) ))""",
30 | """
31 | def mapping1( f, C ):
32 | return (f(a) for a in C)
33 | """
34 | )
35 | )
36 |
37 | print("Generator Function")
38 | dis.dis(mapping2)
39 | print(
40 | timeit.timeit(
41 | """list(mapping2( lambda x: 2**x, range(32) ))""",
42 | """
43 | def mapping2( f, C ):
44 | for a in C:
45 | yield f(a)
46 | """
47 | )
48 | )
49 |
50 | def test():
51 | import doctest
52 | doctest.testmod(verbose=1)
53 |
54 | if __name__ == "__main__":
55 | #import timeit
56 | #performace()
57 | test()
58 |
--------------------------------------------------------------------------------
/Chapter01/ch01_ex2.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 1, Example Set 2
5 |
6 | Newton-Raphson root-finding via bisection.
7 |
8 | http://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf
9 |
10 | Translated from Miranda to Python.
11 | """
12 | from typing import Callable, Iterator
13 |
14 | # next_ = lambda n, x: (x+n/x)/2
15 |
16 | def next_(n: float, x: float) -> float:
17 | # pylint: disable=anomalous-backslash-in-string
18 | """
19 | .. math::
20 |
21 | a_{i+1} = (a_i+n/a_i)/2
22 |
23 | Converges on
24 |
25 | .. math::
26 |
27 | a = (a+n/a)/2
28 |
29 | So
30 |
31 | .. math::
32 |
33 | 2a &= a+n/a \\
34 | a &= n/a \\
35 | a^2 &= n \\
36 | a &= \sqrt n
37 | """
38 | return (x+n/x)/2
39 |
40 | def repeat(f: Callable[[float], float], a: float) -> Iterator[float]:
41 | """yields a, f(a), f(f(a)), etc."""
42 | yield a
43 | yield from repeat(f, f(a))
44 |
45 | def within(eps: float, iterable: Iterator[float]) -> Iterator[float]:
46 | def head_tail(eps: float, a: float, iterable: Iterator[float]):
47 | b = next(iterable)
48 | if abs(a-b) <= eps:
49 | return b
50 | return head_tail(eps, b, iterable)
51 |
52 | return head_tail(eps, next(iterable), iterable)
53 |
54 | def sqrt(a0: float, eps: float, n: float):
55 | return within(eps, repeat(lambda x: next_(n, x), a0))
56 |
57 | def test():
58 | """
59 | >>> round(next_( 2, 1.5 ), 4)
60 | 1.4167
61 | >>> n= 2
62 | >>> f= lambda x: next_( n, x )
63 | >>> a0= 1.0
64 | >>> [ round(x,4) for x in (a0, f(a0), f(f(a0)), f(f(f(a0))),) ]
65 | [1.0, 1.5, 1.4167, 1.4142]
66 |
67 | >>> within( .5, iter([3, 2, 1, .5, .25]) )
68 | 0.5
69 |
70 | >>> round( sqrt( 1.0, .0001, 3 ), 6 )
71 | 1.732051
72 | >>> round(1.732051**2, 5)
73 | 3.0
74 | """
75 | import doctest
76 | doctest.testmod(verbose=1)
77 |
78 | if __name__ == "__main__":
79 | test()
80 |
--------------------------------------------------------------------------------
/Chapter16/ch16_generator.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 16, Sample Data Generator
5 | """
6 | import csv
7 | import random
8 | from itertools import cycle, islice
9 | from typing import List, Tuple, Optional
10 |
11 | seed = [
12 | ('1', 'A', 15),
13 | ('2', 'A', 26),
14 | ('3', 'A', 33),
15 | ('1', 'B', 21),
16 | ('2', 'B', 31),
17 | ('3', 'B', 17),
18 | ('1', 'C', 45),
19 | ('2', 'C', 34),
20 | ('3', 'C', 49),
21 | ('1', 'D', 13),
22 | ('2', 'D', 5),
23 | ('3', 'D', 20),
24 | ]
25 |
26 | def create_data(seed: List[Tuple[str, str, int]]) -> None:
27 | Data = Tuple[str, Optional[str]]
28 |
29 | raw_defects: List[Data] = [
30 | (shift, defect)
31 | for shift, defect, count in seed for x in range(count)
32 | ]
33 |
34 | # Should be `set(shift for shift, defect, count in seed)`
35 | # Also 1000-309 is based on 1000-sum(count for shift, defect, count in seed)
36 | shifts = ['1', '2', '3']
37 | non_defects: List[Data] = [
38 | (shift, None)
39 | for shift in islice(cycle(shifts), 1000-309)
40 | ]
41 |
42 | data = raw_defects + non_defects
43 |
44 | random.shuffle(data)
45 |
46 | with open("qa_data.csv", 'w', newline='') as output:
47 | wtr = csv.writer(output)
48 | wtr.writerow(["shift", "defect_type", "serial_number"])
49 | wtr.writerows(
50 | (s_d[0], s_d[1], serial)
51 | for serial, s_d in enumerate(data, start=12345)
52 | )
53 |
54 | def verify_data(seed: List[Tuple[str, str, int]]) -> None:
55 | from collections import Counter
56 | with open("qa_data.csv", newline="") as input_file:
57 | rdr = csv.DictReader(input_file)
58 | defects = (
59 | (row['shift'], row['defect_type'])
60 | for row in rdr if row['defect_type']
61 | )
62 | tally = Counter(defects)
63 | print(tally)
64 | expected = Counter({(s, d): c for s, d, c in seed})
65 | assert tally == expected
66 |
67 | if __name__ == "__main__":
68 | #create_data(seed)
69 | verify_data(seed)
70 |
--------------------------------------------------------------------------------
/Chapter07/ch07_ex2.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 7, Example Set 2
5 | """
6 | # pylint: disable=wrong-import-order,wrong-import-position,reimported
7 |
8 | from typing import NamedTuple
9 |
10 | class Pair(NamedTuple):
11 | x: float
12 | y: float
13 |
14 | from typing import Callable, List, Tuple, Iterable
15 | RawPairIter = Iterable[Tuple[float, float]]
16 | pairs: Callable[[RawPairIter], List[Pair]] \
17 | = lambda source: list(Pair(*row) for row in source)
18 |
19 | from typing import Iterable, Iterator, Tuple
20 | RankedPair = Tuple[int, Pair]
21 | def rank_y(pair_iter: Iterable[Pair]) -> Iterator[RankedPair]:
22 | return enumerate(sorted(pair_iter, key=lambda p: p.y))
23 |
24 | Rank2Pair = Tuple[int, RankedPair]
25 | def rank_x(ranked_pair_iter: Iterable[RankedPair]) -> Iterator[Rank2Pair]:
26 | return enumerate(
27 | sorted(ranked_pair_iter, key=lambda rank: rank[1].x)
28 | )
29 |
30 | test_ranking = """
31 | >>> from Chapter03.ch03_ex5 import series, head_map_filter, row_iter
32 | >>> with open("Anscombe.txt") as source:
33 | ... data = list(head_map_filter(row_iter(source)))
34 | ...
35 | >>> series_I = pairs(series(0, data))
36 | >>> series_II = pairs(series(1, data))
37 | >>> series_III = pairs(series(2, data))
38 | >>> series_IV = pairs(series(3, data))
39 |
40 | >>> y_rank = list(rank_y(series_I))
41 | >>> y_rank
42 | [(0, Pair(x=4.0, y=4.26)), (1, Pair(x=7.0, y=4.82)), (2, Pair(x=5.0, y=5.68)), (3, Pair(x=8.0, y=6.95)), (4, Pair(x=6.0, y=7.24)), (5, Pair(x=13.0, y=7.58)), (6, Pair(x=10.0, y=8.04)), (7, Pair(x=11.0, y=8.33)), (8, Pair(x=9.0, y=8.81)), (9, Pair(x=14.0, y=9.96)), (10, Pair(x=12.0, y=10.84))]
43 | >>> xy_rank = list(rank_x(y_rank))
44 | >>> xy_rank
45 | [(0, (0, Pair(x=4.0, y=4.26))), (1, (2, Pair(x=5.0, y=5.68))), (2, (4, Pair(x=6.0, y=7.24))), (3, (1, Pair(x=7.0, y=4.82))), (4, (3, Pair(x=8.0, y=6.95))), (5, (8, Pair(x=9.0, y=8.81))), (6, (6, Pair(x=10.0, y=8.04))), (7, (7, Pair(x=11.0, y=8.33))), (8, (10, Pair(x=12.0, y=10.84))), (9, (5, Pair(x=13.0, y=7.58))), (10, (9, Pair(x=14.0, y=9.96)))]
46 |
47 | """
48 |
49 | __test__ = {
50 | "test_ranking": test_ranking,
51 | }
52 |
53 | def test():
54 | import doctest
55 | doctest.testmod(verbose=1)
56 |
57 | if __name__ == "__main__":
58 | test()
59 |
--------------------------------------------------------------------------------
/Chapter08/ch08_ex1.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 8, Example Set 1
5 | """
6 | # pylint: disable = wrong-import-position,wrong-import-order,too-few-public-methods,missing-docstring
7 |
8 | from Chapter04.ch04_ex1 import haversine
9 |
10 | #from collections import namedtuple
11 | #Leg = namedtuple("Leg", ("order", "start", "end", "distance"))
12 | #Point = namedtuple("Point", ("latitude", "longitude"))
13 |
14 | from typing import NamedTuple
15 | class Point(NamedTuple):
16 | latitude: float
17 | longitude: float
18 |
19 | class Leg(NamedTuple):
20 | order: int
21 | start: Point
22 | end: Point
23 | distance: float
24 |
25 | from typing import Tuple
26 | def pick_lat_lon(lon: str, lat: str, alt: str) -> Tuple[str, str]:
27 | return lat, lon
28 |
29 | from typing import Iterator, List
30 | def float_lat_lon(row_iter: Iterator[List[str]]) -> Iterator[Point]:
31 | return (
32 | Point(*map(float, pick_lat_lon(*row)))
33 | for row in row_iter
34 | )
35 |
36 | def ordered_leg_iter(
37 | pair_iter: Iterator[Tuple[Point, Point]]
38 | ) -> Iterator[Leg]:
39 | for order, pair in enumerate(pair_iter):
40 | start, end = pair
41 | yield Leg(
42 | order,
43 | start,
44 | end,
45 | round(haversine(start, end), 4)
46 | )
47 |
48 | test_parser = """
49 | >>> from Chapter06.ch06_ex3 import row_iter_kml
50 | >>> from Chapter04.ch04_ex1 import legs, haversine
51 | >>> import urllib.request
52 |
53 | >>> filename = "file:./Winter%202012-2013.kml"
54 | >>> with urllib.request.urlopen(filename) as source:
55 | ... path_iter = float_lat_lon(row_iter_kml(source))
56 | ... pair_iter = legs(path_iter)
57 | ... trip_iter = ordered_leg_iter( pair_iter )
58 | ... trip = list(trip_iter)
59 |
60 | >>> len(trip)
61 | 73
62 | >>> trip[0]
63 | Leg(order=0, start=Point(latitude=37.54901619777347, longitude=-76.33029518659048), end=Point(latitude=37.840832, longitude=-76.273834), distance=17.7246)
64 | >>> trip[-1]
65 | Leg(order=72, start=Point(latitude=38.330166, longitude=-76.458504), end=Point(latitude=38.976334, longitude=-76.473503), distance=38.8019)
66 |
67 | """
68 |
69 | __test__ = {
70 | "test_parser": test_parser,
71 | }
72 |
73 | def test():
74 | import doctest
75 | doctest.testmod(verbose=True)
76 |
77 | if __name__ == "__main__":
78 | test()
79 |
--------------------------------------------------------------------------------
/Chapter15/ch15_ex5.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 15, Example Set 5
5 |
6 | See https://tools.ietf.org/html/rfc4648
7 | """
8 | # pylint: disable=wrong-import-position
9 |
10 | import random
11 | rng = random.SystemRandom()
12 |
13 | class TestRandom:
14 | def __init__(self):
15 | self.calls = 0
16 | def randrange(self, low, high):
17 | self.calls += 1
18 | return (self.calls % (high-low)) + low
19 |
20 | import base64
21 | def make_key_1(rng=rng, size=1):
22 | """Creates a 24*size character key of upper, lower, digits, - and _.
23 |
24 | >>> test_rng = TestRandom()
25 | >>> make_key_1(test_rng)
26 | 'AQIDBAUGBwgJCgsMDQ4PEBES'
27 | >>> test_rng.calls
28 | 18
29 | """
30 | key_bytes = bytes(rng.randrange(0, 256) for i in range(18*size))
31 | key_string = base64.urlsafe_b64encode(key_bytes).decode('us-ascii')
32 | return key_string
33 |
34 | import hashlib
35 | def make_key_2(rng=rng, size=2):
36 | """Creates a fixed-size key of upper, lower, digits, - and _.
37 | sha384 produces 48 bytes which are encoded as 64 characters.
38 | The size parameter here increases the randomness, not the length.
39 |
40 | >>> test_rng = TestRandom()
41 | >>> make_key_2(test_rng)
42 | 'Luk-0U4W3bGXW0OF_UE9WYMS3ERY92eJsJnmy8khCkBVCglz0MlzuPlM1wgm1KrM'
43 | >>> test_rng.calls
44 | 512
45 | """
46 | raw_bytes = bytes(rng.randrange(0, 256) for i in range(256*size))
47 | key_bytes = hashlib.sha384(raw_bytes).digest()
48 | key_string = base64.urlsafe_b64encode(key_bytes).decode('us-ascii')
49 | return key_string
50 |
51 | def make_key_3(rng=rng, size=1):
52 | """Creates a 32*size character key of all upper case and digits.
53 |
54 | >>> test_rng = TestRandom()
55 | >>> make_key_3(test_rng)
56 | 'AEBAGBAFAYDQQCIKBMGA2DQPCAIREEYU'
57 | >>> test_rng.calls
58 | 20
59 | """
60 | key_bytes = bytes(rng.randrange(0, 256) for i in range(20*size))
61 | key_string = base64.b32encode(key_bytes).decode('us-ascii')
62 | return key_string
63 |
64 | import uuid
65 | make_key_4 = lambda: uuid.uuid4()
66 |
67 | import secrets
68 | def make_key_5(size=1):
69 | """
70 | Creates a 24*size character key
71 | """
72 | return secrets.token_urlsafe(18*size)
73 |
74 | def demo():
75 | print(make_key_1())
76 | print(make_key_2())
77 | print(make_key_3())
78 | print(make_key_4())
79 | print(make_key_5())
80 |
81 | def test():
82 | import doctest
83 | doctest.testmod(verbose=1)
84 |
85 | if __name__ == "__main__":
86 | test()
87 | demo()
88 |
--------------------------------------------------------------------------------
/Chapter10/ch10_ex1.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 10, Example Set 1
5 | """
6 | # pylint: disable=wrong-import-position
7 |
8 | def fib(n: int) -> int:
9 | """Fibonacci numbers with naive recursion
10 |
11 | >>> fib(20)
12 | 6765
13 | >>> fib(1)
14 | 1
15 | """
16 | if n == 0: return 0
17 | if n == 1: return 1
18 | return fib(n-1) + fib(n-2)
19 |
20 | from functools import lru_cache
21 |
22 | @lru_cache(maxsize=128)
23 | def fibc(n: int) -> int:
24 | """Fibonacci numbers with naive recursion and caching
25 |
26 | >>> fibc(20)
27 | 6765
28 | >>> fibc(1)
29 | 1
30 | """
31 | if n == 0: return 0
32 | if n == 1: return 1
33 | return fibc(n-1) + fibc(n-2)
34 |
35 | def performance_fib():
36 | import timeit
37 |
38 | f1 = timeit.timeit(
39 | """fib(20)""",
40 | setup="""from ch10_ex1 import fib""", number=1000)
41 | print("Naive", f1)
42 |
43 | f2 = timeit.timeit(
44 | """fibc(20); fibc.cache_clear()""",
45 | setup="""from ch10_ex1 import fibc""", number=1000)
46 | print("Cached", f2)
47 |
48 | def nfact(n: int) -> int:
49 | """
50 | >>> nfact(5)
51 | 120
52 | """
53 | if n == 0: return 1
54 | return n*nfact(n-1)
55 |
56 | @lru_cache(maxsize=128)
57 | def cfact(n: int) -> int:
58 | """
59 | >>> cfact(5)
60 | 120
61 | """
62 | if n == 0: return 1
63 | return n*cfact(n-1)
64 |
65 | from typing import Callable
66 | def binom(p: int, r: int, fact: Callable[[int], int]) -> int:
67 | """
68 | >>> nfact(5)
69 | 120
70 | >>> binom(52, 5, nfact)
71 | 2598960
72 | >>> binom(52, 5, cfact)
73 | 2598960
74 | """
75 | return fact(p)//(fact(r)*fact(p-r))
76 |
77 | def performance_fact():
78 | import timeit
79 |
80 | f1 = timeit.timeit(
81 | """binom(52, 5, nfact)""",
82 | setup="""from ch10_ex1 import binom, nfact""", number=10000)
83 | print("Naive Factorial", f1)
84 |
85 | f2 = timeit.timeit(
86 | """binom(52, 5, cfact)""",
87 | setup="""from ch10_ex1 import binom, cfact""", number=10000)
88 | print("Cached Factorial, Dirty", f2)
89 |
90 | f3 = timeit.timeit(
91 | """binom(52, 5, cfact); cfact.cache_clear()""",
92 | setup="""from ch10_ex1 import binom, cfact""", number=10000)
93 | print("Cached Factorial, Cleared", f3)
94 |
95 | def performance():
96 | performance_fib()
97 | performance_fact()
98 |
99 | def test():
100 | import doctest
101 | doctest.testmod(verbose=1)
102 |
103 | if __name__ == "__main__":
104 | test()
105 | performance()
106 |
--------------------------------------------------------------------------------
/Chapter14/ch14_ex2.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 14, Example Set 2
5 | """
6 | # pylint: disable=wrong-import-order
7 |
8 | from pymonad import curry, Just, List
9 |
10 | @curry
11 | def read_header(file):
12 | _ = file.readline()
13 | _ = file.readline()
14 | _ = file.readline()
15 | return Just([])
16 |
17 | @curry
18 | def read_rest(file, data):
19 | # file, data = file_data[0], file_data[1]
20 | txt = file.readline().rstrip()
21 | if txt:
22 | row = float * List(*txt.split("\t"))
23 | return Just(data + [list(row)]) >> read_rest(file)
24 | return Just(data)
25 |
26 | def anscombe():
27 | """
28 | >>> d= anscombe()
29 | >>> d[0]
30 | [10.0, 8.04, 10.0, 9.14, 10.0, 7.46, 8.0, 6.58]
31 | >>> d[-1]
32 | [5.0, 5.68, 5.0, 4.74, 5.0, 5.73, 8.0, 6.89]
33 | """
34 | with open("Anscombe.txt") as source:
35 | data = Just([]) >> read_header(source) >> read_rest(source)
36 | data = data.getValue()
37 | return data
38 |
39 | import random
40 |
41 | def rng():
42 | return (random.randint(1, 6), random.randint(1, 6))
43 |
44 | @curry
45 | def come_out_roll(dice, status):
46 | d = dice()
47 | if sum(d) in (7, 11):
48 | return Just(("win", sum(d), [d]))
49 | elif sum(d) in (2, 3, 12):
50 | return Just(("lose", sum(d), [d]))
51 | return Just(("point", sum(d), [d]))
52 |
53 | @curry
54 | def point_roll(dice, status):
55 | prev, point, so_far = status
56 | if prev != "point":
57 | return Just(status)
58 | d = dice()
59 | if sum(d) == 7:
60 | return Just(("craps", point, so_far+[d]))
61 | elif sum(d) == point:
62 | return Just(("win", point, so_far+[d]))
63 | return Just(("point", point, so_far+[d])) >> point_roll(dice)
64 |
65 | def craps(dice):
66 | """
67 | >>> def seven():
68 | ... return (3,4)
69 | >>> craps( seven )
70 | ('win', 7, [(3, 4)])
71 | >>> rolls= [(3,3), (2,2), (3,3)]
72 | >>> def fixed():
73 | ... global rolls
74 | ... head, *tail = rolls
75 | ... rolls= tail
76 | ... return head
77 | >>> craps( fixed )
78 | ('win', 6, [(3, 3), (2, 2), (3, 3)])
79 | """
80 | outcome = (
81 | Just(("", 0, [])) >> come_out_roll(dice)
82 | >> point_roll(dice)
83 | )
84 | print(outcome.getValue())
85 |
86 | def test():
87 | import doctest
88 | doctest.testmod(verbose=1)
89 |
90 | def demo():
91 | """
92 | Play 10 rounds of craps.
93 | """
94 | for _ in range(10):
95 | craps(rng)
96 |
97 | if __name__ == "__main__":
98 | test()
99 | demo()
100 |
--------------------------------------------------------------------------------
/Chapter09/ch09_ex2.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 9, Example Set 2
5 |
6 | An example of an optimization problem:
7 |
8 | https://www.me.utexas.edu/~jensen/ORMM/models/unit/combinatorics/permute.html
9 |
10 | """
11 | # pylint: disable=wildcard-import,unused-wildcard-import,wrong-import-position
12 |
13 | import csv
14 | import io
15 |
16 | # Cost data
17 | cost_data = """\
18 | 14,11,6,20,12,9,4
19 | 15,28,34,4,12,24,21
20 | 16,31,22,18,31,15,23
21 | 20,18,9,15,30,4,18
22 | 24,8,24,30,28,25,4
23 | 3,23,22,11,5,30,5
24 | 13,7,5,10,7,7,32
25 | """
26 |
27 | from typing import List, Tuple
28 | def get_cost_matrix() -> List[Tuple[int, ...]]:
29 | with io.StringIO(cost_data) as source:
30 | rdr = csv.reader(source)
31 | cost = list(tuple(map(int, row)) for row in rdr)
32 | return cost
33 |
34 | from itertools import *
35 |
36 | def assignment(cost: List[Tuple[int, ...]]) -> List[Tuple[int, ...]]:
37 | n = len(cost)
38 | perms = permutations(range(n))
39 | alt = [
40 | (
41 | sum(
42 | cost[x][y] for y, x in enumerate(perm)
43 | ),
44 | perm
45 | )
46 | for perm in perms
47 | ]
48 | m = min(alt)[0]
49 | return [ans for s, ans in alt if s == m]
50 |
51 | test_assignment = """
52 | >>> from pprint import pprint
53 | >>> cost= get_cost_matrix()
54 | >>> len(cost)
55 | 7
56 | >>> pprint(cost)
57 | [(14, 11, 6, 20, 12, 9, 4),
58 | (15, 28, 34, 4, 12, 24, 21),
59 | (16, 31, 22, 18, 31, 15, 23),
60 | (20, 18, 9, 15, 30, 4, 18),
61 | (24, 8, 24, 30, 28, 25, 4),
62 | (3, 23, 22, 11, 5, 30, 5),
63 | (13, 7, 5, 10, 7, 7, 32)]
64 |
65 | >>> solutions = assignment(cost)
66 | >>> pprint(solutions)
67 | [(2, 4, 6, 1, 5, 3, 0), (2, 6, 0, 1, 5, 3, 4)]
68 |
69 | >>> expected= tuple(map(lambda x:x-1, [3,5,7,2,6,4,1] ) )
70 | >>> expected
71 | (2, 4, 6, 1, 5, 3, 0)
72 | >>> expected in solutions
73 | True
74 | """
75 |
76 | def performance():
77 | """Takes almost 1 minute."""
78 | import timeit
79 | perf = timeit.timeit(
80 | """list(permutations(range(10)))""",
81 | """from itertools import permutations""",
82 | number=100)
83 |
84 | print("10!", perf/100)
85 |
86 | test_combinations = """
87 | >>> hands= list(combinations( tuple(product(range(13),'♠♥♦♣')), 5 ))
88 | >>> print( len(hands) )
89 | 2598960
90 | """
91 |
92 | __test__ = {
93 | "test_assignment": test_assignment,
94 | "test_combinations": test_combinations,
95 | }
96 |
97 | def test():
98 | import doctest
99 | doctest.testmod(verbose=1)
100 |
101 | if __name__ == "__main__":
102 | #performance()
103 |
104 | test()
105 |
--------------------------------------------------------------------------------
/Bonus/pygments-long.css:
--------------------------------------------------------------------------------
1 | /* example stylesheet for Docutils */
2 |
3 | /* :Author: Günter Milde */
4 | /* :Copyright: © 2012 G. Milde */
5 | /* :License: This stylesheet is placed in the public domain. */
6 |
7 | /* Syntax highlight rules for HTML documents generated with Docutils */
8 | /* using the ``--syntax-highlight=long`` option (new in v. 0.9). */
9 |
10 | /* This stylesheet implements Pygment's "default" style with less rules than */
11 | /* pygments-default using class hierarchies. */
12 | /* Use it as example for "handcrafted" styles with only few rules. */
13 |
14 | .code { background: #f8f8f8; }
15 | .code .comment { color: #008800; font-style: italic }
16 | .code .error { border: 1px solid #FF0000 }
17 | .code .generic.deleted { color: #A00000 }
18 | .code .generic.emph { font-style: italic }
19 | .code .generic.error { color: #FF0000 }
20 | .code .generic.heading { color: #000080; font-weight: bold }
21 | .code .generic.inserted { color: #00A000 }
22 | .code .generic.output { color: #808080 }
23 | .code .generic.prompt { color: #000080; font-weight: bold }
24 | .code .generic.strong { font-weight: bold }
25 | .code .generic.subheading { color: #800080; font-weight: bold }
26 | .code .generic.traceback { color: #0040D0 }
27 | .code .keyword { color: #AA22FF; font-weight: bold }
28 | .code .keyword.pseudo { font-weight: normal }
29 | .code .literal.number { color: #666666 }
30 | .code .literal.string { color: #BB4444 }
31 | .code .literal.string.doc { color: #BB4444; font-style: italic }
32 | .code .literal.string.escape { color: #BB6622; font-weight: bold }
33 | .code .literal.string.interpol { color: #BB6688; font-weight: bold }
34 | .code .literal.string.other { color: #008000 }
35 | .code .literal.string.regex { color: #BB6688 }
36 | .code .literal.string.symbol { color: #B8860B }
37 | .code .name.attribute { color: #BB4444 }
38 | .code .name.builtin { color: #AA22FF }
39 | .code .name.class { color: #0000FF }
40 | .code .name.constant { color: #880000 }
41 | .code .name.decorator { color: #AA22FF }
42 | .code .name.entity { color: #999999; font-weight: bold }
43 | .code .name.exception { color: #D2413A; font-weight: bold }
44 | .code .name.function { color: #00A000 }
45 | .code .name.label { color: #A0A000 }
46 | .code .name.namespace { color: #0000FF; font-weight: bold }
47 | .code .name.tag { color: #008000; font-weight: bold }
48 | .code .name.variable { color: #B8860B }
49 | .code .operator { color: #666666 }
50 | .code .operator.word { color: #AA22FF; font-weight: bold }
51 |
--------------------------------------------------------------------------------
/Chapter01/ch01_ex1.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 1, Example Set 1
5 | """
6 |
7 | def sum_numeric():
8 | """Purely numeric.
9 |
10 | >>> sum_numeric()
11 | 23
12 | """
13 | s = 0
14 | for n in range(1, 10):
15 | if n % 3 == 0 or n % 5 == 0:
16 | s += n
17 | print(s)
18 |
19 | def sum_object_light():
20 | """Some Object Features.
21 |
22 | >>> sum_object_light()
23 | 23
24 | """
25 | m = list()
26 | for n in range(1, 10):
27 | if n % 3 == 0 or n % 5 == 0:
28 | m.append(n)
29 | print(sum(m))
30 |
31 | class Summable_List(list):
32 | def sum(self):
33 | s = 0
34 | for v in self:
35 | s += v
36 | return s
37 |
38 | def sum_full_oo():
39 | """Full-on OO.
40 |
41 | >>> sum_full_oo()
42 | 23
43 | """
44 | m = Summable_List()
45 | for n in range(1, 10):
46 | if n % 3 == 0 or n % 5 == 0:
47 | m.append(n)
48 | print(m.sum())
49 |
50 | def foldr(seq, op, init):
51 | """Recursive sum.
52 |
53 | >>> foldr( [2,3,5,7], lambda x,y: x+y, 0 )
54 | 17
55 | """
56 | if len(seq) == 0:
57 | return init
58 | return op(seq[0], sum(seq[1:]))
59 |
60 | def until(n, filter_func, v):
61 | """Build a list: list( filter( filter_func, range(n) ) )
62 |
63 | >>> list( filter( lambda x: x%3==0 or x%5==0, range(10) ) )
64 | [0, 3, 5, 6, 9]
65 | >>> until(10, lambda x: x%3==0 or x%5==0, 0)
66 | [0, 3, 5, 6, 9]
67 | """
68 | if v == n:
69 | return []
70 | if filter_func(v):
71 | return [v] + until(n, filter_func, v+1)
72 | else:
73 | return until(n, filter_func, v+1)
74 |
75 | def sum_functional():
76 | """
77 | >>> sum_functional()
78 | 23
79 | """
80 | mult_3_5 = lambda x: x%3 == 0 or x%5 == 0
81 | add = lambda x, y: x+y
82 | return foldr(until(10, mult_3_5, 0), add, 0)
83 |
84 | def sum_hybrid():
85 | """Hybrid Function.
86 |
87 | >>> sum_hybrid()
88 | 23
89 | """
90 | print(sum(n for n in range(1, 10) if n%3 == 0 or n%5 == 0))
91 |
92 | def folding():
93 | """Performance differences from folding.
94 |
95 | >>> ((([]+[1])+[2])+[3])+[4]
96 | [1, 2, 3, 4]
97 | >>> []+([1]+([2]+([3]+[4])))
98 | [1, 2, 3, 4]
99 | """
100 | print("foldl", timeit.timeit("((([]+[1])+[2])+[3])+[4]"))
101 | print("foldr", timeit.timeit("[]+([1]+([2]+([3]+[4])))"))
102 |
103 | demo_1 = """
104 | >>> def sumr(seq):
105 | ... if len(seq) == 0: return 0
106 | ... return seq[0] + sumr(seq[1:])
107 | >>> sumr([7, 11])
108 | 18
109 | >>> sumr([11])
110 | 11
111 | >>> sumr([])
112 | 0
113 | """
114 |
115 | __test__ = {
116 | 'demo_1': demo_1
117 | }
118 |
119 | def test():
120 | import doctest
121 | doctest.testmod(verbose=1)
122 |
123 | if __name__ == "__main__":
124 | test()
125 | # import timeit
126 | # folding()
127 |
--------------------------------------------------------------------------------
/Chapter03/ch03_ex4.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 3, Example Set 4
5 | """
6 |
7 | import math
8 | from typing import Iterator
9 |
10 | def pfactorsl(x: int) -> Iterator[int]:
11 | """Loop/Recursion factors. Limited to numbers with 1,000 factors.
12 |
13 | >>> list(pfactorsl(1560))
14 | [2, 2, 2, 3, 5, 13]
15 | >>> list(pfactorsl(2))
16 | [2]
17 | >>> list(pfactorsl(3))
18 | [3]
19 | """
20 | if x % 2 == 0:
21 | yield 2
22 | if x//2 > 1:
23 | #for f in pfactorsl(x//2): yield f
24 | yield from pfactorsl(x//2)
25 | return
26 | for i in range(3, int(math.sqrt(x)+.5)+1, 2):
27 | if x % i == 0:
28 | yield i
29 | if x//i > 1:
30 | #for f in pfactorsl(x//i): yield f
31 | yield from pfactorsl(x//i)
32 | return
33 | yield x
34 |
35 | def pfactorsr(x: int) -> Iterator[int]:
36 | """Pure Recursion factors. Limited to numbers below about 4,000,000
37 |
38 | >>> list(pfactorsr(1560))
39 | [2, 2, 2, 3, 5, 13]
40 | >>> list(pfactorsr(2))
41 | [2]
42 | >>> list(pfactorsr(3))
43 | [3]
44 | """
45 | def factor_n(x: int, n: int) -> Iterator[int]:
46 | if n*n > x:
47 | yield x
48 | return
49 | if x % n == 0:
50 | yield n
51 | if x//n > 1:
52 | #for f in factor_n( x // n, n ): yield f
53 | yield from factor_n(x // n, n)
54 | else:
55 | #for f in factor_n( x, n+2 ): yield f
56 | yield from factor_n(x, n+2)
57 | if x % 2 == 0:
58 | yield 2
59 | if x//2 > 1:
60 | #for f in pfactorsr( x//2 ): yield f
61 | yield from pfactorsr(x//2)
62 | return
63 | #for f in factor_n( x, 3 ): yield f
64 | yield from factor_n(x, 3)
65 |
66 | def divisorsr(n: int, a: int=1) -> Iterator[int]:
67 | """Recursive divisors of n
68 |
69 | >>> list(divisorsr( 26 ))
70 | [1, 2, 13]
71 | """
72 | if a == n:
73 | return
74 | if n % a == 0:
75 | yield a
76 | #for d in divisorsr( n, a+1 ): yield d
77 | yield from divisorsr(n, a+1)
78 |
79 | def divisorsi(n):
80 | """Imperative divisors of n
81 |
82 | >>> list(divisorsi( 26 ))
83 | [1, 2, 13]
84 | """
85 | return (a for a in range(1, n) if n%a == 0)
86 |
87 | def perfect(n):
88 | """Perfect numbers test
89 |
90 | >>> perfect( 6 )
91 | True
92 | >>> perfect( 28 )
93 | True
94 | >>> perfect( 26 )
95 | False
96 | >>> perfect( 496 )
97 | True
98 | """
99 | return sum(divisorsr(n, 1)) == n
100 |
101 | import itertools
102 | from typing import Iterable, Any
103 | def limits(iterable: Iterable[Any]) -> Any:
104 | """
105 | >>> limits([1, 2, 3, 4, 5])
106 | (5, 1)
107 | """
108 | max_tee, min_tee = itertools.tee(iterable, 2)
109 | return max(max_tee), min(min_tee)
110 |
111 | def test():
112 | import doctest
113 | doctest.testmod(verbose=1)
114 |
115 | if __name__ == "__main__":
116 | test()
117 |
--------------------------------------------------------------------------------
/Chapter03/ch03_ex1.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 3, Example Set 1
5 | """
6 | from typing import Callable
7 |
8 | class Mersenne1:
9 | """Callable object with a **Strategy** plug in required."""
10 | def __init__(self, algorithm: Callable[[int], int]) -> None:
11 | self.pow2 = algorithm
12 | def __call__(self, arg: int) -> int:
13 | return self.pow2(arg)-1
14 |
15 | def shifty(b: int) -> int:
16 | """2**b via shifting.
17 |
18 | >>> shifty(17)-1
19 | 131071
20 | """
21 | return 1 << b
22 |
23 | def multy(b: int) -> int:
24 | """2**b via naive recursion.
25 |
26 | >>> multy(17)-1
27 | 131071
28 | """
29 | if b == 0:
30 | return 1
31 | return 2*multy(b-1)
32 |
33 | def faster(b: int) -> int:
34 | """2**b via faster divide-and-conquer recursion.
35 |
36 | >>> faster(17)-1
37 | 131071
38 | """
39 | if b == 0:
40 | return 1
41 | if b%2 == 1:
42 | return 2*faster(b-1)
43 | t = faster(b//2)
44 | return t*t
45 |
46 | # Implementations of Mersenne with strategy objects plugged in properly.
47 |
48 | m1s = Mersenne1(shifty)
49 | m1m = Mersenne1(multy)
50 | m1f = Mersenne1(faster)
51 |
52 | # Alternative Mersenne using class-level configuration.
53 | # The syntax is awkward.
54 |
55 | class Mersenne2:
56 | pow2: Callable[[int], int] = None
57 | def __call__(self, arg: int) -> int:
58 | pow2 = self.__class__.__dict__['pow2']
59 | return pow2(arg)-1
60 |
61 | class ShiftyMersenne(Mersenne2):
62 | pow2 = shifty
63 |
64 | class MultyMersenee(Mersenne2):
65 | pow2 = multy
66 |
67 | class FasterMersenne(Mersenne2):
68 | pow2 = faster
69 |
70 | m2s = ShiftyMersenne()
71 | m2m = MultyMersenee()
72 | m2f = FasterMersenne()
73 |
74 | test_mersenne = """
75 | >>> m1s(17)
76 | 131071
77 | >>> m1m(17)
78 | 131071
79 | >>> m1f(17)
80 | 131071
81 | >>> m2s(17)
82 | 131071
83 | >>> m2m(17)
84 | 131071
85 | >>> m2f(17)
86 | 131071
87 | >>> m1s(89)
88 | 618970019642690137449562111
89 | >>> m1m(89)
90 | 618970019642690137449562111
91 | >>> m1f(89)
92 | 618970019642690137449562111
93 | """
94 |
95 | test_pure = """
96 | >>> def m(n):
97 | ... return 2**n-1
98 | >>> m(89)
99 | 618970019642690137449562111
100 | """
101 |
102 | __test__ = {
103 | 'test_mersenne': test_mersenne,
104 | 'test_pure': test_pure
105 | }
106 | def test():
107 | import doctest
108 | doctest.testmod(verbose=2)
109 |
110 | def performance():
111 | import timeit
112 | print(m1s.pow2.__name__,
113 | timeit.timeit(
114 | """m1s(17)""",
115 | """from Chapter_3.ch03_ex1 import m1s"""))
116 | print(m1m.pow2.__name__,
117 | timeit.timeit(
118 | """m1m(17)""",
119 | """from Chapter_3.ch03_ex1 import m1m"""))
120 | print(m1f.pow2.__name__,
121 | timeit.timeit(
122 | """m1f(17)""",
123 | """from Chapter_3.ch03_ex1 import m1f"""))
124 |
125 | if __name__ == "__main__":
126 | import sys
127 | print(sys.version)
128 | test()
129 | # import timeit
130 | # performance()
131 |
--------------------------------------------------------------------------------
/Chapter10/ch10_ex5.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 10, Example Set 5
5 | """
6 |
7 | from collections import defaultdict
8 | from typing import (
9 | Iterable, Callable, Dict, List, TypeVar,
10 | Iterator, Tuple, cast)
11 | D_ = TypeVar("D_")
12 | K_ = TypeVar("K_")
13 | def partition(
14 | source: Iterable[D_],
15 | key: Callable[[D_], K_] = lambda x: cast(K_, x)
16 | ) -> Iterable[Tuple[K_, Iterator[D_]]]:
17 | """Sort not required."""
18 | pd: Dict[K_, List[D_]] = defaultdict(list)
19 | for item in source:
20 | pd[key(item)].append(item)
21 | for k in sorted(pd):
22 | yield k, iter(pd[k])
23 |
24 | from itertools import groupby
25 |
26 | def partition_s(
27 | source: Iterable[D_],
28 | key: Callable[[D_], K_] = lambda x: cast(K_, x)
29 | ) -> Iterable[Tuple[K_, Iterator[D_]]]:
30 | """Sort required"""
31 | return groupby(sorted(source, key=key), key)
32 |
33 |
34 | test_data = """
35 | >>> data = [('4', 6.1), ('1', 4.0), ('2', 8.3), ('2', 6.5),
36 | ... ('1', 4.6), ('2', 6.8), ('3', 9.3), ('2', 7.8),
37 | ... ('2', 9.2), ('4', 5.6), ('3', 10.5), ('1', 5.8),
38 | ... ('4', 3.8), ('3', 8.1), ('3', 8.0), ('1', 6.9),
39 | ... ('3', 6.9), ('4', 6.2), ('1', 5.4), ('4', 5.8)]
40 |
41 | >>> for key, group_iter in partition(data, key=lambda x:x[0]):
42 | ... print(key, tuple(group_iter))
43 | 1 (('1', 4.0), ('1', 4.6), ('1', 5.8), ('1', 6.9), ('1', 5.4))
44 | 2 (('2', 8.3), ('2', 6.5), ('2', 6.8), ('2', 7.8), ('2', 9.2))
45 | 3 (('3', 9.3), ('3', 10.5), ('3', 8.1), ('3', 8.0), ('3', 6.9))
46 | 4 (('4', 6.1), ('4', 5.6), ('4', 3.8), ('4', 6.2), ('4', 5.8))
47 | >>> for key, group_iter in partition_s(data, key=lambda x:x[0]):
48 | ... print(key, tuple(group_iter))
49 | 1 (('1', 4.0), ('1', 4.6), ('1', 5.8), ('1', 6.9), ('1', 5.4))
50 | 2 (('2', 8.3), ('2', 6.5), ('2', 6.8), ('2', 7.8), ('2', 9.2))
51 | 3 (('3', 9.3), ('3', 10.5), ('3', 8.1), ('3', 8.0), ('3', 6.9))
52 | 4 (('4', 6.1), ('4', 5.6), ('4', 3.8), ('4', 6.2), ('4', 5.8))
53 |
54 | """
55 |
56 | mean = lambda seq: sum(seq)/len(seq)
57 | var = lambda mean, seq: sum((x-mean)**2/mean for x in seq)
58 |
59 | Item = Tuple[K_, float]
60 | def summarize(
61 | key_iter: Tuple[K_, Iterable[Item]]
62 | ) -> Tuple[K_, float, float]:
63 | key, item_iter = key_iter
64 | values = tuple(v for k, v in item_iter)
65 | m = mean(values)
66 | return key, m, var(m, values)
67 |
68 | test_summarize = """
69 | >>> data = [('4', 6.1), ('1', 4.0), ('2', 8.3), ('2', 6.5), ('1', 4.6),
70 | ... ('2', 6.8), ('3', 9.3), ('2', 7.8), ('2', 9.2), ('4', 5.6),
71 | ... ('3', 10.5), ('1', 5.8), ('4', 3.8), ('3', 8.1), ('3', 8.0),
72 | ... ('1', 6.9), ('3', 6.9), ('4', 6.2), ('1', 5.4), ('4', 5.8)]
73 |
74 | >>> partition1= partition( data, key=lambda x:x[0] )
75 | >>> groups1= map( summarize, partition1 )
76 | >>> for g, s, s2 in groups1:
77 | ... print( g, round(s,2), round(s2,2) )
78 | 1 5.34 0.93
79 | 2 7.72 0.63
80 | 3 8.56 0.89
81 | 4 5.5 0.7
82 |
83 | >>> partition2= partition_s( data, key=lambda x:x[0] )
84 | >>> groups2= map( summarize, partition2 )
85 | >>> for g, s, s2 in groups2:
86 | ... print( g, round(s,2), round(s2,2) )
87 | 1 5.34 0.93
88 | 2 7.72 0.63
89 | 3 8.56 0.89
90 | 4 5.5 0.7
91 |
92 |
93 | """
94 |
95 | __test__ = {
96 | "test_data": test_data,
97 | "test_summarize": test_summarize,
98 | }
99 |
100 | def test():
101 | import doctest
102 | doctest.testmod(verbose=1)
103 |
104 | if __name__ == "__main__":
105 | test()
106 | #performance()
107 |
--------------------------------------------------------------------------------
/Chapter04/ch04_ex4.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 4, Example Set 4
5 |
6 | Definitions of mean, stddev, Pearson correlation
7 | and linear estimation.
8 |
9 | http://en.wikipedia.org/wiki/Mean
10 |
11 | http://en.wikipedia.org/wiki/Standard_deviation
12 |
13 | http://en.wikipedia.org/wiki/Standard_score
14 |
15 | http://en.wikipedia.org/wiki/Normalization_(statistics)
16 |
17 | http://en.wikipedia.org/wiki/Simple_linear_regression
18 | """
19 | from math import sqrt
20 | from collections import Sequence
21 |
22 | def s0(samples: Sequence) -> float:
23 | return sum(1 for x in samples) # sum(x**0 for x in samples)
24 |
25 | def s1(samples: Sequence) -> float:
26 | return sum(samples) # sum(x**1 for x in samples)
27 |
28 | def s2(samples: Sequence) -> float:
29 | return sum(x**2 for x in samples)
30 |
31 | def mean(samples: Sequence) -> float:
32 | """Arithmetic mean.
33 |
34 | >>> d = [4, 36, 45, 50, 75]
35 | >>> mean(d)
36 | 42.0
37 | """
38 | # return sum(samples)/len(samples)
39 | return s1(samples)/s0(samples)
40 |
41 | def stdev(samples: Sequence) -> float:
42 | """Standard deviation.
43 |
44 | >>> d = [ 2, 4, 4, 4, 5, 5, 7, 9 ]
45 | >>> mean(d)
46 | 5.0
47 | >>> stdev(d)
48 | 2.0
49 | """
50 | N = s0(samples) # len(samples)
51 | return sqrt((s2(samples)/N)-(s1(samples)/N)**2)
52 |
53 | #def z(x, μ_x, σ_x):
54 | def z(x: float, m_x: float, s_x: float) -> float:
55 | """
56 | Compute a normalized score.
57 |
58 | >>> d = [ 2, 4, 4, 4, 5, 5, 7, 9 ]
59 | >>> list( z( x, mean(d), stdev(d) ) for x in d )
60 | [-1.5, -0.5, -0.5, -0.5, 0.0, 0.0, 1.0, 2.0]
61 |
62 | The above example recomputed mean and standard deviation.
63 | Not a best practice.
64 | """
65 | return (x-m_x)/s_x
66 |
67 | def corr(samples1: Sequence, samples2: Sequence) -> float:
68 | """Pearson product-moment correlation.
69 |
70 | >>> xi= [1.47, 1.50, 1.52, 1.55, 1.57, 1.60, 1.63, 1.65,
71 | ... 1.68, 1.70, 1.73, 1.75, 1.78, 1.80, 1.83,] # Height (m)
72 | >>> yi= [52.21, 53.12, 54.48, 55.84, 57.20, 58.57, 59.93, 61.29,
73 | ... 63.11, 64.47, 66.28, 68.10, 69.92, 72.19, 74.46,] # Mass (kg)
74 | >>> round( corr( xi, yi ), 5 )
75 | 0.99458
76 | """
77 | m_1, s_1 = mean(samples1), stdev(samples1)
78 | m_2, s_2 = mean(samples2), stdev(samples2)
79 | z_1 = (z(x, m_1, s_1) for x in samples1)
80 | z_2 = (z(x, m_2, s_2) for x in samples2)
81 | r = sum(zx1*zx2 for zx1, zx2 in zip(z_1, z_2))/len(samples1)
82 | return r
83 |
84 | from typing import Tuple
85 | def linest(x_list: Sequence, y_list: Sequence) -> Tuple[float, float]:
86 | """Linear Least-Squares Estimation.
87 |
88 | >>> xi= [1.47, 1.50, 1.52, 1.55, 1.57, 1.60, 1.63, 1.65,
89 | ... 1.68, 1.70, 1.73, 1.75, 1.78, 1.80, 1.83,] # Height (m)
90 | >>> yi= [52.21, 53.12, 54.48, 55.84, 57.20, 58.57, 59.93, 61.29,
91 | ... 63.11, 64.47, 66.28, 68.10, 69.92, 72.19, 74.46,] # Mass (kg)
92 | >>> assert len(xi) == len(yi)
93 | >>> alpha, beta = linest(xi, yi)
94 | >>> round(alpha,3)
95 | -39.062
96 | >>> round(beta,3)
97 | 61.272
98 | """
99 | r_xy = corr(x_list, y_list)
100 | m_x, s_x = mean(x_list), stdev(x_list)
101 | m_y, s_y = mean(y_list), stdev(y_list)
102 | beta = r_xy * s_y/s_x
103 | alpha = m_y - beta*m_x
104 | return alpha, beta
105 |
106 | def test():
107 | import doctest
108 | doctest.testmod(verbose=1)
109 |
110 | if __name__ == "__main__":
111 | import sys
112 | print(sys.version)
113 | test()
114 |
--------------------------------------------------------------------------------
/crayola.gpl:
--------------------------------------------------------------------------------
1 | GIMP Palette
2 | Name: Crayola
3 | Columns: 16
4 | #
5 | 239 222 205 Almond
6 | 205 149 117 Antique Brass
7 | 253 217 181 Apricot
8 | 120 219 226 Aquamarine
9 | 135 169 107 Asparagus
10 | 255 164 116 Atomic Tangerine
11 | 250 231 181 Banana Mania
12 | 159 129 112 Beaver
13 | 253 124 110 Bittersweet
14 | 0 0 0 Black
15 | 172 229 238 Blizzard Blue
16 | 31 117 254 Blue
17 | 162 162 208 Blue Bell
18 | 102 153 204 Blue Gray
19 | 13 152 186 Blue Green
20 | 115 102 189 Blue Violet
21 | 222 93 131 Blush
22 | 203 65 84 Brick Red
23 | 180 103 77 Brown
24 | 255 127 73 Burnt Orange
25 | 234 126 93 Burnt Sienna
26 | 176 183 198 Cadet Blue
27 | 255 255 153 Canary
28 | 28 211 162 Caribbean Green
29 | 255 170 204 Carnation Pink
30 | 221 68 146 Cerise
31 | 29 172 214 Cerulean
32 | 188 93 88 Chestnut
33 | 221 148 117 Copper
34 | 154 206 235 Cornflower
35 | 255 188 217 Cotton Candy
36 | 253 219 109 Dandelion
37 | 43 108 196 Denim
38 | 239 205 184 Desert Sand
39 | 110 81 96 Eggplant
40 | 206 255 29 Electric Lime
41 | 113 188 120 Fern
42 | 109 174 129 Forest Green
43 | 195 100 197 Fuchsia
44 | 204 102 102 Fuzzy Wuzzy
45 | 231 198 151 Gold
46 | 252 217 117 Goldenrod
47 | 168 228 160 Granny Smith Apple
48 | 149 145 140 Gray
49 | 28 172 120 Green
50 | 17 100 180 Green Blue
51 | 240 232 145 Green Yellow
52 | 255 29 206 Hot Magenta
53 | 178 236 93 Inchworm
54 | 93 118 203 Indigo
55 | 202 55 103 Jazzberry Jam
56 | 59 176 143 Jungle Green
57 | 254 254 34 Laser Lemon
58 | 252 180 213 Lavender
59 | 255 244 79 Lemon Yellow
60 | 255 189 136 Macaroni and Cheese
61 | 246 100 175 Magenta
62 | 170 240 209 Magic Mint
63 | 205 74 76 Mahogany
64 | 237 209 156 Maize
65 | 151 154 170 Manatee
66 | 255 130 67 Mango Tango
67 | 200 56 90 Maroon
68 | 239 152 170 Mauvelous
69 | 253 188 180 Melon
70 | 26 72 118 Midnight Blue
71 | 48 186 143 Mountain Meadow
72 | 197 75 140 Mulberry
73 | 25 116 210 Navy Blue
74 | 255 163 67 Neon Carrot
75 | 186 184 108 Olive Green
76 | 255 117 56 Orange
77 | 255 43 43 Orange Red
78 | 248 213 104 Orange Yellow
79 | 230 168 215 Orchid
80 | 65 74 76 Outer Space
81 | 255 110 74 Outrageous Orange
82 | 28 169 201 Pacific Blue
83 | 255 207 171 Peach
84 | 197 208 230 Periwinkle
85 | 253 221 230 Piggy Pink
86 | 21 128 120 Pine Green
87 | 252 116 253 Pink Flamingo
88 | 247 143 167 Pink Sherbert
89 | 142 69 133 Plum
90 | 116 66 200 Purple Heart
91 | 157 129 186 Purple Mountain's Majesty
92 | 254 78 218 Purple Pizzazz
93 | 255 73 108 Radical Red
94 | 214 138 89 Raw Sienna
95 | 113 75 35 Raw Umber
96 | 255 72 208 Razzle Dazzle Rose
97 | 227 37 107 Razzmatazz
98 | 238 32 77 Red
99 | 255 83 73 Red Orange
100 | 192 68 143 Red Violet
101 | 31 206 203 Robin's Egg Blue
102 | 120 81 169 Royal Purple
103 | 255 155 170 Salmon
104 | 252 40 71 Scarlet
105 | 118 255 122 Screamin' Green
106 | 159 226 191 Sea Green
107 | 165 105 79 Sepia
108 | 138 121 93 Shadow
109 | 69 206 162 Shamrock
110 | 251 126 253 Shocking Pink
111 | 205 197 194 Silver
112 | 128 218 235 Sky Blue
113 | 236 234 190 Spring Green
114 | 255 207 72 Sunglow
115 | 253 94 83 Sunset Orange
116 | 250 167 108 Tan
117 | 24 167 181 Teal Blue
118 | 235 199 223 Thistle
119 | 252 137 172 Tickle Me Pink
120 | 219 215 210 Timberwolf
121 | 23 128 109 Tropical Rain Forest
122 | 222 170 136 Tumbleweed
123 | 119 221 231 Turquoise Blue
124 | 255 255 102 Unmellow Yellow
125 | 146 110 174 Violet Purple
126 | 50 74 178 Violet Blue
127 | 247 83 148 Violet Red
128 | 255 160 137 Vivid Tangerine
129 | 143 80 157 Vivid Violet
130 | 255 255 255 White
131 | 162 173 208 Wild Blue Yonder
132 | 255 67 164 Wild Strawberry
133 | 252 108 133 Wild Watermelon
134 | 205 164 222 Wisteria
135 | 252 232 131 Yellow
136 | 197 227 132 Yellow Green
137 | 255 174 66 Yellow Orange
138 |
--------------------------------------------------------------------------------
/test_all.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Run all the chapter modules, doctests or performance() function
3 |
4 | This is run from the top-level directory, where all of the sample
5 | data files are also located.
6 |
7 | When runnning individual examples, working directory is expected
8 | to be this top-level directory.
9 | """
10 | import doctest
11 | import os
12 | import glob
13 | import runpy
14 | import unittest
15 | import sys
16 | import time
17 | import importlib
18 |
19 | DEBUG = False # Can't easily use logging -- conflict with Chapter_11
20 |
21 | def package_module_iter(*packages):
22 | """For a given list of packages, emit the package name and a generator
23 | for all modules in the package. Structured like ``itertools.groupby()``.
24 | """
25 | def module_iter(package, module_iter):
26 | if DEBUG:
27 | print("Package {0}".format(package))
28 | for filename in module_iter:
29 | basename = os.path.split(filename)[-1]
30 | module, ext = os.path.splitext(basename)
31 | if (
32 | module.startswith("__")
33 | and module.endswith("__")
34 | and ext == ".py"):
35 | continue
36 | if DEBUG:
37 | print(" file {0} module {1}".format(basename, module))
38 | yield basename, module
39 |
40 | for package in packages:
41 | yield (package,
42 | module_iter(package, glob.glob(os.path.join(package,"*.py"))))
43 |
44 | def run(pkg_mod_iter):
45 | """Run each module."""
46 | for package, module_iter in pkg_mod_iter:
47 | print(package)
48 | print("="*len(package))
49 | print()
50 | for filename, module in module_iter:
51 | runpy.run_path(package+"/"+filename, run_name="__main__")
52 |
53 | def run_test_suite(pkg_mod_iter):
54 | """Doctest each module individually.
55 |
56 | Might be simpler to use subprcoess.run(['python3', '-m', 'doctest', module])
57 | """
58 | for package, module_iter in pkg_mod_iter:
59 | print(package)
60 | print("="*len(package))
61 | print()
62 | for filename, module in module_iter:
63 | suite = doctest.DocTestSuite(package+"."+module)
64 | runner = unittest.TextTestRunner(verbosity=1)
65 | runner.run(suite)
66 |
67 | def run_performance(pkg_mod_iter):
68 | """Locate a performance() function in each module and run it."""
69 | for package, module_iter in pkg_mod_iter:
70 | print(package)
71 | print("="*len(package))
72 | print()
73 | for filename, modname in module_iter:
74 | print(filename, modname)
75 | try:
76 | module = __import__(
77 | package+"."+modname, fromlist=(modname, "performance"))
78 | module.performance()
79 | except AttributeError:
80 | pass # no performance() function in the module.
81 |
82 | def master_test_suite(pkg_mod_iter):
83 | """Build a master test suite from all modules and run that."""
84 | master_suite = unittest.TestSuite()
85 | for package, module_iter in pkg_mod_iter:
86 | for filename, module in module_iter:
87 | print(package+"."+module, file=sys.stderr)
88 | suite = doctest.DocTestSuite(package+"."+module)
89 | print(" ", suite, file=sys.stderr)
90 | master_suite.addTests(suite)
91 | runner = unittest.TextTestRunner(verbosity=1)
92 | runner.run(master_suite)
93 |
94 | def chap_key(name: str) -> int:
95 | _, _, n = name.partition("_")
96 | return int(n)
97 |
98 | if __name__ == "__main__":
99 | content = sorted(glob.glob("Chapter_*"), key=chap_key)
100 | if DEBUG:
101 | print(content, file=sys.stderr)
102 | master_test_suite(package_module_iter(*content))
103 |
--------------------------------------------------------------------------------
/Chapter07/ch07_ex1.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 7, Example Set 1
5 | """
6 | # pylint: disable=wrong-import-position,wrong-import-order,too-few-public-methods
7 |
8 | from Chapter06.ch06_ex3 import row_iter_kml
9 | from Chapter04.ch04_ex1 import legs, haversine
10 |
11 | import urllib.request
12 |
13 | first_leg = ((29.050501, -80.651169), (27.186001, -80.139503), 115.1751)
14 |
15 | selectors = '''
16 | >>> from typing import Tuple, Callable
17 | >>> Point = Tuple[float, float]
18 | >>> Leg = Tuple[Point, Point, float]
19 | >>> start: Callable[[Leg], Point] = lambda leg: leg[0]
20 | >>> end: Callable[[Leg], Point] = lambda leg: leg[1]
21 | >>> distance = lambda leg: leg[2]
22 | >>> latitude = lambda pt: pt[0]
23 | >>> longitude = lambda pt: pt[1]
24 |
25 | >>> first_leg = ((29.050501, -80.651169), (27.186001, -80.139503), 115.1751)
26 | >>> latitude(start(first_leg))
27 | 29.050501
28 |
29 | >>> start: Callable[[Leg], Point] = lambda leg: leg[0]
30 | >>> latitude(start(first_leg))
31 | 29.050501
32 |
33 | '''
34 |
35 | selectors_args = """
36 | >>> from typing import Tuple, Callable
37 | >>> Point = Tuple[float, float]
38 | >>> Leg = Tuple[Point, Point, float]
39 | >>> start: Callable[[Point, Point, float], Point] = lambda start, end, distance: start
40 | >>> end: Callable[[Point, Point, float], Point] = lambda start, end, distance: end
41 | >>> distance = lambda start, end, distance: distance
42 | >>> latitude = lambda lat, lon: lat
43 | >>> longitude = lambda lat, lon: lon
44 |
45 | >>> first_leg = ((29.050501, -80.651169), (27.186001, -80.139503), 115.1751)
46 | >>> latitude(*start(*first_leg))
47 | 29.050501
48 | """
49 |
50 | # from collections import namedtuple
51 | # Leg = namedtuple("Leg", ("start", "end", "distance"))
52 | # Point = namedtuple("Point", ("latitude", "longitude"))
53 |
54 | from typing import NamedTuple
55 |
56 | class Point(NamedTuple):
57 | latitude: float
58 | longitude: float
59 |
60 | class Leg(NamedTuple):
61 | start: Point
62 | end: Point
63 | distance: float
64 |
65 | first_leg = Leg(
66 | Point(29.050501, -80.651169),
67 | Point(27.186001, -80.139503),
68 | 115.1751
69 | )
70 |
71 | named_tuples = """
72 | >>> first_leg = Leg(
73 | ... Point(29.050501, -80.651169),
74 | ... Point(27.186001, -80.139503),
75 | ... 115.1751)
76 | >>> first_leg.start.latitude
77 | 29.050501
78 | """
79 |
80 | from typing import Tuple, Iterator, List
81 |
82 | # pylint: disable=unused-argument
83 | def pick_lat_lon(lon: str, lat: str, alt: str) -> Tuple[str, str]:
84 | return lat, lon
85 |
86 | def float_lat_lon(
87 | row_iter: Iterator[List[str]]
88 | ) -> Iterator[Point]:
89 | return (
90 | Point(*map(float, pick_lat_lon(*row)))
91 | for row in row_iter
92 | )
93 |
94 | import codecs
95 | from typing import cast, TextIO, BinaryIO
96 | source = "file:./Winter%202012-2013.kml"
97 | def get_trip(url: str = source) -> List[Leg]:
98 | with urllib.request.urlopen(url) as source:
99 | path_iter = float_lat_lon(row_iter_kml(
100 | # cast(TextIO, source)
101 | cast(
102 | TextIO,
103 | codecs.getreader('utf-8')(cast(BinaryIO, source))
104 | )
105 | ))
106 | pair_iter = legs(path_iter)
107 | trip_iter = (
108 | Leg(start, end, round(haversine(start, end), 4))
109 | for start, end in pair_iter
110 | )
111 | trip = list(trip_iter)
112 | return trip
113 |
114 | find_given_leg_demo = """
115 | >>> trip= get_trip()
116 | >>> leg= next(filter(lambda leg: int(leg.distance)==115, trip))
117 | >>> leg.start.latitude
118 | 29.050501
119 | """
120 |
121 | __test__ = {
122 | 'selectors': selectors,
123 | 'selectors_args': selectors_args,
124 | 'named_tuples': named_tuples,
125 | 'find_given_leg_demo': find_given_leg_demo,
126 | }
127 |
128 | def test():
129 | import doctest
130 | doctest.testmod(verbose=1)
131 |
132 | if __name__ == "__main__":
133 | test()
134 |
--------------------------------------------------------------------------------
/Chapter10/ch10_ex4.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 10, Example Set 4
5 | """
6 | # pylint: disable=wrong-import-position
7 |
8 | from functools import reduce, partial
9 |
10 | display = lambda data: reduce(lambda x, y: print(x, y), data)
11 | sum2 = lambda data: reduce(lambda x, y: x+y**2, data, 0)
12 | sum = lambda data: reduce(lambda x, y: x+y, data, 0)
13 | count = lambda data: reduce(lambda x, y: x+1, data, 0)
14 | min = lambda data: reduce(lambda x, y: x if x < y else y, data)
15 | max = lambda data: reduce(lambda x, y: x if x > y else y, data)
16 |
17 | test_reductions = """
18 | >>> import math
19 | >>> d = [ 2, 4, 4, 4, 5, 5, 7, 9 ]
20 | >>> sum2(d)
21 | 232
22 | >>> sum(d)
23 | 40
24 | >>> count(d)
25 | 8
26 | >>> sum(d)/count(d)
27 | 5.0
28 | >>> math.sqrt((sum2(d)/count(d))-(sum(d)/count(d))**2)
29 | 2.0
30 | >>> max(d)
31 | 9
32 | >>> min(d)
33 | 2
34 | """
35 |
36 | from typing import Callable, Iterable, TypeVar
37 | T_ = TypeVar("T_")
38 | def map_reduce(
39 | map_fun: Callable[[T_], T_],
40 | reduce_fun: Callable[[T_, T_], T_],
41 | source: Iterable[T_]) -> T_:
42 | """
43 | This example doesn't *actually* fit the type definitions!
44 |
45 | This involves a mapping transformation from T1_ to T2_,
46 | Then the reduce works with T2_.
47 |
48 | Either we have to provide super-complex type definitions,
49 | or resort to using ``Any``.
50 |
51 | >>> from operator import add
52 | >>> data = ["2", "3", "5", "7"]
53 | >>> map_reduce(int, add, data)
54 | 17
55 | """
56 | return reduce(reduce_fun, map(map_fun, source))
57 |
58 | def sum2_mr(source: Iterable[float]) -> float:
59 | """
60 | >>> d = [2, 4, 4, 4, 5, 5, 7, 9]
61 | >>> sum2_mr(d)
62 | 232
63 | """
64 | return map_reduce(lambda y: y**2, lambda x, y: x+y, source)
65 |
66 | import operator
67 | def sum2_mr2(source: Iterable[float]) -> float:
68 | """
69 | >>> d = [2, 4, 4, 4, 5, 5, 7, 9]
70 | >>> sum2_mr2(d)
71 | 232
72 | """
73 | return map_reduce(lambda y: y**2, operator.add, source)
74 |
75 | def count_mr(source: Iterable[float]) -> float:
76 | """
77 | >>> d = [2, 4, 4, 4, 5, 5, 7, 9]
78 | >>> count_mr(d)
79 | 8
80 | """
81 | return map_reduce(lambda y: 1, lambda x, y: x+y, source)
82 |
83 | def comma_fix(data: str) -> float:
84 | try:
85 | return float(data)
86 | except ValueError:
87 | return float(data.replace(",", ""))
88 |
89 | def clean_sum(
90 | cleaner: Callable[[str], float],
91 | data: Iterable[str]
92 | ) -> float:
93 | """
94 | >>> d = ('1,196', '1,176', '1,269', '1,240', '1,307',
95 | ... '1,435', '1,601', '1,654', '1,803', '1,734')
96 | >>> clean_sum(comma_fix, d)
97 | 14415.0
98 | """
99 | return reduce(operator.add, map(cleaner, data))
100 |
101 | sum_p = partial(reduce, operator.add)
102 |
103 | test_sump = """
104 | >>> iterable = [2, 4, 4, 4, 5, 5, 7, 9]
105 | >>> sum_p(iterable)
106 | 40
107 | >>> sum_p(map(lambda x:x**2, iterable))
108 | 232
109 | >>> reduce(lambda x, y: x+y**2, iterable, 0)
110 | 232
111 | >>> reduce(lambda x, y: x+y**2, iterable)
112 | 230
113 | """
114 |
115 | def performance():
116 | import timeit
117 | import sys
118 | for source_len in range(100, 1000, 100):
119 | data = repr(['x']*source_len)
120 | op_r = f'reduce( operator.add, {data}, "" )'
121 | op_j = f'"".join({data})'
122 | r = timeit.timeit(
123 | op_r,
124 | """from functools import reduce; import operator""")
125 | j = timeit.timeit(
126 | op_j)
127 | print('reduce', source_len, r)
128 | print('join', source_len, j)
129 | sys.stdout.flush()
130 |
131 | __test__ = {
132 | "test_reductions": test_reductions,
133 | "test_sump": test_sump,
134 | }
135 |
136 | def test():
137 | import doctest
138 | doctest.testmod(verbose=1)
139 |
140 | if __name__ == "__main__":
141 | test()
142 | # performance()
143 |
--------------------------------------------------------------------------------
/Chapter14/ch14_ex1.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 14, Example Set 1
5 | """
6 | from pymonad import curry, Just, Nothing, List
7 |
8 | @curry
9 | def systolic_bp(bmi, age, gender_male, treatment):
10 | """
11 | Example of multiple regression model.
12 |
13 | See http://sphweb.bumc.bu.edu/otlt/MPH-Modules/BS/BS704_Multivariable/BS704_Multivariable7.html
14 | """
15 | return (
16 | 68.15+0.58*bmi+0.65*age+0.94*gender_male+6.44*treatment
17 | )
18 |
19 | tests_curry_1 = """
20 | >>> systolic_bp( 25, 50, 1, 0 )
21 | 116.09
22 | >>> systolic_bp( 25, 50, 0, 1 )
23 | 121.59
24 | >>> treated = systolic_bp( 25, 50, 0 )
25 | >>> treated(0)
26 | 115.15
27 | >>> treated(1)
28 | 121.59
29 | >>> g_t= systolic_bp( 25, 50 )
30 | >>> g_t(1, 0)
31 | 116.09
32 | >>> g_t(0, 1)
33 | 121.59
34 | """
35 |
36 | from collections.abc import Sequence
37 |
38 | @curry
39 | def myreduce(function, iterable_or_sequence):
40 | if isinstance(iterable_or_sequence, Sequence):
41 | iterator = iter(iterable_or_sequence)
42 | else:
43 | iterator = iterable_or_sequence
44 | s = next(iterator)
45 | for v in iterator:
46 | s = function(s, v)
47 | return s
48 |
49 | test_curry_2 = """
50 | >>> from operator import *
51 | >>> sum= myreduce( add )
52 | >>> sum( [1,2,3] )
53 | 6
54 | >>> max= myreduce( lambda x,y: x if x > y else y )
55 | >>> max( [2,5,3] )
56 | 5
57 | """
58 |
59 | def f(x, *args):
60 | def f1(y, *args):
61 | def f2(z):
62 | return (x+y)*z
63 | if args:
64 | return f2(*args)
65 | return f2
66 | if args:
67 | return f1(*args)
68 | return f1
69 |
70 | test_manual_curry = """
71 | >>> f(2)(3)(5)
72 | 25
73 | >>> f(3,5,7)
74 | 56
75 | >>> f(5,7)(9)
76 | 108
77 | """
78 |
79 | import operator
80 |
81 | prod = myreduce(operator.mul)
82 |
83 | @curry
84 | def alt_range(n):
85 | if n == 0:
86 | return range(1, 2) # Only 1
87 | elif n % 2 == 0:
88 | return range(2, n+1, 2)
89 | else:
90 | return range(1, n+1, 2)
91 |
92 | @curry
93 | def range1n(n):
94 | if n == 0:
95 | return range(1, 2) # Only 1
96 | return range(1, n+1)
97 |
98 | test_composition = """
99 | >>> semi_fact= prod * alt_range
100 | >>> semi_fact(9)
101 | 945
102 | >>> semi_fact(1)
103 | 1
104 | >>> semi_fact(2)
105 | 2
106 | >>> semi_fact(3)
107 | 3
108 | >>> semi_fact(4)
109 | 8
110 | >>> semi_fact(5)
111 | 15
112 | >>> fact= prod * range1n
113 | >>> fact(1)
114 | 1
115 | >>> fact(2)
116 | 2
117 | >>> fact(3)
118 | 6
119 | >>> semi_fact(0)
120 | 1
121 | >>> fact(1)
122 | 1
123 | """
124 |
125 | test_functor = """
126 | >>> x1= systolic_bp * Just(25) & Just(50) & Just(1) & Just(0)
127 | >>> x1.getValue()
128 | 116.09
129 | >>> x2= systolic_bp * Just(25) & Just(50) & Just(1) & Nothing
130 | >>> x2.getValue() is None
131 | True
132 |
133 | >>> pi = lambda: 3.14
134 | >>> pi()
135 | 3.14
136 | """
137 |
138 | @curry
139 | def n21(n):
140 | """
141 | >>> n21(0)
142 | 1
143 | >>> n21(1)
144 | 2
145 | """
146 | return 2*n+1
147 |
148 | test_functor2 = """
149 | >>> fact= prod * range1n
150 | >>> seq1 = List(*range(20))
151 | >>> f1=fact * seq1
152 | >>> f1[:10]
153 | [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880]
154 |
155 | >>> semi_fact= prod * alt_range
156 | >>> f2=semi_fact * n21 * seq1
157 | >>> f2[:10]
158 | [1, 3, 15, 105, 945, 10395, 135135, 2027025, 34459425, 654729075]
159 |
160 | >>> 2*sum(map( operator.truediv, f1, f2 ))
161 | 3.1415919276751456
162 | """
163 |
164 | test_bind = """
165 | >>> fact= prod * range1n
166 | >>> r= Just(3) >> Just * fact
167 | >>> r.getValue()
168 | 6
169 | """
170 |
171 | __test__ = {
172 | "tests_curry_1": tests_curry_1,
173 | "test_curry_2": test_curry_2,
174 | "test_manual_curry": test_manual_curry,
175 | "test_composition": test_composition,
176 | "test_functor": test_functor,
177 | "test_functor2": test_functor2,
178 | "test_bind": test_bind,
179 | }
180 |
181 | def test():
182 | import doctest
183 | doctest.testmod(verbose=1)
184 |
185 | if __name__ == "__main__":
186 | test()
187 |
--------------------------------------------------------------------------------
/Chapter06/ch06_ex1.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 6, Example Set 1
5 | """
6 | # pylint: disable=reimported,wrong-import-position
7 |
8 | def add(a: int, b: int) -> int:
9 | """Recursive addition.
10 |
11 | >>> add( 3, 5 )
12 | 8
13 | """
14 | if a == 0:
15 | return b
16 | elif b == 0:
17 | return a
18 | else: return add(a-1, b+1)
19 |
20 | def fact(n: int) -> int:
21 | """Recursive Factorial
22 |
23 | >>> fact(0)
24 | 1
25 | >>> fact(1)
26 | 1
27 | >>> fact(7)
28 | 5040
29 | """
30 | if n == 0:
31 | return 1
32 | else:
33 | return n*fact(n-1)
34 |
35 | def facti(n: int) -> int:
36 | """Imperative Factorial
37 |
38 | >>> fact(0)
39 | 1
40 | >>> fact(1)
41 | 1
42 | >>> fact(7)
43 | 5040
44 | """
45 | if n == 0:
46 | return 1
47 | f = 1
48 | for i in range(2, n):
49 | f = f*i
50 | return f
51 |
52 | def fastexp(a: float, n: int) -> float:
53 | """Recursive exponentiation by squaring
54 |
55 | >>> fastexp( 3, 11 )
56 | 177147
57 | """
58 | if n == 0:
59 | return 1
60 | elif n % 2 == 1:
61 | return a*fastexp(a, n-1)
62 | else:
63 | t = fastexp(a, n//2)
64 | return t*t
65 |
66 | def fib(n: int) -> int:
67 | """Fibonacci numbers with naive recursion
68 |
69 | >>> fib(20)
70 | 6765
71 | >>> fib(1)
72 | 1
73 | """
74 | if n == 0:
75 | return 0
76 | if n == 1:
77 | return 1
78 | return fib(n-1) + fib(n-2)
79 |
80 | def fibi(n: int) -> int:
81 | """Fibonacci numbers saving just two previous values
82 |
83 | >>> fibi(20)
84 | 6765
85 | >>> fibi(1)
86 | 1
87 | >>> fibi(2)
88 | 1
89 | >>> fibi(3)
90 | 2
91 | """
92 | if n == 0:
93 | return 0
94 | if n == 1:
95 | return 1
96 | f_n2, f_n1 = 1, 1
97 | for _ in range(3, n+1):
98 | f_n2, f_n1 = f_n1, f_n2+f_n1
99 | return f_n1
100 |
101 | def fibi2(n: int) -> int:
102 | """Fibonacci numbers with iteration and memoization
103 |
104 | >>> fibi2(20)
105 | 6765
106 | >>> fibi2(1)
107 | 1
108 | """
109 | f = [0, 1] + [None for _ in range(2, n+1)]
110 | for i in range(2, n+1):
111 | f[i] = f[i-1]+f[i-2]
112 | return f[n]
113 |
114 | from typing import Callable, Sequence, Any, List
115 | def mapr(
116 | f: Callable[[Any], Any],
117 | collection: Sequence[Any]) -> List[Any]:
118 | """Recursive definition of map-like function.
119 |
120 | >>> mapr( lambda x:2**x, [0, 1, 2, 3, 4] )
121 | [1, 2, 4, 8, 16]
122 | """
123 | if len(collection) == 0:
124 | return []
125 | return mapr(f, collection[:-1]) + [f(collection[-1])]
126 |
127 | from typing import Callable, Iterable, Iterator, Any, TypeVar
128 | D_ = TypeVar("D_")
129 | R_ = TypeVar("R_")
130 | def mapf(f: Callable[[D_], R_], C: Iterable[D_]) -> Iterator[R_]:
131 | """Higher-Order definition of map.
132 |
133 | >>> list( mapf( lambda x:2**x, [0, 1, 2, 3, 4] ) )
134 | [1, 2, 4, 8, 16]
135 | """
136 | return (f(x) for x in C)
137 |
138 | def mapg(f: Callable[[D_], R_], C: Iterable[D_]) -> Iterator[R_]:
139 | """Generator definition of map
140 |
141 | >>> list( mapg( lambda x:2**x, [0, 1, 2, 3, 4] ) )
142 | [1, 2, 4, 8, 16]
143 | """
144 | for x in C:
145 | yield f(x)
146 |
147 | def prodi(items: Iterable[float]) -> float:
148 | """Imperative product
149 |
150 | >>> prodi( [1,2,3,4,5,6,7] )
151 | 5040
152 | """
153 | p: float = 1
154 | for n in items:
155 | p *= n
156 | return p
157 |
158 | def prodrc(collection: Sequence[float]) -> float:
159 | """Recursive product with a collection
160 |
161 | >>> prodrc( [1,2,3,4,5,6,7] )
162 | 5040
163 | """
164 | if len(collection) == 0:
165 | return 1
166 | return collection[0] * prodrc(collection[1:])
167 |
168 | def prodri(items: Iterator[float]) -> float:
169 | """Recursive product with an iterable
170 |
171 | >>> prodri( iter([1,2,3,4,5,6,7]) )
172 | 5040
173 | """
174 | try:
175 | head = next(items)
176 | except StopIteration:
177 | return 1
178 | return head*prodri(items)
179 |
180 |
181 | def test():
182 | import doctest
183 | doctest.testmod(verbose=1)
184 |
185 | if __name__ == "__main__":
186 | test()
187 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Functional Python Programming - Second Edition
2 | This is the code repository for [Functional Python Programming - Second Edition](https://www.packtpub.com/application-development/functional-python-programming-second-edition?utm_source=github&utm_medium=repository&utm_campaign=9781788627061), published by [Packt](https://www.packtpub.com/?utm_source=github). It contains all the supporting project files necessary to work through the book from start to finish.
3 | ## About the Book
4 | If you’re a Python developer who wants to discover how to take the power of functional programming (FP) and bring it into your own programs, then this book is essential for you, even if you know next to nothing about the paradigm.
5 |
6 | Starting with a general overview of functional concepts, you’ll explore common functional features such as first-class and higher-order functions, pure functions, and more. You’ll see how these are accomplished in Python 3.6 to give you the core foundations you’ll build upon. After that, you’ll discover common functional optimizations for Python to help your apps reach even higher speeds.
7 |
8 | You’ll learn FP concepts such as lazy evaluation using Python’s generator functions and expressions. Moving forward, you’ll learn to design and implement decorators to create composite functions. You'll also explore data preparation techniques and data exploration in depth, and see how the Python standard library fits the functional programming model. Finally, to top off your journey into the world of functional Python, you’ll at look at the PyMonad project and some larger examples to put everything into perspective.
9 |
10 | ## Instructions and Navigation
11 | All of the code is organized into folders. Each folder starts with a number followed by the application name. For example, Chapter02.
12 |
13 |
14 |
15 | The code will look like the following:
16 | ```
17 | s = 0
18 | for n in range(1, 10):
19 | if n % 3 == 0 or n % 5 == 0:
20 | s += n
21 | print(s)
22 | ```
23 |
24 | This book presumes some familiarity with Python 3 and general concepts of application development. We won’t look deeply at subtle or complex features of Python; we’ll avoid much consideration of the internals of the language.
25 |
26 | We’ll presume some familiarity with functional programming. Since Python is not a functional programming language, we can’t dig deeply into functional concepts. We’ll pick and choose the aspects of functional programming that fit well with Python and leverage just those that seem useful.
27 |
28 | Some of the examples use exploratory data analysis (EDA) as a problem domain to show the value of functional programming. Some familiarity with basic probability and statistics will help with this. There are only a few examples that move into more serious data science.
29 |
30 | You’ll need to have Python 3.6 installed and running. For more information on Python, visit http://www.python.org/. The examples all make extensive use of type hints, which means that the latest version of mypy must be installed as well.
31 |
32 | Check out https://pypi.python.org/pypi/mypy for the latest version of mypy.
33 |
34 | Examples in Chapter 9, More Itertools Techniques, use PIL and Beautiful Soup 4. The Pillow fork of the original PIL library works nicely; refer to https://pypi.python.org/pypi/Pillow/2.7.0 and https://pypi.python.org/pypi/beautifulsoup4/4.6.0.
35 |
36 | Examples in Chapter 14, The PyMonad Library, use PyMonad; check out https://pypi.python.org/pypi/PyMonad/1.3.
37 |
38 | All of these packages should be installed using the following:
39 |
40 | $ pip install pillow beautifulsoup4 PyMonad
41 |
42 | To confirm that all the doctests pass run the following:
43 |
44 | $ python3 test_all.py
45 |
46 | To run the tests chapter wise use the following:
47 |
48 | $ python3 -m doctest (folder name)/*.py
49 |
50 | Example:
51 |
52 | $ python3 -m doctest Chapter03/*.py
53 |
54 | There is no response when the tests pass.
55 |
56 | If you want details, you can run the following:
57 |
58 | $ python3 -m doctest -v Chapter04/*.py
59 |
60 | This will produce a lot of detail, but at the end is a count of tests passed.
61 |
62 | ## Related Products
63 | * [Functional Python Programming](https://www.packtpub.com/application-development/functional-python-programming?utm_source=github&utm_medium=repository&utm_campaign=9781784396992)
64 |
65 | * [Learn Python Programming - Fundamentals of Python - Second Edition](https://www.packtpub.com/application-development/learn-python-programming-fundamentals-python?utm_source=github&utm_medium=repository&utm_campaign=9781788996662)
66 |
67 | * [Neural Network Programming with Python](https://www.packtpub.com/big-data-and-business-intelligence/neural-network-programming-python?utm_source=github&utm_medium=repository&utm_campaign=9781784398217)
68 |
69 | ### Suggestions and Feedback
70 | [Click here](https://docs.google.com/forms/d/e/1FAIpQLSe5qwunkGf6PUvzPirPDtuy1Du5Rlzew23UBp2S-P3wB-GcwQ/viewform) if you have any feedback or suggestions.
71 |
--------------------------------------------------------------------------------
/Chapter08/ch08_ex2.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 8, Example Set 2
5 | """
6 | # pylint: disable=wrong-import-position
7 | from itertools import count
8 | from typing import Callable, Iterator, TypeVar, Tuple
9 |
10 | Generator = Iterator[Tuple[float, float]]
11 | source: Generator = zip(count(0, 0.1), (.1*c for c in count()))
12 |
13 | Extractor = Callable[[Tuple[float, float]], float]
14 | x: Extractor = lambda x_y: x_y[0]
15 | y: Extractor = lambda x_y: x_y[1]
16 |
17 | Comparator = Callable[[Tuple[float, float]], bool]
18 | neq: Comparator = lambda xy: abs(x(xy)-y(xy)) > 1.0E-12
19 |
20 | T_ = TypeVar("T_")
21 | def until(
22 | terminate: Callable[[T_], bool],
23 | iterator: Iterator[T_]
24 | ) -> T_:
25 | i = next(iterator)
26 | if terminate(i):
27 | return i
28 | return until(terminate, iterator)
29 |
30 | accumulated_error_1 = """
31 | >>> until(neq, source)
32 | (92.799999999999, 92.80000000000001)
33 | """
34 |
35 | def until_i(
36 | terminate: Callable[[T_], bool],
37 | iterator: Iterator[T_]) -> T_:
38 | for i in iterator:
39 | if terminate(i):
40 | return i
41 | raise StopIteration
42 |
43 | accumulated_error_2 = """
44 | >>> source_2 = zip(count(0, .1), (.1*c for c in count()))
45 | >>> x = lambda x_y: x_y[0]
46 | >>> y = lambda x_y: x_y[1]
47 | >>> neq6: Callable[[Tuple[float, float]], bool] = lambda xy: abs(x(xy)-y(xy)) > 1.0E-6
48 | >>> until_i(neq6, source_2)
49 | (94281.30000100001, 94281.3)
50 | >>> source_3 = zip( count(0, 1/35), (c/35 for c in count()) )
51 | >>> until_i(neq6, source_3)
52 | (73143.51428471429, 73143.5142857143)
53 | >>> source_4 = zip( count(0, 1/35), (c/35 for c in count()) )
54 | >>> until_i(lambda xy: x(xy) != y(xy), source_4)
55 | (0.2285714285714286, 0.22857142857142856)
56 | """
57 |
58 | fizz_buzz = """
59 | # Silly fizz-buzz-like problem.
60 | >>> from itertools import cycle
61 | >>> m3 = (i == 0 for i in cycle(range(3)))
62 | >>> m5 = (i == 0 for i in cycle(range(5)))
63 | >>> multipliers = zip(range(10), m3, m5)
64 | >>> list(multipliers)
65 | [(0, True, True), (1, False, False), (2, False, False), (3, True, False), (4, False, False), (5, False, True), (6, True, False), (7, False, False), (8, False, False), (9, True, False)]
66 |
67 | >>> m3 = (i == 0 for i in cycle(range(3)))
68 | >>> m5 = (i == 0 for i in cycle(range(5)))
69 | >>> multipliers = zip(range(10), m3, m5)
70 | >>> total = sum(i
71 | ... for i, *multipliers in multipliers
72 | ... if any(multipliers))
73 | >>> total
74 | 23
75 | """
76 |
77 | file_filter = """
78 | >>> import io
79 | >>> import csv
80 | >>> from itertools import cycle, compress
81 | >>> chooser = (x == 0 for x in cycle(range(3)))
82 | >>> with open("Anscombe.txt") as source_file:
83 | ... rdr= csv.reader( source_file, delimiter="\\t" )
84 | ... #keep= (row for pick, row in zip(chooser, rdr) if pick)
85 | ... keep= tuple( compress( rdr, chooser ) )
86 | >>> for row in keep:
87 | ... print( row )
88 | ["Anscombe's quartet"]
89 | ['10.0', '8.04', '10.0', '9.14', '10.0', '7.46', '8.0', '6.58']
90 | ['9.0', '8.81', '9.0', '8.77', '9.0', '7.11', '8.0', '8.84']
91 | ['6.0', '7.24', '6.0', '6.13', '6.0', '6.08', '8.0', '5.25']
92 | ['7.0', '4.82', '7.0', '7.26', '7.0', '6.42', '8.0', '7.91']
93 | """
94 |
95 | repeater = """
96 | >>> import random
97 | >>> from itertools import cycle, repeat, compress
98 | >>> all = repeat(0)
99 | >>> subset = cycle(range(3))
100 | >>> def randseq(limit):
101 | ... while True:
102 | ... yield random.randrange(limit)
103 | >>> randomized = randseq(3)
104 | >>> choose = lambda rule: (x == 0 for x in rule)
105 | >>> random.seed(1)
106 | >>> data = [random.randint(1,12) for _ in range(12)]
107 | >>> data
108 | [3, 10, 2, 5, 2, 8, 8, 8, 11, 7, 4, 2]
109 | >>> [v for v, pick in zip(data, choose(all)) if pick]
110 | [3, 10, 2, 5, 2, 8, 8, 8, 11, 7, 4, 2]
111 | >>> [v for v, pick in zip(data, choose(subset)) if pick]
112 | [3, 5, 8, 7]
113 | >>> random.seed(1)
114 | >>> [v for v, pick in zip(data, choose(randomized)) if pick]
115 | [3, 2, 2, 4, 2]
116 |
117 | >>> list(compress(data, choose(all)))
118 | [3, 10, 2, 5, 2, 8, 8, 8, 11, 7, 4, 2]
119 | >>> list(compress(data, choose(subset)))
120 | [3, 5, 8, 7]
121 | >>> random.seed(1)
122 | >>> list(compress(data, choose(randomized)))
123 | [3, 2, 2, 4, 2]
124 |
125 | """
126 |
127 | squares = """
128 | >>> from itertools import repeat
129 | >>> squares = list(sum(repeat(i, times=i)) for i in range(10))
130 | >>> print( squares )
131 | [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
132 |
133 | """
134 |
135 | __test__ = {
136 | "accumulated_error_1": accumulated_error_1,
137 | "accumulated_error_2": accumulated_error_2,
138 | "fizz_buzz": fizz_buzz,
139 | "file_filter": file_filter,
140 | "repeat": repeater,
141 | "squares": squares,
142 | }
143 |
144 | def test():
145 | import doctest
146 | doctest.testmod(verbose=True)
147 |
148 | if __name__ == "__main__":
149 | test()
150 |
--------------------------------------------------------------------------------
/Chapter06/ch06_ex2.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 6, Example Set 2
5 | """
6 | # pylint: disable=reimported,wrong-import-position
7 |
8 | from collections import defaultdict
9 |
10 | from typing import Callable, Sequence, Dict, List, TypeVar
11 | S_ = TypeVar("S_")
12 | K_ = TypeVar("K_")
13 | def group_by(key: Callable[[S_], K_], data: Sequence[S_]) -> Dict[K_, List[S_]]:
14 | def group_into(
15 | key: Callable[[S_], K_],
16 | collection: Sequence[S_],
17 | dictionary: Dict[K_, List[S_]]
18 | ) -> Dict[K_, List[S_]]:
19 | if len(collection) == 0:
20 | return dictionary
21 | head, *tail = collection
22 | dictionary[key(head)].append(head)
23 | return group_into(key, tail, dictionary)
24 | return group_into(key, data, defaultdict(list))
25 |
26 | binned_distance = lambda leg: 5*(leg[2]//5)
27 |
28 | test_group_by = """
29 | >>> from Chapter04.ch04_ex1 import (
30 | ... float_from_pair, float_lat_lon, row_iter_kml, limits, haversine, legs
31 | ... )
32 | >>> import urllib.request
33 | >>> with urllib.request.urlopen("file:./Winter%202012-2013.kml") as source:
34 | ... path= float_from_pair(float_lat_lon(row_iter_kml(source)))
35 | ... trip= tuple( (start, end, round(haversine(start, end),4))
36 | ... for start,end in legs(path))
37 |
38 | >>> by_distance= group_by(binned_distance, trip)
39 | >>> for distance in sorted(by_distance):
40 | ... print( distance, len(by_distance[distance]) )
41 | 0.0 4
42 | 5.0 5
43 | 10.0 5
44 | 15.0 9
45 | 20.0 5
46 | 25.0 5
47 | 30.0 15
48 | 35.0 5
49 | 40.0 3
50 | 45.0 3
51 | 50.0 3
52 | 55.0 1
53 | 60.0 3
54 | 65.0 1
55 | 70.0 2
56 | 80.0 1
57 | 85.0 1
58 | 115.0 1
59 | 125.0 1
60 | """
61 |
62 | from typing import Callable, Iterable, Dict, List
63 | def partition(
64 | key: Callable[[S_], K_],
65 | data: Iterable[S_]
66 | ) -> Dict[K_, List[S_]]:
67 | dictionary: Dict[K_, List[S_]] = defaultdict(list)
68 | for head in data:
69 | dictionary[key(head)].append(head)
70 | return dictionary
71 |
72 |
73 | test_partition = """
74 | >>> from Chapter04.ch04_ex1 import (
75 | ... float_from_pair, float_lat_lon, row_iter_kml, limits, haversine, legs
76 | ... )
77 | >>> import urllib.request
78 | >>> with urllib.request.urlopen("file:./Winter%202012-2013.kml") as source:
79 | ... path= float_from_pair(float_lat_lon(row_iter_kml(source)))
80 | ... trip= tuple( (start, end, round(haversine(start, end),4))
81 | ... for start,end in legs(path))
82 |
83 | >>> by_distance= partition(binned_distance, trip)
84 | >>> for distance in sorted(by_distance):
85 | ... print( distance, len(by_distance[distance]) )
86 | 0.0 4
87 | 5.0 5
88 | 10.0 5
89 | 15.0 9
90 | 20.0 5
91 | 25.0 5
92 | 30.0 15
93 | 35.0 5
94 | 40.0 3
95 | 45.0 3
96 | 50.0 3
97 | 55.0 1
98 | 60.0 3
99 | 65.0 1
100 | 70.0 2
101 | 80.0 1
102 | 85.0 1
103 | 115.0 1
104 | 125.0 1
105 | """
106 |
107 | start = lambda s, e, d: s
108 | end = lambda s, e, d: e
109 | dist = lambda s, e, d: d
110 | latitude = lambda lat, lon: lat
111 | longitude = lambda lat, lon: lon
112 |
113 | test_sorted_max = """
114 | >>> from Chapter04.ch04_ex1 import (
115 | ... float_from_pair, float_lat_lon, row_iter_kml, limits, haversine, legs
116 | ... )
117 | >>> import urllib.request
118 | >>> with urllib.request.urlopen("file:./Winter%202012-2013.kml") as source:
119 | ... path= float_from_pair(float_lat_lon(row_iter_kml(source)))
120 | ... trip= tuple( (start, end, round(haversine(start, end),4))
121 | ... for start,end in legs(path))
122 |
123 | >>> by_distance= partition(binned_distance, trip)
124 | >>> for distance in sorted(by_distance):
125 | ... print( distance, max(by_distance[distance], key=lambda pt: latitude(*start(*pt)) ) )
126 | 0.0 ((35.505665, -76.653664), (35.508335, -76.654999), 0.1731)
127 | 5.0 ((38.845501, -76.537331), (38.992832, -76.451332), 9.7151)
128 | 10.0 ((36.444168, -76.3265), (36.297501, -76.217834), 10.2537)
129 | 15.0 ((37.840332, -76.27417), (37.547165, -76.32917), 17.7944)
130 | 20.0 ((37.547165, -76.32917), (37.181168, -76.411499), 22.3226)
131 | 25.0 ((36.297501, -76.217834), (35.935501, -75.939331), 25.5897)
132 | 30.0 ((38.331501, -76.459503), (38.845501, -76.537331), 31.0756)
133 | 35.0 ((38.992832, -76.451332), (38.331165, -76.459167), 39.7277)
134 | 40.0 ((36.843334, -76.298668), (37.549, -76.331169), 42.3962)
135 | 45.0 ((37.549, -76.331169), (38.330166, -76.458504), 47.2866)
136 | 50.0 ((33.276833, -78.979332), (32.832169, -79.93383), 54.9528)
137 | 55.0 ((31.1595, -81.421997), (31.9105, -80.780998), 55.7582)
138 | 60.0 ((29.887167, -81.30883), (29.050501, -80.651169), 60.8693)
139 | 65.0 ((31.671333, -80.933167), (30.717167, -81.552498), 65.5252)
140 | 70.0 ((31.9105, -80.780998), (32.83248254681784, -79.93379468285697), 70.0694)
141 | 80.0 ((34.204666, -77.800499), (33.276833, -78.979332), 81.0363)
142 | 85.0 ((32.832169, -79.93383), (31.671333, -80.933167), 86.2095)
143 | 115.0 ((29.050501, -80.651169), (27.186001, -80.139503), 115.1751)
144 | 125.0 ((27.154167, -80.195663), (29.195168, -81.002998), 129.7748)
145 | """
146 |
147 | __test__ = {
148 | "test_group_by": test_group_by,
149 | "test_partition": test_partition,
150 | "test_sorted_max": test_sorted_max,
151 | }
152 |
153 | def test():
154 | import doctest
155 | doctest.testmod(verbose=1)
156 |
157 | if __name__ == "__main__":
158 | test()
159 |
--------------------------------------------------------------------------------
/Chapter10/ch10_ex2.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 10, Example Set 2
5 | """
6 | # pylint: disable=wrong-import-position
7 |
8 | from numbers import Number
9 | from functools import total_ordering
10 | from typing import NamedTuple
11 |
12 | class Card1(NamedTuple):
13 | rank: int
14 | suit: str
15 |
16 | test_card1 = """
17 | >>> c2s= Card1(2, '\u2660')
18 | >>> c2s.rank
19 | 2
20 | >>> c2s.suit
21 | '\u2660'
22 | >>> c2s
23 | Card1(rank=2, suit='♠')
24 | >>> len(c2s)
25 | 2
26 |
27 | This is *incorrect* behavior for games where
28 | rank is the only relevant attribute
29 |
30 | >>> c2h= Card1(2, '\u2665')
31 | >>> c2h == c2s
32 | False
33 | >>> "{0}== {1}: {2}".format( c2s, c2h, c2h == c2s )
34 | "Card1(rank=2, suit='♠')== Card1(rank=2, suit='♥'): False"
35 | """
36 |
37 | from typing import Union, Any
38 | CardInt = Union['Card', int]
39 |
40 | @total_ordering
41 | class Card(tuple):
42 | """Immutable object; rank-only comparisons.
43 |
44 | Old School.
45 |
46 | Suits= '\u2660', '\u2665', '\u2666', '\u2663'
47 | """
48 | __slots__ = ()
49 | def __new__(cls, rank, suit):
50 | obj = super().__new__(Card, (suit, rank))
51 | return obj
52 | def __repr__(self) -> str:
53 | return "{0.rank}{0.suit}".format(self)
54 | @property
55 | def rank(self) -> int:
56 | return self[1]
57 | @property
58 | def suit(self) -> str:
59 | return self[0]
60 | def __eq__(self, other: Any) -> bool:
61 | if isinstance(other, Card):
62 | return self.rank == other.rank
63 | elif isinstance(other, int):
64 | return self.rank == other
65 | return NotImplemented
66 | def __lt__(self, other: Any) -> bool:
67 | if isinstance(other, Card):
68 | return self.rank < other.rank
69 | elif isinstance(other, int):
70 | return self.rank < other
71 | return NotImplemented
72 |
73 | test_eq = """
74 | >>> c2s= Card(2, '\u2660')
75 | >>> c2s.rank
76 | 2
77 | >>> c2s.suit
78 | '\u2660'
79 | >>> c2s
80 | 2\u2660
81 | >>> len(c2s)
82 | 2
83 |
84 | This is correct behavior for games where
85 | rank is the only relevant attribute
86 |
87 | >>> c2h= Card(2, '\u2665')
88 | >>> c2h == c2s
89 | True
90 | >>> "{0}== {1}: {2}".format(c2s, c2h, c2h == c2s)
91 | '2\u2660== 2\u2665: True'
92 | >>> c2h == 2
93 | True
94 | >>> 2 == c2h
95 | True
96 | """
97 |
98 | test_order = """
99 | >>> c2s= Card(2, '\u2660')
100 | >>> c3h= Card(3, '\u2665')
101 | >>> c4c= Card(4, '\u2663')
102 | >>> c2s <= c3h < c4c
103 | True
104 | >>> c3h >= c3h
105 | True
106 | >>> c3h > c2s
107 | True
108 | >>> c4c != c2s
109 | True
110 | """
111 |
112 | extra_comparisons = """
113 | These don't work, the logic doesn't fit with total_ordering.
114 |
115 | >>> c4c= Card(4, '\u2663')
116 | >>> try:
117 | ... print("c4c > 3", c4c > 3)
118 | ... except TypeError as e:
119 | ... print(e)
120 | '>' not supported between instances of 'Card' and 'int'
121 | >>> try:
122 | ... print("3 < c4c", 3 < c4c)
123 | ... except TypeError as e:
124 | ... print(e)
125 | '<' not supported between instances of 'int' and 'Card'
126 | """
127 |
128 | @total_ordering
129 | class Card2(NamedTuple):
130 | rank: int
131 | suit: str
132 | def __str__(self) -> str:
133 | return "{0.rank}{0.suit}".format(self)
134 | def __eq__(self, other: Any) -> bool:
135 | if isinstance(other, Card2):
136 | return self.rank == other.rank
137 | elif isinstance(other, int):
138 | return self.rank == other
139 | return NotImplemented
140 | def __lt__(self, other: Any) -> bool:
141 | if isinstance(other, Card2):
142 | return self.rank < other.rank
143 | elif isinstance(other, int):
144 | return self.rank < other
145 | return NotImplemented
146 |
147 | test_eq_2 = """
148 | >>> c2s = Card2(2, '\u2660')
149 | >>> c2s.rank
150 | 2
151 | >>> c2s.suit
152 | '\u2660'
153 | >>> c2s
154 | Card2(rank=2, suit='\u2660')
155 | >>> len(c2s)
156 | 2
157 |
158 | This is correct behavior for games where
159 | rank is the only relevant attribute
160 |
161 | >>> c2h= Card2(2, '\u2665')
162 | >>> c2h == c2s
163 | True
164 | >>> "{0} == {1}: {2}".format(c2s, c2h, c2h == c2s)
165 | '2\u2660 == 2\u2665: True'
166 | >>> c2h == 2
167 | True
168 | >>> 2 == c2h
169 | True
170 | """
171 |
172 | test_order_2 = """
173 | >>> c2s= Card2(2, '\u2660')
174 | >>> c3h= Card2(3, '\u2665')
175 | >>> c4c= Card2(4, '\u2663')
176 | >>> c2s <= c3h < c4c
177 | True
178 | >>> c3h >= c3h
179 | True
180 | >>> c3h > c2s
181 | True
182 | >>> c4c != c2s
183 | True
184 | """
185 |
186 | extra_comparisons_2 = """
187 | These don't work, the logic doesn't fit with total_ordering.
188 |
189 | >>> c4c= Card2(4, '\u2663')
190 | >>> try:
191 | ... print("c4c > 3", c4c > 3)
192 | ... except TypeError as e:
193 | ... print(e)
194 | '>' not supported between instances of 'Card2' and 'int'
195 | >>> try:
196 | ... print("3 < c4c", 3 < c4c)
197 | ... except TypeError as e:
198 | ... print(e)
199 | '<' not supported between instances of 'int' and 'Card2'
200 | """
201 |
202 | __test__ = {
203 | "test_card1": test_card1,
204 | "test_eq": test_eq,
205 | "test_order": test_order,
206 | "extra_comparisons": extra_comparisons,
207 | "test_eq_2": test_eq_2,
208 | "test_order_2": test_order_2,
209 | "extra_comparisons_2": extra_comparisons_2,
210 | }
211 |
212 | def test():
213 | import doctest
214 | doctest.testmod(verbose=1)
215 |
216 | if __name__ == "__main__":
217 | test()
218 |
--------------------------------------------------------------------------------
/Chapter06/ch06_ex3.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 6, Example Set 3
5 | """
6 | # pylint: disable=reimported,wrong-import-order,wrong-import-position
7 |
8 | import xml.etree.ElementTree as XML
9 | import re
10 |
11 | from typing import Tuple, List, Any
12 |
13 | def pick_lat_lon(lon: Any, lat: Any, alt: Any) -> Tuple[Any, Any]:
14 | return lat, lon
15 |
16 | def comma_split(text: str) -> List[str]:
17 | return text.split(",")
18 |
19 | from typing import TextIO, Iterator, Tuple, cast
20 | def float_lat_lon3(file_obj: TextIO) -> Iterator[Tuple[float, ...]]:
21 | """
22 | Less than ideal parser: does too much in one hard-to-tweak step.
23 |
24 | >>> import io
25 | >>> doc= io.StringIO('''
26 | ...
30 | ...
31 | ...
32 | ... Waypoints.kml
33 | ... 1
34 | ...
35 | ...
36 | ... -76.33029518659048,37.54901619777347,0
37 | ...
38 | ...
39 | ...
40 | ...
41 | ... ''')
42 | >>> list(float_lat_lon3( doc ))
43 | [(37.54901619777347, -76.33029518659048)]
44 | """
45 | ns_map = {
46 | "ns0": "http://www.opengis.net/kml/2.2",
47 | "ns1": "http://www.google.com/kml/ext/2.2"}
48 | xpath = (
49 | "./ns0:Document/ns0:Folder/"
50 | "ns0:Placemark/ns0:Point/ns0:coordinates")
51 | doc = XML.parse(file_obj)
52 | return (
53 | tuple(
54 | map(float,
55 | pick_lat_lon(*comma_split(
56 | cast(str, coordinates.text)
57 | ))
58 | )
59 | )
60 | for coordinates in doc.findall(xpath, ns_map)
61 | )
62 |
63 | test_float_lan_lon3 = """
64 | >>> import urllib.request
65 | >>> with urllib.request.urlopen("file:./Winter%202012-2013.kml") as source:
66 | ... trip= tuple(float_lat_lon3(source))
67 | >>> len(trip)
68 | 74
69 | >>> trip[0]
70 | (37.54901619777347, -76.33029518659048)
71 | >>> trip[-1]
72 | (38.976334, -76.473503)
73 |
74 | """
75 |
76 | def row_iter_kml(file_obj: TextIO) -> Iterator[List[str]]:
77 | """
78 | More consistent with CSV parsing.
79 | Low-level produces rows of tuples of text.
80 | High-level produces application objects.
81 |
82 | """
83 | ns_map = {
84 | "ns0": "http://www.opengis.net/kml/2.2",
85 | "ns1": "http://www.google.com/kml/ext/2.2"}
86 | xpath = (
87 | "./ns0:Document/ns0:Folder/"
88 | "ns0:Placemark/ns0:Point/ns0:coordinates")
89 | doc = XML.parse(file_obj)
90 | return (
91 | comma_split(
92 | cast(str, coordinates.text)
93 | )
94 | for coordinates in doc.findall(xpath, ns_map)
95 | )
96 |
97 | def float_lat_lon(
98 | row_iter: Iterator[Tuple[str, ...]]) -> Iterator[Tuple[float, ...]]:
99 | return (
100 | tuple(
101 | map(float, pick_lat_lon(*row))
102 | )
103 | for row in row_iter
104 | )
105 |
106 | test_row_iter_kml = """
107 | >>> import urllib.request
108 | >>> with urllib.request.urlopen("file:./Winter%202012-2013.kml") as source:
109 | ... trip = tuple( float_lat_lon(row_iter_kml(source)) )
110 | >>> len(trip)
111 | 74
112 | >>> trip[0]
113 | (37.54901619777347, -76.33029518659048)
114 | >>> trip[-1]
115 | (38.976334, -76.473503)
116 |
117 | """
118 |
119 | Head_Body = Tuple[Tuple[str, str], Iterator[List[str]]]
120 | def row_iter_gpl(file_obj: TextIO) -> Head_Body:
121 | header_pat = re.compile(
122 | r"GIMP Palette\nName:\s*(.*?)\nColumns:\s*(.*?)\n#\n",
123 | re.M)
124 |
125 | def read_head(
126 | file_obj: TextIO
127 | ) -> Tuple[Tuple[str, str], TextIO]:
128 | match = header_pat.match("".join(file_obj.readline() for _ in range(4)))
129 | return (match.group(1), match.group(2)), file_obj
130 |
131 | def read_tail(
132 | headers: Tuple[str, str],
133 | file_obj: TextIO) -> Head_Body:
134 | return (
135 | headers,
136 | (next_line.split() for next_line in file_obj)
137 | )
138 |
139 | return read_tail(*read_head(file_obj))
140 |
141 | # from collections import namedtuple
142 | # Color = namedtuple("Color", ("red", "green", "blue", "name"))
143 |
144 | from typing import NamedTuple
145 | class Color(NamedTuple):
146 | red: int
147 | blue: int
148 | green: int
149 | name: str
150 |
151 | def color_palette(
152 | headers: Tuple[str, str],
153 | row_iter: Iterator[List[str]]
154 | ) -> Tuple[str, str, Tuple[Color, ...]]:
155 | name, columns = headers
156 | colors = tuple(
157 | Color(int(r), int(g), int(b), " ".join(name))
158 | for r, g, b, *name in row_iter)
159 | return name, columns, colors
160 |
161 | test_row_iter_gpl = """
162 | >>> with open("crayola.gpl") as source:
163 | ... name, columns, colors = color_palette( *row_iter_gpl(source) )
164 | >>> name
165 | 'Crayola'
166 | >>> columns
167 | '16'
168 | >>> len(colors)
169 | 133
170 | """
171 |
172 | __test__ = {
173 | "test_float_lan_lon3": test_float_lan_lon3,
174 | "test_row_iter_kml": test_row_iter_kml,
175 | "test_row_iter_gpl": test_row_iter_gpl,
176 | }
177 |
178 | def test():
179 | import doctest
180 | doctest.testmod(verbose=1)
181 |
182 | if __name__ == "__main__":
183 | test()
184 |
--------------------------------------------------------------------------------
/Chapter16/ch16_ex3.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 16, Example Set 3
5 | """
6 | # pylint: disable=wrong-import-position
7 |
8 | from functools import lru_cache, reduce
9 | import operator
10 | from fractions import Fraction
11 | import warnings
12 |
13 | @lru_cache(128)
14 | def fact(k: int) -> int:
15 | """Simple factorial of a Fraction or an int.
16 |
17 | >>> fact(1)
18 | 1
19 | >>> fact(2)
20 | 2
21 | >>> fact(3)
22 | 6
23 | >>> fact(4)
24 | 24
25 | """
26 | if k < 2:
27 | return 1
28 | return reduce(operator.mul, range(2, int(k)+1))
29 |
30 | from typing import Iterator, Iterable, Callable, cast
31 | def gamma(s: Fraction, z: Fraction) -> Fraction:
32 | """Incomplete gamma function.
33 |
34 | >>> import math
35 | >>> round(float(gamma(1, 2)),7)
36 | 0.8646647
37 | >>> round(1-math.exp(-2),7)
38 | 0.8646647
39 | >>> round(float(gamma(1, 3)),7)
40 | 0.9502129
41 | >>> round(1-math.exp(-3),7)
42 | 0.9502129
43 | >>> round(float(gamma(Fraction(1,2), Fraction(2))),7)
44 | 1.6918067
45 | >>> round(math.sqrt(math.pi)*math.erf(math.sqrt(2)),7)
46 | 1.6918067
47 | >>> g= gamma(Fraction(1,2), Fraction(2)).limit_denominator(1000000)
48 | >>> g
49 | Fraction(144438, 85375)
50 | >>> round(float(g),7)
51 | 1.6918067
52 | """
53 | def terms(s: Fraction, z: Fraction) -> Iterator[Fraction]:
54 | """Terms for computing partial gamma"""
55 | for k in range(100):
56 | t2 = Fraction(z**(s+k))/(s+k)
57 | term = Fraction((-1)**k, fact(k))*t2
58 | yield term
59 | warnings.warn("More than 100 terms")
60 | def take_until(function: Callable[..., bool], source: Iterable) -> Iterator:
61 | """Take from source until function is false."""
62 | for v in source:
63 | if function(v):
64 | return
65 | yield v
66 | ε = 1E-8
67 | g = sum(take_until(lambda t: abs(t) < ε, terms(s, z)))
68 | # cast required to narrow sum from Union[Fraction, int] to Fraction
69 | return cast(Fraction, g)
70 |
71 | pi = Fraction(5_419_351, 1_725_033)
72 | # Fraction(817_696_623, 260_280_919)
73 |
74 | sqrt_pi = Fraction(677_622_787, 382_307_718)
75 | # Fraction(582_540, 328_663) # Good for almost all test cases but one.
76 |
77 | from typing import Union
78 | def Gamma_Half(k: Union[int, Fraction]) -> Union[int, Fraction]:
79 | """Gamma(k) with special case for k = n+1/2; k-1/2=n.
80 |
81 | >>> import math
82 | >>> Gamma_Half(2)
83 | 1
84 | >>> Gamma_Half(3)
85 | 2
86 | >>> Gamma_Half(4)
87 | 6
88 | >>> Gamma_Half(5)
89 | 24
90 |
91 | >>> g= Gamma_Half(Fraction(1,2)) # Varies with sqrt_pi setting
92 | >>> g.limit_denominator(2_000_000)
93 | Fraction(582540, 328663)
94 | >>> round(float(g), 7)
95 | 1.7724539
96 | >>> round(math.sqrt(math.pi), 7)
97 | 1.7724539
98 | >>> g= Gamma_Half(Fraction(3,2)) # Varies with sqrt_pi setting
99 | >>> g.limit_denominator(2_000_000)
100 | Fraction(291270, 328663)
101 | >>> round(float(g), 7)
102 | 0.8862269
103 | >>> round(math.sqrt(math.pi)/2, 7)
104 | 0.8862269
105 | """
106 | if isinstance(k, int):
107 | return fact(k-1)
108 | elif isinstance(k, Fraction):
109 | if k.denominator == 1:
110 | return fact(k-1)
111 | elif k.denominator == 2:
112 | n = k-Fraction(1, 2)
113 | return fact(2*n)/(Fraction(4**n)*fact(n))*sqrt_pi
114 | raise ValueError(f"Can't compute Γ({k})")
115 |
116 | def cdf(x: Union[Fraction, float], k: int) -> Fraction:
117 | """χ² cumulative distribution function.
118 |
119 | :param x: χ² value -- generally sum(obs[i]-exp[i])**2/exp[i]
120 | for parallel sequences of observed and expected values.
121 | :param k: degrees of freedom >= 1; generally len(data)-1
122 |
123 | From http://en.wikipedia.org/wiki/Chi-squared_distribution
124 |
125 | >>> round(float(cdf(0.004, 1)), 2)
126 | 0.95
127 | >>> cdf(0.004, 1).limit_denominator(100)
128 | Fraction(94, 99)
129 | >>> round(float(cdf(10.83, 1)), 3)
130 | 0.001
131 | >>> cdf(10.83, 1).limit_denominator(1000)
132 | Fraction(1, 1000)
133 | >>> round(float(cdf(3.94, 10)), 2)
134 | 0.95
135 | >>> cdf(3.94, 10).limit_denominator(100)
136 | Fraction(19, 20)
137 | >>> round(float(cdf(29.59, 10)), 3)
138 | 0.001
139 | >>> cdf(29.59, 10).limit_denominator(10000)
140 | Fraction(8, 8005)
141 | >>> expected = [0.95, 0.90, 0.80, 0.70, 0.50, 0.30, 0.20, 0.10, 0.05, 0.01, 0.001]
142 | >>> chi2 = [0.004, 0.02, 0.06, 0.15, 0.46, 1.07, 1.64, 2.71, 3.84, 6.64, 10.83]
143 | >>> act = [round(float(x), 3)
144 | ... for x in map(cdf, chi2, [1]*len(chi2))]
145 | >>> act
146 | [0.95, 0.888, 0.806, 0.699, 0.498, 0.301, 0.2, 0.1, 0.05, 0.01, 0.001]
147 |
148 | From http://www.itl.nist.gov/div898/handbook/prc/section4/prc45.htm
149 |
150 | >>> round(float(cdf(19.18, 6)), 5)
151 | 0.00387
152 | >>> round(float(cdf(12.5916, 6)), 2)
153 | 0.05
154 | >>> cdf(19.18, 6).limit_denominator(1000)
155 | Fraction(3, 775)
156 |
157 | From http://www.itl.nist.gov/div898/handbook/prc/section4/prc46.htm
158 |
159 | >>> round(float(cdf(12.131, 4)), 5) # 0.01639 shown in reference
160 | 0.0164
161 | >>> cdf(12.131, 4).limit_denominator(1000)
162 | Fraction(16, 975)
163 | >>> round(float(cdf(9.488, 4)), 2)
164 | 0.05
165 | >>> cdf(9.488, 4).limit_denominator(1000)
166 | Fraction(1, 20)
167 | """
168 | return 1-gamma(Fraction(k, 2), Fraction(x/2))/Gamma_Half(Fraction(k, 2))
169 | #return 1-gamma(Fraction(k,2), Fraction(x/2).limit_denominator(1000))/Gamma_Half(Fraction(k,2))
170 |
171 | def test():
172 | import doctest
173 | doctest.testmod(verbose=1)
174 |
175 | if __name__ == "__main__":
176 | test()
177 |
--------------------------------------------------------------------------------
/Chapter16/ch16_ex2.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 16, Example Set 2
5 |
6 | See http://www.itl.nist.gov/div898/handbook/prc/section4/prc45.htm
7 | """
8 | # pylint: disable=wrong-import-position,reimported
9 |
10 | # Original data.
11 | # Three rows for each shift.
12 | # Four columns for each defect.
13 | expected_defects = [
14 | [15, 21, 45, 13],
15 | [26, 31, 34, 5],
16 | [33, 17, 49, 20],
17 | ]
18 |
19 | # Raw data reader based on qa_data.csv file.
20 |
21 | from typing import TextIO, cast
22 | import csv
23 | from collections import Counter
24 | from types import SimpleNamespace
25 | def defect_reduce(input_file: TextIO) -> Counter:
26 | """
27 | >>> with open("qa_data.csv") as input:
28 | ... defects = defect_reduce(input)
29 | >>> len(defects)
30 | 12
31 | >>> sum(defects.values())
32 | 309
33 | """
34 | rdr = csv.DictReader(input_file)
35 | assert set(rdr.fieldnames) == set(["defect_type", "serial_number", "shift"])
36 | rows_ns = (SimpleNamespace(**row) for row in rdr)
37 | defects = (
38 | (row.shift, row.defect_type)
39 | for row in rows_ns
40 | if row.defect_type)
41 | tally = Counter(defects)
42 | return tally
43 |
44 | # Alternative reader based on summaries instead of details
45 |
46 | from typing import TextIO
47 | from collections import Counter
48 | import csv
49 | def defect_counts(source: TextIO) -> Counter:
50 | """
51 | >>> import io
52 | >>> source = io.StringIO('''shift,defect_code,count
53 | ... 1,A,15
54 | ... 2,A,26
55 | ... 3,A,33
56 | ... 1,B,21
57 | ... 2,B,31
58 | ... 3,B,17
59 | ... 1,C,45
60 | ... 2,C,34
61 | ... 3,C,49
62 | ... 1,D,13
63 | ... 2,D,5
64 | ... 3,D,20''')
65 | >>> defects = defect_counts(source)
66 | >>> len(defects)
67 | 12
68 | >>> sum(defects.values())
69 | 309
70 | """
71 | rdr = csv.DictReader(source)
72 | assert set(rdr.fieldnames) == set(["shift", "defect_code", "count"])
73 | rows_ns = (SimpleNamespace(**row) for row in rdr)
74 | convert = map(
75 | lambda d: ((d.shift, d.defect_code), int(d.count)),
76 | rows_ns)
77 | return Counter(dict(convert))
78 |
79 | from fractions import Fraction
80 | def chi2_eval(defects: Counter) -> Fraction:
81 | """
82 | >>> with open("qa_data.csv") as input:
83 | ... defects = defect_reduce(input)
84 | >>> chi2 = chi2_eval(defects) #doctest: +NORMALIZE_WHITESPACE
85 | Total 309
86 | Shift Total [('1', 94), ('2', 96), ('3', 119)]
87 | Type Total [('A', 74), ('B', 69), ('C', 128), ('D', 38)]
88 | Prob(shift) of defect [('1', Fraction(94, 309)), ('2', Fraction(32, 103)), ('3', Fraction(119, 309))]
89 | Prob(type) of defect [('A', Fraction(74, 309)), ('B', Fraction(23, 103)), ('C', Fraction(128, 309)), ('D', Fraction(38, 309))]
90 |
91 | Contingency Table
92 | obs exp obs exp obs exp obs exp
93 | 15 22.51 21 20.99 45 38.94 13 11.56 94
94 | 26 22.99 31 21.44 34 39.77 5 11.81 96
95 | 33 28.50 17 26.57 49 49.29 20 14.63 119
96 | 74 69 128 38 309
97 | >>> chi2.limit_denominator(100)
98 | Fraction(1400, 73)
99 | """
100 | # pylint: disable=too-many-locals
101 | total = sum(defects.values())
102 | print(f"Total {total}")
103 |
104 | shift_totals = sum(
105 | (Counter({s: defects[s, d]}) for s, d in defects),
106 | Counter() # start value is an empty Counter!
107 | )
108 | shift_detail = list(
109 | (s, shift_totals[s])
110 | for s in sorted(shift_totals)
111 | )
112 | print(f"Shift Total {shift_detail}")
113 |
114 | type_totals = sum(
115 | (Counter({d: defects[s, d]}) for s, d in defects),
116 | Counter() # start value = empty Counter
117 | )
118 | type_detail = list(
119 | (t, type_totals[t])
120 | for t in sorted(type_totals))
121 | print(f"Type Total {type_detail}")
122 |
123 | P_shift = {
124 | shift: Fraction(shift_totals[shift], total)
125 | for shift in sorted(shift_totals)
126 | }
127 |
128 | P_shift_details = list(
129 | (s, P_shift[s]) for s in sorted(P_shift))
130 | print(f"Prob(shift) of defect {P_shift_details}")
131 |
132 | P_type = {
133 | type: Fraction(type_totals[type], total)
134 | for type in sorted(type_totals)
135 | }
136 |
137 | P_type_details = list(
138 | (t, P_type[t]) for t in sorted(P_type))
139 | print(f"Prob(type) of defect {P_type_details}")
140 |
141 | expected = {
142 | (s, t): P_shift[s]*P_type[t]*total
143 | for t in P_type
144 | for s in P_shift
145 | }
146 |
147 | print("\nContingency Table")
148 | print("obs exp "*len(type_totals))
149 | for s in sorted(shift_totals):
150 | pairs = [
151 | f"{defects[s,t]:3d} {float(expected[s,t]):5.2f}"
152 | for t in sorted(type_totals)
153 | ]
154 | print(f"{' '.join(pairs)} {shift_totals[s]:3d}")
155 | footers = [
156 | f"{type_totals[t]:3d} "
157 | for t in sorted(type_totals)]
158 | print(f"{' '.join(footers)} {total:3d}")
159 |
160 | # Difference
161 |
162 | diff = lambda e, o: (e-o)**2/e
163 |
164 | chi2 = sum(
165 | diff(expected[s, t], defects[s, t])
166 | for s in shift_totals
167 | for t in type_totals
168 | )
169 | # Cast required to narrow sum from Union[Fraction, int] to Fraction
170 | return cast(Fraction, chi2)
171 |
172 | from Chapter16.ch16_ex3 import cdf
173 |
174 | def demo():
175 | with open("qa_data.csv") as input_file:
176 | defects = defect_reduce(input_file)
177 | chi2 = chi2_eval(defects)
178 | print(f"χ² = {float(chi2):.2f}")
179 | print(f"χ² = {chi2.limit_denominator(50)}, P = {float(cdf(chi2, 6)):0.3%}")
180 | print(f"χ² = {chi2.limit_denominator(100)}, P = {cdf(chi2, 6).limit_denominator(1000)}")
181 |
182 | def test():
183 | import doctest
184 | doctest.testmod(verbose=1)
185 |
186 | if __name__ == "__main__":
187 | test()
188 | demo()
189 |
--------------------------------------------------------------------------------
/Chapter07/ch07_ex4.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 7, Example Set 4
5 | """
6 | # pylint: disable=wrong-import-position,reimported
7 |
8 | # Even more generic Rank-Order processing.
9 |
10 | # from collections import namedtuple
11 | # Rank_Data = namedtuple("Rank_Data", ("rank_seq", "raw"))
12 |
13 | from typing import NamedTuple, Tuple, Any
14 | class Rank_Data(NamedTuple):
15 | """
16 | Two similar variations:
17 | - Rank_Data((rank,), data) -- singleton ranking
18 | - Rank_Data((rank, rank), data)
19 |
20 | >>> data = {'key1': 1, 'key2': 2}
21 | >>> r = Rank_Data((2, 7), data)
22 | >>> r.rank_seq[0]
23 | 2
24 | >>> r.raw
25 | {'key1': 1, 'key2': 2}
26 | """
27 | rank_seq: Tuple[float]
28 | raw: Any
29 |
30 | from typing import (
31 | Callable, Sequence, Iterator, Union, Iterable, TypeVar, cast,
32 | Union
33 | )
34 | K_ = TypeVar("K_") # Some comparable key type used for ranking.
35 | Source = Union[Rank_Data, Any] # Generic with respect to the source
36 | def rank_data(
37 | seq_or_iter: Union[Sequence[Source], Iterator[Source]],
38 | key: Callable[[Rank_Data], K_] = lambda obj: cast(K_, obj)
39 | ) -> Iterable[Rank_Data]:
40 | """Rank raw data by creating Rank_Data objects from an iterable.
41 | Or rerank existing data by creating new Rank_Data objects from
42 | old Rank_Data objects.
43 |
44 | >>> scalars = [0.8, 1.2, 1.2, 2.3, 18]
45 | >>> list(rank_data(scalars)) # doctest: +NORMALIZE_WHITESPACE
46 | [Rank_Data(rank_seq=(1.0,), raw=0.8), Rank_Data(rank_seq=(2.5,), raw=1.2),
47 | Rank_Data(rank_seq=(2.5,), raw=1.2), Rank_Data(rank_seq=(4.0,), raw=2.3),
48 | Rank_Data(rank_seq=(5.0,), raw=18)]
49 |
50 | >>> pairs = ((2, 0.8), (3, 1.2), (5, 1.2), (7, 2.3), (11, 18))
51 | >>> rank_x = tuple(rank_data(pairs, key=lambda x:x[0] ))
52 | >>> rank_x # doctest: +NORMALIZE_WHITESPACE
53 | (Rank_Data(rank_seq=(1.0,), raw=(2, 0.8)),
54 | Rank_Data(rank_seq=(2.0,), raw=(3, 1.2)),
55 | Rank_Data(rank_seq=(3.0,), raw=(5, 1.2)),
56 | Rank_Data(rank_seq=(4.0,), raw=(7, 2.3)),
57 | Rank_Data(rank_seq=(5.0,), raw=(11, 18)))
58 | >>> rank_xy = (rank_data(rank_x, key=lambda x:x[1] ))
59 | >>> tuple(rank_xy) # doctest: +NORMALIZE_WHITESPACE
60 | (Rank_Data(rank_seq=(1.0, 1.0), raw=(2, 0.8)),
61 | Rank_Data(rank_seq=(2.0, 2.5), raw=(3, 1.2)),
62 | Rank_Data(rank_seq=(3.0, 2.5), raw=(5, 1.2)),
63 | Rank_Data(rank_seq=(4.0, 4.0), raw=(7, 2.3)),
64 | Rank_Data(rank_seq=(5.0, 5.0), raw=(11, 18)))
65 | """
66 | if isinstance(seq_or_iter, Iterator):
67 | # Not a sequence? Materialize a sequence object.
68 | yield from rank_data(list(seq_or_iter), key)
69 | return
70 | data: Sequence[Rank_Data]
71 | if isinstance(seq_or_iter[0], Rank_Data):
72 | # Collection of Rank_Data is what we prefer.
73 | data = seq_or_iter
74 | else:
75 | # Collection of non-Rank_Data? Convert to Rank_Data and process.
76 | empty_ranks: Tuple[float] = cast(Tuple[float], ())
77 | data = list(
78 | Rank_Data(empty_ranks, raw_data)
79 | for raw_data in cast(Sequence[Source], seq_or_iter)
80 | )
81 |
82 | for r, rd in rerank(data, key):
83 | new_ranks = cast(Tuple[float], rd.rank_seq + cast(Tuple[float], (r,)))
84 | yield Rank_Data(new_ranks, rd.raw)
85 |
86 | from typing import Callable, Tuple, Iterator, Iterable, TypeVar, cast
87 | def rerank(
88 | rank_data_iter: Iterable[Rank_Data],
89 | key: Callable[[Rank_Data], K_]
90 | ) -> Iterator[Tuple[float, Rank_Data]]:
91 | """Re-rank by adding another rank order to a Rank_Data object.
92 | """
93 | sorted_iter = iter(
94 | sorted(
95 | rank_data_iter, key=lambda obj: key(obj.raw)
96 | )
97 | )
98 | # Apply ranker to `head, *tail = sorted(rank_data_iter)`
99 | head = next(sorted_iter)
100 | yield from ranker(sorted_iter, 0, [head], key)
101 |
102 | from typing import Iterator, Tuple
103 | def yield_sequence(
104 | rank: float,
105 | same_rank_iter: Iterator[Rank_Data]
106 | ) -> Iterator[Tuple[float, Rank_Data]]:
107 | """Emit a sequence of same rank values."""
108 | head = next(same_rank_iter)
109 | yield rank, head
110 | yield from yield_sequence(rank, same_rank_iter)
111 |
112 | from typing import List
113 | def ranker(
114 | sorted_iter: Iterator[Rank_Data],
115 | base: float,
116 | same_rank_seq: List[Rank_Data],
117 | key: Callable[[Rank_Data], K_]
118 | ) -> Iterator[Tuple[float, Rank_Data]]:
119 | """Rank values from a sorted_iter using a base rank value.
120 | If the next value's key matches same_rank_seq, accumulate those.
121 | If the next value's key is different, accumulate same rank values
122 | and start accumulating a new sequence.
123 |
124 | >>> scalars= [0.8, 1.2, 1.2, 2.3, 18]
125 | >>> list(rank_data(scalars)) # doctest: +NORMALIZE_WHITESPACE
126 | [Rank_Data(rank_seq=(1.0,), raw=0.8), Rank_Data(rank_seq=(2.5,), raw=1.2),
127 | Rank_Data(rank_seq=(2.5,), raw=1.2), Rank_Data(rank_seq=(4.0,), raw=2.3),
128 | Rank_Data(rank_seq=(5.0,), raw=18)]
129 | """
130 | try:
131 | value = next(sorted_iter)
132 | except StopIteration:
133 | # Final batch
134 | dups = len(same_rank_seq)
135 | yield from yield_sequence(
136 | (base+1+base+dups)/2, iter(same_rank_seq))
137 | return
138 | if key(value.raw) == key(same_rank_seq[0].raw):
139 | # Matching, accumulate a batch
140 | yield from ranker(
141 | sorted_iter, base, same_rank_seq+[value], key)
142 | else:
143 | # Non-matching, emit the previous batch and start a new batch
144 | dups = len(same_rank_seq)
145 | yield from yield_sequence(
146 | (base+1+base+dups)/2, iter(same_rank_seq))
147 | yield from ranker(
148 | sorted_iter, base+dups, [value], key)
149 |
150 | __test__ = {
151 | 'example': '''
152 | >>> scalars= [0.8, 1.2, 1.2, 2.3, 18]
153 | >>> list(rank_data(scalars))
154 | [Rank_Data(rank_seq=(1.0,), raw=0.8), Rank_Data(rank_seq=(2.5,), raw=1.2), Rank_Data(rank_seq=(2.5,), raw=1.2), Rank_Data(rank_seq=(4.0,), raw=2.3), Rank_Data(rank_seq=(5.0,), raw=18)]
155 | '''
156 | }
157 |
158 | def test():
159 | import doctest
160 | doctest.testmod(verbose=True)
161 |
162 | if __name__ == "__main__":
163 | test()
164 |
--------------------------------------------------------------------------------
/Chapter02/ch02_ex1.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 2, Example Set 1
5 | """
6 | # pylint: disable=missing-docstring,wrong-import-position
7 |
8 | from typing import Iterator
9 | def numbers() -> Iterator[int]:
10 | for i in range(1024):
11 | # print(f"= {i}")
12 | yield i
13 |
14 | def sum_to(n: int) -> int:
15 | sum: int = 0
16 | for i in numbers():
17 | if i == n:
18 | break
19 | sum += i
20 | return sum
21 |
22 |
23 | def namedtuples():
24 | """nametuple vs. class performance"""
25 | import timeit
26 | class_time = timeit.timeit(
27 | """x= X(1,2,3)""",
28 | """
29 | class X:
30 | def __init__( self, a, b, c ):
31 | self.a= a
32 | self.b= b
33 | self.c= c
34 | """)
35 | print(f"class {class_time}")
36 |
37 | tuple_time = timeit.timeit("""x= (1,2,3)""")
38 | print(f"tuple {tuple_time}")
39 |
40 | collections_nt_time = timeit.timeit(
41 | """x= X(1,2,3)""",
42 | """
43 | from collections import namedtuple
44 | X = namedtuple( "X", ("a", "b", "c") )
45 | """)
46 | print(f"namedtuple {collections_nt_time}")
47 |
48 | typing_nt_time = timeit.timeit(
49 | """x= X(1,2,3)""",
50 | """
51 | from typing import NamedTuple
52 | class X(NamedTuple):
53 | a: str
54 | b: str
55 | c: str
56 | """)
57 | print(f"NamedTuple {typing_nt_time}")
58 |
59 | import math
60 | def isprimei(n: int) -> bool:
61 | """Is n prime?
62 |
63 | >>> isprimei(2)
64 | True
65 | >>> tuple( isprimei(x) for x in range(3,11) )
66 | (True, False, True, False, True, False, False, False)
67 | """
68 | if n < 2:
69 | return False
70 | if n == 2:
71 | return True
72 | if n % 2 == 0:
73 | return False
74 | for i in range(3, 1+int(math.sqrt(n)), 2):
75 | if n % i == 0:
76 | return False
77 | return True
78 |
79 | def isprimer(n: int) -> bool:
80 | """Is n prime?
81 |
82 | >>> isprimer(2)
83 | True
84 | >>> tuple( isprimer(x) for x in range(3,11) )
85 | (True, False, True, False, True, False, False, False)
86 | """
87 | def isprime(k: int, coprime: int) -> bool:
88 | """Is k relatively prime to the value coprime?"""
89 | if k < coprime*coprime:
90 | return True
91 | if k % coprime == 0:
92 | return False
93 | return isprime(k, coprime+2)
94 |
95 | if n < 2:
96 | return False
97 | if n == 2:
98 | return True
99 | if n % 2 == 0:
100 | return False
101 | return isprime(n, 3)
102 |
103 | def isprimeg(n: int) -> bool:
104 | """Is n prime?
105 |
106 | >>> isprimeg(2)
107 | True
108 | >>> tuple( isprimeg(x) for x in range(3,11) )
109 | (True, False, True, False, True, False, False, False)
110 |
111 | Remarkably slow for large primes, for example, M_61=2**61-1.
112 |
113 | >>> isprimeg(62710593)
114 | False
115 | """
116 | if n < 2:
117 | return False
118 | if n == 2:
119 | return True
120 | if n % 2 == 0:
121 | return False
122 | return not any(n%p == 0 for p in range(3, int(math.sqrt(n))+1, 2))
123 |
124 | def recursion():
125 | """Recursion Performance Comparison.
126 | """
127 | import timeit
128 | print("isprimei",
129 | timeit.timeit(
130 | """isprimei(131071)""",
131 | """
132 | import math
133 | def isprimei( n ):
134 | if n < 2: return False
135 | if n == 2: return True
136 | if n % 2 == 0: return False
137 | for i in range(3,1+int(math.sqrt(n)),2):
138 | if n % i == 0:
139 | return False
140 | return True
141 | """, number=100000))
142 |
143 | print("isprimer",
144 | timeit.timeit(
145 | """isprimer(131071)""",
146 | """
147 | def isprimer( n ):
148 | def isprime( n, coprime ):
149 | if n < coprime*coprime: return True
150 | if n % coprime == 0: return False
151 | return isprime( n, coprime+2 )
152 |
153 | if n < 2: return False
154 | if n == 2: return True
155 | if n % 2 == 0: return False
156 | return isprime( n, 3 )
157 | """, number=100000))
158 |
159 | print("isprimeg",
160 | timeit.timeit(
161 | """isprimeg(131071)""",
162 | """
163 | import math
164 | def isprimeg( n ):
165 | if n < 2: return False
166 | if n == 2: return True
167 | if n % 2 == 0: return False
168 | return not any(n%p==0 for p in range(3,int(math.sqrt(n))+2))
169 | """, number=100000))
170 |
171 | def limit_of_performance():
172 | """We can see that testing a large prime is
173 | quite slow. Testing large non-primes is quite fast.
174 | """
175 | import time
176 |
177 | t = time.perf_counter()
178 | for i in range(30, 89):
179 | m = 2**i-1
180 | print(i, m, end="")
181 | if isprimeg(m):
182 | print("prime", end="")
183 | else:
184 | print("composite", end="")
185 | print(time.perf_counter() - t)
186 |
187 |
188 | __test__ = {
189 | 'higher_order':
190 | """Higher Order Functions.
191 |
192 | >>> year_cheese = [(2000, 29.87), (2001, 30.12), (2002, 30.6),
193 | ... (2003, 30.66), (2004, 31.33), (2005, 32.62), (2006, 32.73),
194 | ... (2007, 33.5), (2008, 32.84), (2009, 33.02), (2010, 32.92)
195 | ... ]
196 |
197 | >>> max( year_cheese )
198 | (2010, 32.92)
199 | >>> max( year_cheese, key= lambda yc: yc[1] )
200 | (2007, 33.5)
201 |
202 | Wrap-prcess-unwrap
203 | >>> max(map(lambda yc: (yc[1], yc), year_cheese))[1]
204 | (2007, 33.5)
205 |
206 | >>> wrapped = map(lambda yc: (yc[1], yc), year_cheese)
207 | >>> processed = max(wrapped)
208 | >>> processed[1]
209 | (2007, 33.5)
210 |
211 | >>> snd= lambda x: x[1]
212 | >>> snd(max(map(lambda yc: (yc[1],yc), year_cheese)))
213 | (2007, 33.5)
214 |
215 | """,
216 | 'sum_non_strict':
217 | """Non-strict generators.
218 |
219 | >>> sum_to(5)
220 | 10
221 | """,
222 | 'other tests':
223 | """
224 | >>> def example(a, b, **kw):
225 | ... return a*b
226 | ...
227 | >>> type(example)
228 |
229 | >>> example.__code__.co_varnames
230 | ('a', 'b', 'kw')
231 | >>> example.__code__.co_argcount
232 | 2
233 | >>> isprimei(131071)
234 | True
235 | >>> isprimer(131071)
236 | True
237 | >>> isprimeg(131071)
238 | True
239 | """
240 | }
241 |
242 | def test():
243 | import doctest
244 | doctest.testmod(verbose=2)
245 |
246 | if __name__ == "__main__":
247 | test()
248 | namedtuples()
249 | #recursion()
250 | #limit_of_performance()
251 |
--------------------------------------------------------------------------------
/Chapter03/ch03_ex6.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 3, Example Set 6
5 | """
6 | # pylint: disable=line-too-long,wrong-import-position,reimported
7 |
8 |
9 | # from collections import namedtuple
10 | # Color = namedtuple("Color", ("red", "green", "blue", "name"))
11 |
12 | from typing import NamedTuple
13 | class Color(NamedTuple):
14 | """An RGB color."""
15 | red: int
16 | green: int
17 | blue: int
18 | name: str
19 |
20 | example = '''GIMP Palette
21 | Name: Small
22 | Columns: 3
23 | #
24 | 0 0 0 Black
25 | 255 255 255 White
26 | 238 32 77 Red
27 | 28 172 120 Green
28 | 31 117 254 Blue
29 | '''
30 |
31 | import re
32 | from typing import TextIO, Tuple, Iterator, List
33 |
34 | def color_GPL_r(file_obj: TextIO) -> Iterator[Color]:
35 | """GPL Color Reader. Get body from the results of getting the header.
36 |
37 | Strictly recursive.
38 |
39 | >>> import io
40 | >>> data= io.StringIO("GIMP Palette\\nName: Crayola\\nColumns: 16\\n#\\n239 222 205 Almond\\n205 149 117 Antique Brass")
41 | >>> list( color_GPL_r(data))
42 | [Color(red=239, green=222, blue=205, name='Almond'), Color(red=205, green=149, blue=117, name='Antique Brass')]
43 | """
44 | header_pat = re.compile(r"GIMP Palette\nName:\s*(.*?)\nColumns:\s*(.*?)\n#\n", re.M)
45 | def read_head(file_obj: TextIO) -> Tuple[TextIO, str, str, str]:
46 | match = header_pat.match("".join(file_obj.readline() for _ in range(4)))
47 | return file_obj, match.group(1), match.group(2), file_obj.readline().rstrip()
48 |
49 | def read_tail(file_obj: TextIO, palette_name: str, columns: str, next_line: str) -> Iterator[Color]:
50 | if len(next_line) == 0: return
51 | r, g, b, *name = next_line.split()
52 | yield Color(int(r), int(g), int(b), " ".join(name))
53 | yield from read_tail(file_obj, palette_name, columns, file_obj.readline().rstrip())
54 |
55 | return read_tail(*read_head(file_obj))
56 |
57 | def row_iter_gpl(file_obj: TextIO) -> Tuple[str, str, Iterator[List[str]]]:
58 | """GPL Color Reader. Get body from the results of getting the header.
59 |
60 | Uses a higher-level function in the form of a generator expression. Somewhat simpler.
61 |
62 | >>> import io
63 | >>> data= io.StringIO("GIMP Palette\\nName: Crayola\\nColumns: 16\\n#\\n239 222 205 Almond\\n205 149 117 Antique Brass")
64 | >>> name, columns, colors= row_iter_gpl(data)
65 | >>> name
66 | 'Crayola'
67 | >>> list(colors)
68 | [['239', '222', '205', 'Almond'], ['205', '149', '117', 'Antique', 'Brass']]
69 |
70 | """
71 | header_pat = re.compile(r"GIMP Palette\nName:\s*(.*?)\nColumns:\s*(.*?)\n#\n", re.M)
72 | def read_head(file_obj: TextIO) -> Tuple[str, str, TextIO]:
73 | match = header_pat.match("".join(file_obj.readline() for _ in range(4)))
74 | return match.group(1), match.group(2), file_obj
75 |
76 | def read_tail(name: str, columns: str, file_obj: TextIO) -> Tuple[str, str, Iterator[List[str]]]:
77 | return name, columns, (next_line.split() for next_line in file_obj)
78 |
79 | return read_tail(*read_head(file_obj))
80 |
81 | def color_GPL_g(file_obj: TextIO) -> Iterator[Color]:
82 | """GPL Color Reader. Generator function version which leverages
83 | the lower-level row_iter_gpl() function.
84 |
85 | >>> import io
86 | >>> data= io.StringIO("GIMP Palette\\nName: Crayola\\nColumns: 16\\n#\\n239 222 205 Almond\\n205 149 117 Antique Brass")
87 | >>> list( color_GPL_g(data))
88 | [Color(red=239, green=222, blue=205, name='Almond'), Color(red=205, green=149, blue=117, name='Antique Brass')]
89 | """
90 | # pylint: disable=unused-variable
91 | name, columns, row_iter = row_iter_gpl(file_obj)
92 | return (
93 | Color(int(r), int(g), int(b), " ".join(name))
94 | for r, g, b, *name in row_iter
95 | )
96 |
97 | from typing import Iterator, Dict
98 | def load_colors(row_iter_gpl: Tuple[str, str, Iterator[List[str]]]) -> Dict[str, Color]:
99 | """Load colors from the ``crayola.gpl`` file, building a mapping.
100 |
101 | >>> import io
102 | >>> source= io.StringIO(example)
103 | >>> colors= load_colors(row_iter_gpl(source))
104 | >>> [colors[k] for k in sorted(colors)]
105 | [Color(red=0, green=0, blue=0, name='Black'), Color(red=31, green=117, blue=254, name='Blue'), Color(red=28, green=172, blue=120, name='Green'), Color(red=238, green=32, blue=77, name='Red'), Color(red=255, green=255, blue=255, name='White')]
106 | """
107 | # pylint: disable=unused-variable
108 | name, columns, row_iter = row_iter_gpl
109 | colors = tuple(
110 | Color(int(r), int(g), int(b), " ".join(name))
111 | for r, g, b, *name in row_iter
112 | )
113 | #print( colors )
114 | mapping = dict((c.name, c) for c in colors)
115 | #print( mapping )
116 | return mapping
117 |
118 | import bisect
119 | from collections import Mapping
120 | from typing import Iterable, Tuple, Any
121 |
122 | class StaticMapping(Mapping):
123 | """
124 | >>> import io
125 | >>> c= StaticMapping( (c.name, c) for c in color_GPL_r(io.StringIO(example)) )
126 | >>> c.get("Black")
127 | Color(red=0, green=0, blue=0, name='Black')
128 | """
129 | def __init__(self, iterable: Iterable[Tuple[Any, Any]]) -> None:
130 | self._data = tuple(iterable)
131 | self._keys = tuple(sorted(key for key, _ in self._data))
132 |
133 | def __getitem__(self, key):
134 | ix = bisect.bisect_left(self._keys, key)
135 | if ix != len(self._keys) and self._keys[ix] == key:
136 | return self._data[ix][1]
137 | raise ValueError("{0!r} not found".format(key))
138 | def __iter__(self):
139 | return iter(self._keys)
140 | def __len__(self):
141 | return len(self._keys)
142 |
143 | t1 = """
144 | >>> import io
145 | >>> tuple(color_GPL_r(io.StringIO(example)))
146 | (Color(red=0, green=0, blue=0, name='Black'), Color(red=255, green=255, blue=255, name='White'), Color(red=238, green=32, blue=77, name='Red'), Color(red=28, green=172, blue=120, name='Green'), Color(red=31, green=117, blue=254, name='Blue'))
147 | >>> tuple(color_GPL_g(io.StringIO(example)))
148 | (Color(red=0, green=0, blue=0, name='Black'), Color(red=255, green=255, blue=255, name='White'), Color(red=238, green=32, blue=77, name='Red'), Color(red=28, green=172, blue=120, name='Green'), Color(red=31, green=117, blue=254, name='Blue'))
149 | """
150 |
151 | t2 = """
152 | >>> from typing import Tuple, Callable
153 | >>> RGB = Tuple[int, int, int]
154 | >>> red: Callable[[RGB], int] = lambda color: color[0]
155 | >>> green: Callable[[RGB], int] = lambda color: color[1]
156 | >>> blue: Callable[[RGB], int] = lambda color: color[2]
157 | >>> almond = (239, 222, 205)
158 | >>> red(almond)
159 | 239
160 | """
161 |
162 | __test__ = {
163 | 't1': t1,
164 | 't2': t2
165 | }
166 | def test():
167 | import doctest
168 | doctest.testmod(verbose=1)
169 |
170 | if __name__ == "__main__":
171 | test()
172 |
--------------------------------------------------------------------------------
/Chapter06/ch06_ex5.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 6, Example Set 5
5 | """
6 | #pylint: disable=reimported,wrong-import-position
7 | from typing import Dict, Any, Iterable, Tuple, List, TypeVar
8 |
9 | Leg = Tuple[Any, Any, float]
10 | T_ = TypeVar("T_")
11 |
12 | def group_sort1(trip: Iterable[Leg]) -> Dict[int, int]:
13 | """Group legs into bins with distances 5 nm or less.
14 |
15 | >>> trip = [ ('s1', 'e1', 1), ('s4', 'e4', 4.9), ('s5', 'e5', 5), ('s6', 'e6', 6)]
16 | >>> group_sort1(trip)
17 | {0: 2, 5: 2}
18 | """
19 | def group(
20 | data: Iterable[T_]
21 | ) -> Iterable[Tuple[T_, int]]:
22 | previous, count = None, 0
23 | for d in sorted(data):
24 | if d == previous:
25 | count += 1
26 | elif previous is not None: # and d != previous
27 | yield previous, count
28 | previous, count = d, 1
29 | elif previous is None:
30 | previous, count = d, 1
31 | else:
32 | raise Exception("Bad bad design problem.")
33 | yield previous, count
34 | quantized = (int(5*(dist//5)) for start, stop, dist in trip)
35 | return dict(group(quantized))
36 |
37 | # return sorted(tuple(group(quantized)),
38 | # key=lambda x:x[1], reverse=True )
39 |
40 | def group_sort2(trip: Iterable[Leg]) -> Dict[int, int]:
41 | """Group legs into bins with distances 5 nm or less.
42 |
43 | >>> trip = [ ('s1', 'e1', 1), ('s4', 'e4', 4.9), ('s5', 'e5', 5), ('s6', 'e6', 6)]
44 | >>> group_sort2(trip)
45 | {0: 2, 5: 2}
46 | """
47 | def group(data: Iterable[T_]) -> Iterable[Tuple[T_, int]]:
48 | sorted_data = iter(sorted(data))
49 | previous, count = next(sorted_data), 1
50 | for d in sorted_data:
51 | if d == previous:
52 | count += 1
53 | elif previous is not None: # and d != previous
54 | yield previous, count
55 | previous, count = d, 1
56 | else:
57 | raise Exception("Bad bad design problem.")
58 | yield previous, count
59 | quantized = (int(5*(dist//5)) for start, stop, dist in trip)
60 | try:
61 | return dict(group(quantized))
62 | except StopIteration:
63 | return dict()
64 |
65 | # return sorted(tuple(group(quantized)),
66 | # key=lambda x:x[1], reverse=True )
67 |
68 | from collections import Counter
69 | def group_Counter(trip: Iterable[Leg]) -> List[Tuple[int, int]]:
70 | """Group legs into bins with distances 5 nm or less.
71 |
72 | >>> trip = [ ('s1', 'e1', 1), ('s4', 'e4', 4.9), ('s5', 'e5', 5), ('s6', 'e6', 6)]
73 | >>> group_Counter(trip)
74 | [(0, 2), (5, 2)]
75 | """
76 | quantized = (int(5*(dist//5)) for start, stop, dist in trip)
77 | return Counter(quantized).most_common()
78 |
79 | trip1 = """
80 | >>> import urllib.request
81 | >>> from Chapter04.ch04_ex1 import (
82 | ... float_from_pair, float_lat_lon, row_iter_kml, limits, haversine, legs
83 | ... )
84 | >>> with urllib.request.urlopen("file:./Winter%202012-2013.kml") as source:
85 | ... trip= tuple( (start, end, round(haversine(start, end),4))
86 | ... for start,end in legs( float_from_pair(float_lat_lon(row_iter_kml(source)))) )
87 | >>> start, end, dist = trip[0]
88 | >>> start, end, dist
89 | ((37.54901619777347, -76.33029518659048), (37.840832, -76.273834), 17.7246)
90 | >>> start, end, dist = trip[-1]
91 | >>> start, end, dist
92 | ((38.330166, -76.458504), (38.976334, -76.473503), 38.8019)
93 |
94 | >>> lat_iter = (lat1 for lat1, lon1 in (start for start,stop,dist in trip) )
95 | >>> north, south = limits( lat_iter )
96 | >>> dist_iter= (dist for start,stop,dist in trip)
97 | >>> total= sum( dist_iter )
98 | >>> average = total/len(trip)
99 |
100 | >>> print( "south", south )
101 | south 23.9555
102 | >>> print( "north", north )
103 | north 38.992832
104 | >>> print( "total", total )
105 | total 2481.3662
106 | >>> print( "average", round(average,3) )
107 | average 33.991
108 |
109 | >>> expected = {0.0: 4, 65.0: 1, 35.0: 5, 5.0: 5, 70.0: 2, 40.0: 3, 10.0: 5, 45.0: 3, 15.0: 9, 80.0: 1, 50.0: 3, 115.0: 1, 20.0: 5, 85.0: 1, 55.0: 1, 25.0: 5, 60.0: 3, 125.0: 1, 30.0: 15}
110 | >>> group_sort1(trip) == expected
111 | True
112 | >>> print( "Mode1", group_sort1(trip) )
113 | Mode1 {0: 4, 5: 5, 10: 5, 15: 9, 20: 5, 25: 5, 30: 15, 35: 5, 40: 3, 45: 3, 50: 3, 55: 1, 60: 3, 65: 1, 70: 2, 80: 1, 85: 1, 115: 1, 125: 1}
114 |
115 | >>> group_sort2(trip) == expected
116 | True
117 | >>> print( "Mode2", group_sort2(trip) )
118 | Mode2 {0: 4, 5: 5, 10: 5, 15: 9, 20: 5, 25: 5, 30: 15, 35: 5, 40: 3, 45: 3, 50: 3, 55: 1, 60: 3, 65: 1, 70: 2, 80: 1, 85: 1, 115: 1, 125: 1}
119 |
120 | >>> expected = [(30.0, 15), (15.0, 9), (35.0, 5), (5.0, 5), (10.0, 5), (20.0, 5), (25.0, 5), (0.0, 4), (40.0, 3), (45.0, 3), (50.0, 3), (60.0, 3), (70.0, 2), (65.0, 1), (80.0, 1), (115.0, 1), (85.0, 1), (55.0, 1), (125.0, 1)]
121 | >>> set(group_Counter(trip)) == set(expected)
122 | True
123 | >>> print( "Mode3", group_Counter(trip) )
124 | Mode3 [(30, 15), (15, 9), (5, 5), (35, 5), (20, 5), (10, 5), (25, 5), (0, 4), (50, 3), (60, 3), (45, 3), (40, 3), (70, 2), (80, 1), (85, 1), (65, 1), (115, 1), (125, 1), (55, 1)]
125 |
126 | """
127 |
128 | trip2 = """
129 | If we modify this demo so that path is an iterable, not a materialized tuple,
130 | we'll see that the ``limit()`` function doesn't really do what we hoped.
131 |
132 | >>> import urllib.request
133 | >>> from Chapter04.ch04_ex1 import (
134 | ... float_from_pair, float_lat_lon, row_iter_kml, limits, haversine, legs
135 | ... )
136 | >>> with urllib.request.urlopen("file:./Winter%202012-2013.kml") as source:
137 | ... path= tuple(float_from_pair(float_lat_lon(row_iter_kml(source))))
138 | >>> north, south = limits( path )
139 |
140 | >>> trip= tuple( (start, end, round(haversine(start, end),4))
141 | ... for start,end in legs(iter(path)) )
142 |
143 | >>> start, end, dist = trip[0]
144 | >>> start, end, dist
145 | ((37.54901619777347, -76.33029518659048), (37.840832, -76.273834), 17.7246)
146 | >>> start, end, dist = trip[-1]
147 | >>> start, end, dist
148 | ((38.330166, -76.458504), (38.976334, -76.473503), 38.8019)
149 |
150 | >>> dist_iter= (dist for start,stop,dist in trip)
151 | >>> total= sum( dist_iter )
152 | >>> average = total/len(trip)
153 |
154 | >>> print( "south", south )
155 | south (23.9555, -76.31633)
156 | >>> print( "north", north )
157 | north (38.992832, -76.451332)
158 | >>> print( "total", total )
159 | total 2481.3662
160 | >>> print( "average", round(average,3) )
161 | average 33.991
162 | """
163 |
164 | from typing import Callable, Iterable, Any
165 | def sum_f(function: Callable[[Any], float], data: Iterable) -> float:
166 | """
167 | >>> sum_f(lambda x: x//2, [2, 4, 6, 8, 10])
168 | 15
169 | """
170 | return sum(function(x) for x in data)
171 |
172 | __test__ = {
173 | 'trip1 demo': trip1,
174 | 'trip2 demo': trip2,
175 | }
176 |
177 | def test():
178 | import doctest
179 | doctest.testmod(verbose=True)
180 |
181 | if __name__ == "__main__":
182 | test()
183 |
--------------------------------------------------------------------------------
/Chapter04/ch04_ex2.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 4, Example Set 2
5 | Also used in Chapter 5.
6 | """
7 | # pylint: disable=line-too-long,wrong-import-position,reimported
8 |
9 | from typing import Iterable, Tuple, Any
10 |
11 | Wrapped = Tuple[Any, Tuple]
12 | def wrap(leg_iter: Iterable[Tuple]) -> Iterable[Wrapped]:
13 | return ((leg[2], leg) for leg in leg_iter)
14 |
15 | def unwrap(dist_leg: Tuple[Any, Any]) -> Any:
16 | # pylint: disable=unused-variable
17 | distance, leg = dist_leg
18 | return leg
19 |
20 | def by_dist(leg: Tuple[Any, Any, Any]) -> Any:
21 | # pylint: disable=unused-variable
22 | lat, lon, dist = leg
23 | return dist
24 |
25 | test_max_alternatives = """
26 | >>> from Chapter04.ch04_ex1 import (
27 | ... float_from_pair, float_lat_lon, row_iter_kml, limits, legs,
28 | ... haversine)
29 | >>> import urllib.request
30 | >>> with urllib.request.urlopen("file:./Winter%202012-2013.kml") as source:
31 | ... path= float_from_pair(float_lat_lon(row_iter_kml(source)))
32 | ... trip= tuple( (start, end, round(haversine(start, end),4))
33 | ... for start,end in legs(path))
34 |
35 | >>> long = max(dist for start, end, dist in trip)
36 | >>> short = min(dist for start, end, dist in trip)
37 | >>> long
38 | 129.7748
39 | >>> short
40 | 0.1731
41 |
42 | >>> long, short = unwrap( max( wrap( trip ) ) ), unwrap( min( wrap( trip ) ) )
43 | >>> long
44 | ((27.154167, -80.195663), (29.195168, -81.002998), 129.7748)
45 | >>> short
46 | ((35.505665, -76.653664), (35.508335, -76.654999), 0.1731)
47 |
48 | >>> long, short = max(trip, key=by_dist), min(trip, key=by_dist)
49 | >>> long
50 | ((27.154167, -80.195663), (29.195168, -81.002998), 129.7748)
51 | >>> short
52 | ((35.505665, -76.653664), (35.508335, -76.654999), 0.1731)
53 | """
54 |
55 | from typing import Iterable, Any, Callable
56 | def max_like(trip: Iterable[Any], key: Callable=lambda x: x) -> Any:
57 | """
58 | >>> max_like([1, 3, 2])
59 | 3
60 | """
61 | wrapped = ((key(leg), leg) for leg in trip)
62 | return sorted(wrapped)[-1][1]
63 |
64 |
65 | start = lambda x: x[0]
66 | end = lambda x: x[1]
67 | dist = lambda x: x[2]
68 |
69 | lat = lambda x: x[0]
70 | lon = lambda x: x[1]
71 |
72 | test_min_max = """
73 | >>> from Chapter04.ch04_ex1 import (
74 | ... float_from_pair, float_lat_lon, row_iter_kml, limits, legs,
75 | ... haversine)
76 | >>> import urllib.request
77 | >>> with urllib.request.urlopen("file:./Winter%202012-2013.kml") as source:
78 | ... path= float_from_pair(float_lat_lon(row_iter_kml(source)))
79 | ... trip= tuple( (start, end, round(haversine(start, end),4))
80 | ... for start,end in legs(path))
81 |
82 | >>> long, short = max(trip, key=dist), min(trip, key=dist)
83 | >>> long
84 | ((27.154167, -80.195663), (29.195168, -81.002998), 129.7748)
85 | >>> short
86 | ((35.505665, -76.653664), (35.508335, -76.654999), 0.1731)
87 |
88 | >>> north = min( trip, key=lambda x: lat(start(x)) )
89 | >>> north
90 | ((23.9555, -76.31633), (24.099667, -76.401833), 9.844)
91 |
92 | """
93 |
94 | test_conversion = """
95 | >>> from Chapter04.ch04_ex1 import (
96 | ... float_from_pair, float_lat_lon, row_iter_kml, limits, legs,
97 | ... haversine)
98 | >>> import urllib.request
99 | >>> with urllib.request.urlopen("file:./Winter%202012-2013.kml") as source:
100 | ... path= float_from_pair(float_lat_lon(row_iter_kml(source)))
101 | ... trip= tuple( (start, end, round(haversine(start, end),4))
102 | ... for start,end in legs(path))
103 |
104 | >>> statute1 = list( (start(x),end(x),dist(x)*6076.12/5280) for x in trip )
105 | >>> statute2 = list( map( lambda x: (start(x),end(x),dist(x)*6076.12/5280), trip ) )
106 | >>> statute3 = list( (b, e, d*6076.12/5280) for b, e, d in trip )
107 |
108 | >>> assert statute1 == statute2
109 | >>> assert statute1 == statute3
110 |
111 | >>> statute1[0]
112 | ((37.54901619777347, -76.33029518659048), (37.840832, -76.273834), 20.397120559090908)
113 |
114 | >>> statute1[-1]
115 | ((38.330166, -76.458504), (38.976334, -76.473503), 44.652462240151515)
116 |
117 | """
118 |
119 | test_filter_sorted = """
120 | >>> from Chapter04.ch04_ex1 import (
121 | ... float_from_pair, float_lat_lon, row_iter_kml, limits, legs,
122 | ... haversine)
123 | >>> import urllib.request
124 | >>> with urllib.request.urlopen("file:./Winter%202012-2013.kml") as source:
125 | ... path= float_from_pair(float_lat_lon(row_iter_kml(source)))
126 | ... trip= tuple( (start, end, round(haversine(start, end),4))
127 | ... for start,end in legs(path))
128 |
129 | >>> long= list(filter( lambda leg: dist(leg) >= 50, trip ))
130 | >>> len(long)
131 | 14
132 | >>> long[0]
133 | ((34.204666, -77.800499), (33.276833, -78.979332), 81.0363)
134 | >>> long[-1]
135 | ((31.9105, -80.780998), (32.83248254681784, -79.93379468285697), 70.0694)
136 |
137 | >>> s1= sorted( dist(x) for x in trip)
138 | >>> s1[0]
139 | 0.1731
140 | >>> s1[-1]
141 | 129.7748
142 |
143 | >>> s2=( sorted( trip, key=dist ) )
144 | >>> s2[0]
145 | ((35.505665, -76.653664), (35.508335, -76.654999), 0.1731)
146 | >>> s2[-1]
147 | ((27.154167, -80.195663), (29.195168, -81.002998), 129.7748)
148 |
149 | >>> from Chapter04.ch04_ex4 import mean, stdev, z
150 |
151 | >>> dist_data = list(map(dist, trip))
152 | >>> μ_d = mean(dist_data)
153 | >>> σ_d = stdev(dist_data)
154 | >>> print( "Average leg", μ_d, "with σ_d of", σ_d, "Z(0)=", z(0,μ_d,σ_d) )
155 | Average leg 33.99131780821918 with σ_d of 24.158473730346035 Z(0)= -1.407014291864054
156 |
157 | >>> outlier = lambda leg: abs(z(dist(leg),μ_d,σ_d)) > 3
158 | >>> print( "Outliers", list( filter( outlier, trip ) ) )
159 | Outliers [((29.050501, -80.651169), (27.186001, -80.139503), 115.1751), ((27.154167, -80.195663), (29.195168, -81.002998), 129.7748)]
160 | """
161 |
162 | def performance():
163 | print(
164 | "map",
165 | timeit.timeit(
166 | """list(map(int,data))""",
167 | """data = ['2', '3', '5', '7', '11', '13', '17', '19', '23', '29', '31', '37', '41', '43', '47', '53', '59', '61', '67', '71', '73', '79', '83', '89', '97', '101', '103', '107', '109', '113', '127', '131', '137', '139', '149', '151', '157', '163', '167', '173', '179', '181', '191', '193', '197', '199', '211', '223', '227', '229']"""
168 | )
169 | )
170 | print(
171 | "expr",
172 | timeit.timeit(
173 | """list(int(v) for v in data)""",
174 | """data = ['2', '3', '5', '7', '11', '13', '17', '19', '23', '29', '31', '37', '41', '43', '47', '53', '59', '61', '67', '71', '73', '79', '83', '89', '97', '101', '103', '107', '109', '113', '127', '131', '137', '139', '149', '151', '157', '163', '167', '173', '179', '181', '191', '193', '197', '199', '211', '223', '227', '229']"""
175 | )
176 | )
177 |
178 |
179 | __test__ = {
180 | "test_max_alternatives": test_max_alternatives,
181 | "test_min_max": test_min_max,
182 | "test_conversion": test_conversion,
183 | "test_filter_sorted": test_filter_sorted,
184 | }
185 |
186 | def test():
187 | import doctest
188 | doctest.testmod(verbose=1)
189 |
190 | if __name__ == "__main__":
191 | # import timeit
192 | # performance()
193 | test()
194 |
--------------------------------------------------------------------------------
/Chapter13/ch13_ex1.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 13, Example Set 1
5 | """
6 | # pylint: disable=unused-wildcard-import,wrong-import-position,unused-import
7 |
8 | from typing import Iterable
9 | from functools import reduce
10 | def prod(data: Iterable[int]) -> int:
11 | """
12 | >>> prod((1,2,3))
13 | 6
14 | """
15 | return reduce(lambda x, y: x*y, data, 1)
16 |
17 | year_cheese = [
18 | (2000, 29.87), (2001, 30.12), (2002, 30.6), (2003, 30.66),
19 | (2004, 31.33), (2005, 32.62), (2006, 32.73), (2007, 33.5),
20 | (2008, 32.84), (2009, 33.02), (2010, 32.92)
21 | ]
22 |
23 | from typing import Callable, Sequence, TypeVar
24 | T_ = TypeVar("T_")
25 | fst: Callable[[Sequence[T_]], T_] = lambda x: x[0]
26 | snd: Callable[[Sequence[T_]], T_] = lambda x: x[1]
27 |
28 | x = min(year_cheese, key=snd)
29 |
30 |
31 | test_itemgetter = """
32 | >>> from operator import itemgetter
33 | >>> itemgetter(0)([1, 2, 3])
34 | 1
35 | >>> min(year_cheese, key=snd)
36 | (2000, 29.87)
37 | >>> max(year_cheese, key=itemgetter(1))
38 | (2007, 33.5)
39 | """
40 |
41 | # from collections import namedtuple
42 | # YearCheese = namedtuple( "YearCheese", ("year", "cheese") )'
43 |
44 | from typing import NamedTuple
45 | class YearCheese(NamedTuple):
46 | year: int
47 | cheese: float
48 |
49 | year_cheese_2 = list(YearCheese(*yc) for yc in year_cheese)
50 |
51 | test_year_cheese_2 = """
52 | >>> year_cheese_2 # doctest: +NORMALIZE_WHITESPACE
53 | [YearCheese(year=2000, cheese=29.87), YearCheese(year=2001, cheese=30.12),
54 | YearCheese(year=2002, cheese=30.6), YearCheese(year=2003, cheese=30.66),
55 | YearCheese(year=2004, cheese=31.33), YearCheese(year=2005, cheese=32.62),
56 | YearCheese(year=2006, cheese=32.73), YearCheese(year=2007, cheese=33.5),
57 | YearCheese(year=2008, cheese=32.84), YearCheese(year=2009, cheese=33.02),
58 | YearCheese(year=2010, cheese=32.92)]
59 | """
60 |
61 | test_attrgetter = """
62 | >>> from operator import attrgetter
63 | >>> min( year_cheese_2, key=attrgetter('cheese') )
64 | YearCheese(year=2000, cheese=29.87)
65 | >>> max( year_cheese_2, key=lambda x: x.cheese )
66 | YearCheese(year=2007, cheese=33.5)
67 | """
68 |
69 | g_f = [
70 | 1, 1/12, 1/288, -139/51840, -571/2488320, 163879/209018880,
71 | 5246819/75246796800
72 | ]
73 |
74 | g = [
75 | (1, 1), (1, 12), (1, 288), (-139, 51840),
76 | (-571, 2488320), (163879, 209018880),
77 | (5246819, 75246796800)
78 | ]
79 |
80 | from itertools import starmap
81 |
82 | from fractions import Fraction
83 | test_starmap1 = """
84 | >>> from operator import truediv
85 | >>> round( sum( starmap( truediv, g ) ), 6 )
86 | 1.084749
87 | >>> round( sum( g_f ), 6 )
88 | 1.084749
89 | >>> f= sum( Fraction(*x) for x in g )
90 | >>> f
91 | Fraction(81623851739, 75246796800)
92 | >>> round( float(f), 6 )
93 | 1.084749
94 | """
95 |
96 | from itertools import zip_longest
97 |
98 | test_starmap2 = """
99 | >>> from operator import truediv
100 | >>> p = (3, 8, 29, 44)
101 | >>> d = starmap( pow, zip_longest([], range(4), fillvalue=60) )
102 | >>> pi = sum( starmap( truediv, zip( p, d ) ) )
103 | >>> pi
104 | 3.1415925925925925
105 | >>> d = starmap( pow, zip_longest([], range(4), fillvalue=60) )
106 | >>> pi = sum( map( truediv, p, d ) )
107 | >>> pi
108 | 3.1415925925925925
109 | """
110 |
111 | def fact(n: int) -> int:
112 | """
113 | >>> fact(0)
114 | 1
115 | >>> fact(1)
116 | 1
117 | >>> fact(2)
118 | 2
119 | >>> fact(3)
120 | 6
121 | >>> fact(4)
122 | 24
123 | """
124 | f = {
125 | n == 0: lambda n: 1,
126 | n == 1: lambda n: 1,
127 | n == 2: lambda n: 2,
128 | n > 2: lambda n: fact(n-1)*n
129 | }[True]
130 | return f(n)
131 |
132 | from typing import Callable, Tuple, List
133 |
134 | from operator import itemgetter
135 | def semifact(n: int) -> int:
136 | """
137 | >>> semifact(0)
138 | 1
139 | >>> semifact(1)
140 | 1
141 | >>> semifact(2)
142 | 2
143 | >>> semifact(3)
144 | 3
145 | >>> semifact(4)
146 | 8
147 | >>> semifact(5)
148 | 15
149 | >>> semifact(9)
150 | 945
151 | """
152 | alternatives: List[Tuple[bool, Callable[[int], int]]] = [
153 | (n == 0, lambda n: 1),
154 | (n == 1, lambda n: 1),
155 | (n == 2, lambda n: 2),
156 | (n > 2, lambda n: semifact(n-2)*n)
157 | ]
158 | _, f = next(filter(itemgetter(0), alternatives))
159 | return f(n)
160 |
161 | def semifact2(n: int) -> int:
162 | """
163 | >>> semifact2(9)
164 | 945
165 | """
166 | alternatives = [
167 | (lambda n: 1) if n == 0 else None,
168 | (lambda n: 1) if n == 1 else None,
169 | (lambda n: 2) if n == 2 else None,
170 | (lambda n: semifact2(n-2)*n) if n > 2 else None
171 | ]
172 | f = next(filter(None, alternatives))
173 | return f(n)
174 |
175 | # Here's a "stub" definition for a class that includes
176 | # the minimal feature set for comparison.
177 | # These are often in a module in the `stubs` directory.
178 |
179 | from abc import ABCMeta, abstractmethod
180 | from typing import TypeVar, Any
181 |
182 | # pylint: disable=pointless-statement,multiple-statements
183 | class Rankable(metaclass=ABCMeta):
184 | @abstractmethod
185 | def __lt__(self, other: Any) -> bool: ...
186 | @abstractmethod
187 | def __gt__(self, other: Any) -> bool: ...
188 | @abstractmethod
189 | def __le__(self, other: Any) -> bool: ...
190 | @abstractmethod
191 | def __ge__(self, other: Any) -> bool: ...
192 |
193 | RT = TypeVar('RT', bound=Rankable)
194 |
195 | def non_strict_max(a: RT, b: RT) -> RT:
196 | """
197 | >>> non_strict_max( 2, 2 )
198 | 2
199 | >>> non_strict_max( 3, 5 )
200 | 5
201 | >>> non_strict_max( 11, 7 )
202 | 11
203 | """
204 | f = {a >= b: lambda: a, b >= a: lambda: b}[True]
205 | return f()
206 |
207 | test_starmap3 = """
208 | >>> from itertools import count, takewhile
209 | >>> from operator import truediv
210 | >>> num = map(fact, count())
211 | >>> den = map(semifact, (2*n+1 for n in count()))
212 | >>> terms = takewhile(
213 | ... lambda t: t > 1E-15, map(truediv, num, den))
214 | >>> round( float(2*sum(terms)), 8 )
215 | 3.14159265
216 | """
217 |
218 | test_reduction = """
219 | >>> import functools, operator
220 | >>> sum= functools.partial( functools.reduce, operator.add )
221 | >>> sum([1,2,3])
222 | 6
223 | >>> prod = functools.partial( functools.reduce, operator.mul )
224 | >>> prod( [1,2,3,4] )
225 | 24
226 | >>> fact = lambda n: 1 if n < 2 else n*prod( range(1,n) )
227 | >>> fact(4)
228 | 24
229 | >>> fact(0)
230 | 1
231 | >>> fact(1)
232 | 1
233 | """
234 |
235 | test_unordered = """
236 | >>> {'a': 1, 'a': 2}
237 | {'a': 2}
238 | """
239 |
240 | __test__ = {
241 | "test_itemgetter": test_itemgetter,
242 | "test_attrgetter": test_attrgetter,
243 | "test_year_cheese_2": test_year_cheese_2,
244 | "test_starmap1": test_starmap1,
245 | "test_starmap2": test_starmap2,
246 | "test_starmap3": test_starmap3,
247 | "test_reduction": test_reduction,
248 | "test_unordered": test_unordered,
249 | }
250 |
251 | def test():
252 | import doctest
253 | doctest.testmod(verbose=1)
254 |
255 | if __name__ == "__main__":
256 | test()
257 |
--------------------------------------------------------------------------------
/Chapter15/ch15_ex3.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 15, Example Set 3
5 | """
6 |
7 | from wsgiref.simple_server import make_server, demo_app
8 | import wsgiref.util
9 | import urllib
10 | import urllib.parse
11 | from pathlib import Path
12 | import sys
13 |
14 | from typing import (
15 | Dict, Callable, List, Tuple, Iterator, Union, Optional
16 | )
17 | from mypy_extensions import DefaultArg
18 |
19 | # Requires mypy_extensions to properly declare the start_response callback.
20 | SR_Func = Callable[[str, List[Tuple[str, str]], DefaultArg(Tuple)], None]
21 |
22 | TEST_TEMPLATE = """
23 | Run Tests
24 |
25 | Tests
26 | Results
27 | {0}
28 |
29 |
33 |
34 | """
35 |
36 | def test_app(
37 | environ: Dict,
38 | start_response: SR_Func
39 | ) -> Union[Iterator[bytes], List[bytes]]:
40 | """Runs the unit test suite."""
41 | if environ['REQUEST_METHOD'] == "GET":
42 | # send form and previous results (if any)
43 | if environ['QUERY_STRING']:
44 | query = urllib.parse.parse_qs(environ['QUERY_STRING'])
45 | file_path = Path(environ['TMPDIR']) / query['filename'][0]
46 | with file_path.open() as result_file:
47 | results = result_file.read()
48 | else:
49 | results = ""
50 | page = TEST_TEMPLATE.format(results)
51 | content = page.encode("utf-8")
52 | headers = [
53 | ("Content-Type", 'text/html; charset="utf-8"'),
54 | ("Content-Length", str(len(content))),
55 | ]
56 | start_response('200 OK', headers)
57 | return [content]
58 | elif environ['REQUEST_METHOD'] == "POST":
59 | # Run tests, collect data in a cache file
60 | import test_all
61 | file_path = Path(environ['TMPDIR']) / "results"
62 | file_list = sorted(
63 | Path.cwd().glob("Chapter_*"),
64 | key=lambda p: test_all.chap_key(p.name))
65 | with file_path.open("w") as result_file:
66 | sys.stderr = result_file
67 | local_names = [
68 | str(item.relative_to(Path.cwd())) for item in file_list
69 | ]
70 | test_all.master_test_suite(
71 | test_all.package_module_iter(*local_names))
72 | sys.stderr = sys.__stderr__
73 | # Might want to compute a distinct filename each time
74 | filename = {"filename": "results"}
75 | encoded_filename = urllib.parse.urlencode(filename)
76 | headers = [
77 | ("Location", "/test?{0}".format(encoded_filename))
78 | ]
79 | start_response("302 FOUND", headers)
80 | return []
81 | start_response("400 NOT ALLOWED", [])
82 | return []
83 |
84 | INDEX_TEMPLATE_HEAD = """
85 | Chapter 15
86 | Files in {0}
87 | """
88 |
89 | INDEX_TEMPLATE_FOOT = """
90 |
91 | """
92 |
93 | def index_app(
94 | environ: Dict,
95 | start_response: SR_Func
96 | ) -> Union[Iterator[bytes], List[bytes]]:
97 | """Displays an index of available files."""
98 | log = environ['wsgi.errors']
99 | print("PATH_INFO '{0}'".format(environ['PATH_INFO']), file=log)
100 | page = INDEX_TEMPLATE_HEAD.format(environ.get('PATH_INFO', '.'))
101 | for entry in (Path.cwd()/environ['PATH_INFO'][1:]).glob('*'):
102 | if entry.name.startswith('.'): continue
103 | rel_path = entry.relative_to(Path.cwd())
104 | page += '{1}
'.format(rel_path, entry.name)
105 | page += INDEX_TEMPLATE_FOOT
106 | content = page.encode("utf-8")
107 | headers = [
108 | ("Content-Type", 'text/html; charset="utf-8"'),
109 | ("Content-Length", str(len(content))),
110 | ]
111 | start_response('200 OK', headers)
112 | return [content]
113 |
114 | def static_app(
115 | environ: Dict,
116 | start_response: SR_Func
117 | ) -> Union[Iterator[bytes], List[bytes]]:
118 | """Displays a single, static file."""
119 | log = environ['wsgi.errors']
120 | try:
121 | print(f"CWD={Path.cwd()}", file=log)
122 | static_path = Path.cwd()/environ['PATH_INFO'][1:]
123 | with static_path.open() as static_file:
124 | content = static_file.read().encode("utf-8")
125 | headers = [
126 | ("Content-Type", 'text/plain; charset="utf-8"'),
127 | ("Content-Length", str(len(content))),
128 | ]
129 | start_response('200 OK', headers)
130 | return [content]
131 | except IsADirectoryError as e:
132 | return index_app(environ, start_response)
133 | except FileNotFoundError as e:
134 | start_response('404 NOT FOUND', [])
135 | return [f"Not Found {static_path}\n{e!r}".encode("utf-8")]
136 |
137 | WELCOME_TEMPLATE = """
138 |
139 |
140 |
141 |
142 | Chapter 15
143 |
144 |
145 |
146 |
153 |
154 |
155 |
156 | """
157 |
158 | def welcome_app(
159 | environ: Dict,
160 | start_response: SR_Func
161 | ) -> Union[Iterator[bytes], List[bytes]]:
162 | """Displays a page of greeting information."""
163 | content = WELCOME_TEMPLATE.encode("utf-8")
164 | headers = [
165 | ("Content-Type", "text/html; charset=utf-8"),
166 | ("Content-Length", str(len(content))),
167 | ]
168 | start_response('200 OK', headers)
169 | return [content]
170 |
171 | SCRIPT_MAP = {
172 | "demo": demo_app,
173 | "static": static_app,
174 | "test": test_app,
175 | "": welcome_app,
176 | }
177 |
178 | def routing(
179 | environ: Dict,
180 | start_response: SR_Func
181 | ) -> Union[Iterator[bytes], List[bytes]]:
182 | """Routing among the apps using the script information."""
183 | top_level = wsgiref.util.shift_path_info(environ)
184 | app = SCRIPT_MAP.get(top_level, SCRIPT_MAP[''])
185 | content = app(environ, start_response)
186 | return content
187 |
188 | def server_demo():
189 | httpd = make_server('', 8080, routing)
190 | print("Serving HTTP on port 8080...")
191 |
192 | # Respond to requests until process is killed
193 | httpd.serve_forever()
194 |
195 | def test():
196 | import doctest
197 | doctest.testmod(verbose=1)
198 |
199 | if __name__ == "__main__":
200 | test()
201 | server_demo()
202 |
--------------------------------------------------------------------------------
/1000.txt:
--------------------------------------------------------------------------------
1 | The First 1,000 Primes
2 | (the 1,000th is 7919)
3 | For more information on primes see http://primes.utm.edu/
4 |
5 | 2 3 5 7 11 13 17 19 23 29
6 | 31 37 41 43 47 53 59 61 67 71
7 | 73 79 83 89 97 101 103 107 109 113
8 | 127 131 137 139 149 151 157 163 167 173
9 | 179 181 191 193 197 199 211 223 227 229
10 | 233 239 241 251 257 263 269 271 277 281
11 | 283 293 307 311 313 317 331 337 347 349
12 | 353 359 367 373 379 383 389 397 401 409
13 | 419 421 431 433 439 443 449 457 461 463
14 | 467 479 487 491 499 503 509 521 523 541
15 | 547 557 563 569 571 577 587 593 599 601
16 | 607 613 617 619 631 641 643 647 653 659
17 | 661 673 677 683 691 701 709 719 727 733
18 | 739 743 751 757 761 769 773 787 797 809
19 | 811 821 823 827 829 839 853 857 859 863
20 | 877 881 883 887 907 911 919 929 937 941
21 | 947 953 967 971 977 983 991 997 1009 1013
22 | 1019 1021 1031 1033 1039 1049 1051 1061 1063 1069
23 | 1087 1091 1093 1097 1103 1109 1117 1123 1129 1151
24 | 1153 1163 1171 1181 1187 1193 1201 1213 1217 1223
25 | 1229 1231 1237 1249 1259 1277 1279 1283 1289 1291
26 | 1297 1301 1303 1307 1319 1321 1327 1361 1367 1373
27 | 1381 1399 1409 1423 1427 1429 1433 1439 1447 1451
28 | 1453 1459 1471 1481 1483 1487 1489 1493 1499 1511
29 | 1523 1531 1543 1549 1553 1559 1567 1571 1579 1583
30 | 1597 1601 1607 1609 1613 1619 1621 1627 1637 1657
31 | 1663 1667 1669 1693 1697 1699 1709 1721 1723 1733
32 | 1741 1747 1753 1759 1777 1783 1787 1789 1801 1811
33 | 1823 1831 1847 1861 1867 1871 1873 1877 1879 1889
34 | 1901 1907 1913 1931 1933 1949 1951 1973 1979 1987
35 | 1993 1997 1999 2003 2011 2017 2027 2029 2039 2053
36 | 2063 2069 2081 2083 2087 2089 2099 2111 2113 2129
37 | 2131 2137 2141 2143 2153 2161 2179 2203 2207 2213
38 | 2221 2237 2239 2243 2251 2267 2269 2273 2281 2287
39 | 2293 2297 2309 2311 2333 2339 2341 2347 2351 2357
40 | 2371 2377 2381 2383 2389 2393 2399 2411 2417 2423
41 | 2437 2441 2447 2459 2467 2473 2477 2503 2521 2531
42 | 2539 2543 2549 2551 2557 2579 2591 2593 2609 2617
43 | 2621 2633 2647 2657 2659 2663 2671 2677 2683 2687
44 | 2689 2693 2699 2707 2711 2713 2719 2729 2731 2741
45 | 2749 2753 2767 2777 2789 2791 2797 2801 2803 2819
46 | 2833 2837 2843 2851 2857 2861 2879 2887 2897 2903
47 | 2909 2917 2927 2939 2953 2957 2963 2969 2971 2999
48 | 3001 3011 3019 3023 3037 3041 3049 3061 3067 3079
49 | 3083 3089 3109 3119 3121 3137 3163 3167 3169 3181
50 | 3187 3191 3203 3209 3217 3221 3229 3251 3253 3257
51 | 3259 3271 3299 3301 3307 3313 3319 3323 3329 3331
52 | 3343 3347 3359 3361 3371 3373 3389 3391 3407 3413
53 | 3433 3449 3457 3461 3463 3467 3469 3491 3499 3511
54 | 3517 3527 3529 3533 3539 3541 3547 3557 3559 3571
55 | 3581 3583 3593 3607 3613 3617 3623 3631 3637 3643
56 | 3659 3671 3673 3677 3691 3697 3701 3709 3719 3727
57 | 3733 3739 3761 3767 3769 3779 3793 3797 3803 3821
58 | 3823 3833 3847 3851 3853 3863 3877 3881 3889 3907
59 | 3911 3917 3919 3923 3929 3931 3943 3947 3967 3989
60 | 4001 4003 4007 4013 4019 4021 4027 4049 4051 4057
61 | 4073 4079 4091 4093 4099 4111 4127 4129 4133 4139
62 | 4153 4157 4159 4177 4201 4211 4217 4219 4229 4231
63 | 4241 4243 4253 4259 4261 4271 4273 4283 4289 4297
64 | 4327 4337 4339 4349 4357 4363 4373 4391 4397 4409
65 | 4421 4423 4441 4447 4451 4457 4463 4481 4483 4493
66 | 4507 4513 4517 4519 4523 4547 4549 4561 4567 4583
67 | 4591 4597 4603 4621 4637 4639 4643 4649 4651 4657
68 | 4663 4673 4679 4691 4703 4721 4723 4729 4733 4751
69 | 4759 4783 4787 4789 4793 4799 4801 4813 4817 4831
70 | 4861 4871 4877 4889 4903 4909 4919 4931 4933 4937
71 | 4943 4951 4957 4967 4969 4973 4987 4993 4999 5003
72 | 5009 5011 5021 5023 5039 5051 5059 5077 5081 5087
73 | 5099 5101 5107 5113 5119 5147 5153 5167 5171 5179
74 | 5189 5197 5209 5227 5231 5233 5237 5261 5273 5279
75 | 5281 5297 5303 5309 5323 5333 5347 5351 5381 5387
76 | 5393 5399 5407 5413 5417 5419 5431 5437 5441 5443
77 | 5449 5471 5477 5479 5483 5501 5503 5507 5519 5521
78 | 5527 5531 5557 5563 5569 5573 5581 5591 5623 5639
79 | 5641 5647 5651 5653 5657 5659 5669 5683 5689 5693
80 | 5701 5711 5717 5737 5741 5743 5749 5779 5783 5791
81 | 5801 5807 5813 5821 5827 5839 5843 5849 5851 5857
82 | 5861 5867 5869 5879 5881 5897 5903 5923 5927 5939
83 | 5953 5981 5987 6007 6011 6029 6037 6043 6047 6053
84 | 6067 6073 6079 6089 6091 6101 6113 6121 6131 6133
85 | 6143 6151 6163 6173 6197 6199 6203 6211 6217 6221
86 | 6229 6247 6257 6263 6269 6271 6277 6287 6299 6301
87 | 6311 6317 6323 6329 6337 6343 6353 6359 6361 6367
88 | 6373 6379 6389 6397 6421 6427 6449 6451 6469 6473
89 | 6481 6491 6521 6529 6547 6551 6553 6563 6569 6571
90 | 6577 6581 6599 6607 6619 6637 6653 6659 6661 6673
91 | 6679 6689 6691 6701 6703 6709 6719 6733 6737 6761
92 | 6763 6779 6781 6791 6793 6803 6823 6827 6829 6833
93 | 6841 6857 6863 6869 6871 6883 6899 6907 6911 6917
94 | 6947 6949 6959 6961 6967 6971 6977 6983 6991 6997
95 | 7001 7013 7019 7027 7039 7043 7057 7069 7079 7103
96 | 7109 7121 7127 7129 7151 7159 7177 7187 7193 7207
97 | 7211 7213 7219 7229 7237 7243 7247 7253 7283 7297
98 | 7307 7309 7321 7331 7333 7349 7351 7369 7393 7411
99 | 7417 7433 7451 7457 7459 7477 7481 7487 7489 7499
100 | 7507 7517 7523 7529 7537 7541 7547 7549 7559 7561
101 | 7573 7577 7583 7589 7591 7603 7607 7621 7639 7643
102 | 7649 7669 7673 7681 7687 7691 7699 7703 7717 7723
103 | 7727 7741 7753 7757 7759 7789 7793 7817 7823 7829
104 | 7841 7853 7867 7873 7877 7879 7883 7901 7907 7919
105 | end.
106 |
--------------------------------------------------------------------------------
/Chapter05/ch05_ex1.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 5, Example Set 1
5 | """
6 | # pylint: disable=reimported,wrong-import-position
7 |
8 | from typing import Callable, Iterable, Tuple, Iterator, Any
9 | Conv_F = Callable[[float], float]
10 | Leg = Tuple[Any, Any, float]
11 | def convert(conversion: Conv_F, trip: Iterable[Leg]) -> Iterator[float]:
12 | return (conversion(distance) for start, end, distance in trip)
13 |
14 | to_miles = lambda nm: nm*6076.12/5280
15 | to_km = lambda nm: nm*1.852
16 | to_nm = lambda nm: nm
17 |
18 | fst = lambda x: x[0]
19 | snd = lambda x: x[1]
20 | sel2 = lambda x: x[2]
21 |
22 | to_miles_sel2 = lambda s_e_d: to_miles(sel2(s_e_d))
23 |
24 | test_convert = """
25 | >>> from Chapter04.ch04_ex1 import (
26 | ... float_from_pair, float_lat_lon, row_iter_kml, limits, haversine, legs
27 | ... )
28 | >>> import urllib.request
29 | >>> data = "file:./Winter%202012-2013.kml"
30 | >>> with urllib.request.urlopen(data) as source:
31 | ... path= float_from_pair(float_lat_lon(row_iter_kml(source)))
32 | ... trip= tuple( (start, end, round(haversine(start, end), 4))
33 | ... for start, end in legs(path))
34 |
35 | >>> trip[0]
36 | ((37.54901619777347, -76.33029518659048), (37.840832, -76.273834), 17.7246)
37 | >>> trip[-1]
38 | ((38.330166, -76.458504), (38.976334, -76.473503), 38.8019)
39 |
40 | >>> miles= list( convert( to_miles, trip ) )
41 | >>> miles[0]
42 | 20.397120559090908
43 | >>> miles[-1]
44 | 44.652462240151515
45 |
46 | >>> miles2 = list( to_miles_sel2(s_e_d) for s_e_d in trip )
47 | >>> miles2[0]
48 | 20.397120559090908
49 | >>> miles2[-1]
50 | 44.652462240151515
51 |
52 | >>> assert miles == miles2
53 | """
54 |
55 | from typing import Callable, Iterable, Tuple, Iterator, Any
56 | Point = Tuple[float, float]
57 | Leg_Raw = Tuple[Point, Point]
58 | Point_Func = Callable[[Point, Point], float]
59 | Leg_D = Tuple[Point, Point, float]
60 |
61 | def cons_distance(
62 | distance: Point_Func,
63 | legs_iter: Iterable[Leg_Raw]
64 | ) -> Iterator[Leg_D]:
65 | return (
66 | (start, end, round(distance(start, end), 4))
67 | for start, end in legs_iter
68 | )
69 |
70 | test_cons_distance = """
71 | >>> from Chapter04.ch04_ex1 import (
72 | ... float_from_pair, float_lat_lon, row_iter_kml, limits, haversine, legs
73 | ... )
74 | >>> import urllib.request
75 | >>> with urllib.request.urlopen("file:./Winter%202012-2013.kml") as source:
76 | ... path= float_from_pair(float_lat_lon(row_iter_kml(source)))
77 | ... trip2= tuple( cons_distance( haversine, legs(iter(path)) ) )
78 |
79 | >>> trip2[0]
80 | ((37.54901619777347, -76.33029518659048), (37.840832, -76.273834), 17.7246)
81 | >>> trip2[-1]
82 | ((38.330166, -76.458504), (38.976334, -76.473503), 38.8019)
83 |
84 | """
85 |
86 | from typing import Callable, Iterable, Tuple, Iterator, Any
87 | Point = Tuple[float, float]
88 | Leg_Raw = Tuple[Point, Point]
89 | Point_Func = Callable[[Point, Point], float]
90 | Leg_P_D = Tuple[Leg_Raw, ...]
91 |
92 | def cons_distance3(
93 | distance: Point_Func,
94 | legs_iter: Iterable[Leg_Raw]) -> Iterator[Leg_P_D]:
95 | return (
96 | leg+(round(distance(*leg), 4),) # 1-tuple
97 | for leg in legs_iter
98 | )
99 |
100 | test_cons_distance3 = """
101 | >>> from Chapter04.ch04_ex1 import (
102 | ... float_from_pair, float_lat_lon, row_iter_kml, limits, haversine, legs
103 | ... )
104 | >>> import urllib.request
105 | >>> with urllib.request.urlopen("file:./Winter%202012-2013.kml") as source:
106 | ... path= float_from_pair(float_lat_lon(row_iter_kml(source)))
107 | ... trip3= tuple( cons_distance3( haversine, legs(iter(path)) ) )
108 |
109 | >>> trip3[0]
110 | ((37.54901619777347, -76.33029518659048), (37.840832, -76.273834), 17.7246)
111 | >>> trip3[-1]
112 | ((38.330166, -76.458504), (38.976334, -76.473503), 38.8019)
113 |
114 | """
115 |
116 | from typing import Callable, Iterator
117 | Num_Conv = Callable[[str], float]
118 | def numbers_from_rows(conversion: Num_Conv, text: str) -> Iterator[float]:
119 | return (
120 | conversion(value)
121 | for line in text.splitlines()
122 | for value in line.split()
123 | )
124 |
125 | test_numbers_from_rows = """
126 | >>> text= ''' 2 3 5 7 11 13 17 19 23 29
127 | ... 31 37 41 43 47 53 59 61 67 71
128 | ... 179 181 191 193 197 199 211 223 227 229'''
129 |
130 | >>> list(numbers_from_rows( float, text ) )
131 | [2.0, 3.0, 5.0, 7.0, 11.0, 13.0, 17.0, 19.0, 23.0, 29.0, 31.0, 37.0, 41.0, 43.0, 47.0, 53.0, 59.0, 61.0, 67.0, 71.0, 179.0, 181.0, 191.0, 193.0, 197.0, 199.0, 211.0, 223.0, 227.0, 229.0]
132 |
133 | """
134 |
135 | def group_by_iter(n: int, items: Iterator) -> Iterator[Tuple]:
136 | """
137 | >>> list( group_by_iter( 7, filter( lambda x: x%3==0 or x%5==0, range(1,50) ) ) )
138 | [(3, 5, 6, 9, 10, 12, 15), (18, 20, 21, 24, 25, 27, 30), (33, 35, 36, 39, 40, 42, 45), (48,)]
139 | """
140 | row = tuple(next(items) for i in range(n))
141 | while row:
142 | yield row
143 | row = tuple(next(items) for i in range(n))
144 |
145 |
146 | def group_filter_iter(n: int, pred: Callable, items: Iterator) -> Iterator:
147 | """
148 | >>> list( group_filter_iter( 7, lambda x: x%3==0 or x%5==0, range(1,50) ) )
149 | [(3, 5, 6, 9, 10, 12, 15), (18, 20, 21, 24, 25, 27, 30), (33, 35, 36, 39, 40, 42, 45), (48,)]
150 | """
151 | subset = filter(pred, items)
152 | row = tuple(next(subset) for i in range(n))
153 | while row:
154 | yield row
155 | row = tuple(next(subset) for i in range(n))
156 |
157 | def sum_filter_f(filter_f: Callable, function: Callable, data: Iterable) -> Iterator:
158 | return sum(function(x) for x in data if filter_f(x))
159 |
160 | count_ = lambda x: 1
161 | sum_ = lambda x: x
162 | valid = lambda x: x is not None
163 |
164 | test_sum_filter_f = """
165 | >>> text= ''' 2 3 5 7 11 13 17 19 23 29
166 | ... 31 37 41 43 47 53 59 61 67 71
167 | ... 179 181 191 193 197 199 211 223 227 229'''
168 |
169 | >>> data= tuple(numbers_from_rows( int, text ))
170 | >>> len(data)
171 | 30
172 |
173 | >>> sum_filter_f( valid, count_, data )
174 | 30
175 | >>> sum_filter_f( valid, sum_, data )
176 | 2669
177 | """
178 |
179 | def first(predicate: Callable, collection: Iterable) -> Any:
180 | for x in collection:
181 | if predicate(x):
182 | return x
183 |
184 | import math
185 | def isprimeh(x: int) -> bool:
186 | """
187 | >>> tuple( isprimeh(x) for x in range(3,11) )
188 | (True, False, True, False, True, False, False, False)
189 | """
190 | if x == 2:
191 | return True
192 | if x % 2 == 0:
193 | return False
194 | factor = first(lambda n: x%n == 0, range(3, int(math.sqrt(x)+.5)+1, 2))
195 | return factor is None
196 |
197 | def map_not_none(func: Callable, source: Iterable) -> Iterator:
198 | """
199 | >>> list( map_not_none( lambda x:x**2, [1, 2, 3, None, 4.5] ) )
200 | [1, 4, 9, 20.25]
201 | """
202 | for x in source:
203 | try:
204 | yield func(x)
205 | except Exception as e: # pylint: disable=broad-except,unused-variable
206 | pass # print(e)
207 |
208 |
209 | __test__ = {
210 | "test_convert": test_convert,
211 | "test_cons_distance": test_cons_distance,
212 | "test_cons_distance3": test_cons_distance3,
213 | "test_numbers_from_rows": test_numbers_from_rows,
214 | "test_sum_filter_f": test_sum_filter_f,
215 | }
216 |
217 | def test():
218 | import doctest
219 | doctest.testmod(verbose=1)
220 |
221 | if __name__ == "__main__":
222 | test()
223 |
--------------------------------------------------------------------------------
/Chapter15/ch15_ex4.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 15, Example Set 4
5 |
6 | typical URL.s
7 |
8 | http://localhost:8080/anscombe/I?form=xml
9 | http://localhost:8080/anscombe/II?form=json
10 | http://localhost:8080/anscombe/III?form=csv
11 | http://localhost:8080/anscombe/IV?form=html
12 |
13 | """
14 | # pylint: disable=wrong-import-position,wrong-import-order,reimported
15 |
16 | from Chapter03.ch03_ex5 import (
17 | series, head_map_filter, row_iter)
18 |
19 | from typing import (
20 | NamedTuple, Callable, List, Tuple, Iterable, Dict, Any)
21 |
22 | class Pair(NamedTuple):
23 | x: float
24 | y: float
25 |
26 | RawPairIter = Iterable[Tuple[float, float]]
27 |
28 | pairs: Callable[[RawPairIter], List[Pair]] \
29 | = lambda source: list(Pair(*row) for row in source)
30 |
31 | def raw_data() -> Dict[str, List[Pair]]:
32 | """
33 | >>> with open("Anscombe.txt") as source:
34 | ... data = tuple(head_map_filter(row_iter(source)))
35 | ...
36 | >>> data # doctest: +ELLIPSIS
37 | ([10.0, 8.04, 10.0, 9.14, 10.0, 7.46, 8.0, 6.58], ...)
38 | >>> raw_data()['I'] # doctest: +ELLIPSIS
39 | [Pair(x=10.0, y=8.04), Pair(x=8.0, y=6.95), ...
40 | """
41 | with open("Anscombe.txt") as source:
42 | data = tuple(head_map_filter(row_iter(source)))
43 | mapping = {
44 | id_str: pairs(series(id_num, data))
45 | for id_num, id_str in enumerate(['I', 'II', 'III', 'IV'])
46 | }
47 | return mapping
48 |
49 | def anscombe_filter(
50 | set_id: str, raw_data_map: Dict[str, List[Pair]]
51 | ) -> List[Pair]:
52 | """
53 | >>> anscombe_filter( "II", raw_data() ) # doctest: +ELLIPSIS
54 | [Pair(x=10.0, y=9.14), Pair(x=8.0, y=8.14), Pair(x=13.0, y=8.74), ...
55 | """
56 | return raw_data_map[set_id]
57 |
58 | from typing import Callable, TypeVar, Any, cast
59 |
60 | from functools import wraps
61 | def to_bytes(function: Callable[..., str]) -> Callable[..., bytes]:
62 | @wraps(function)
63 | def decorated(*args, **kw):
64 | text = function(*args, **kw)
65 | return text.encode("utf-8")
66 | return cast(Callable[..., bytes], decorated)
67 |
68 | import xml.etree.ElementTree as XML
69 | def serialize_xml(series: str, data: List[Pair]) -> bytes:
70 | """
71 | >>> data = [Pair(2,3), Pair(5,7)]
72 | >>> serialize_xml( "test", data )
73 | b'23
57
'
74 | """
75 | doc = XML.Element("series", name=series)
76 | for row in data:
77 | row_xml = XML.SubElement(doc, "row")
78 | x = XML.SubElement(row_xml, "x")
79 | x.text = str(row.x)
80 | y = XML.SubElement(row_xml, "y")
81 | y.text = str(row.y)
82 | return cast(bytes, XML.tostring(doc, encoding='utf-8'))
83 |
84 | import string
85 | data_page = string.Template("""\
86 |
87 | Series ${title}
88 |
89 | Series ${title}
90 |
91 | | x | y |
92 |
93 | ${rows}
94 |
95 |
96 |
97 |
98 | """)
99 | @to_bytes
100 | def serialize_html(series: str, data: List[Pair]) -> str:
101 | """
102 | >>> data = [Pair(2,3), Pair(5,7)]
103 | >>> serialize_html( "test", data ) #doctest: +ELLIPSIS
104 | b'...| 2 | 3 |
\\n| 5 | 7 |
...
105 | """
106 | text = data_page.substitute(
107 | title=series,
108 | rows="\n".join(
109 | "| {0.x} | {0.y} |
".format(row)
110 | for row in data)
111 | )
112 | return text
113 |
114 | import json
115 | @to_bytes
116 | def serialize_json(series: str, data: List[Pair]) -> str:
117 | """
118 | >>> data = [Pair(2,3), Pair(5,7)]
119 | >>> serialize_json( "test", data )
120 | b'[{"x": 2, "y": 3}, {"x": 5, "y": 7}]'
121 | """
122 | obj = [dict(x=r.x, y=r.y) for r in data]
123 | text = json.dumps(obj, sort_keys=True)
124 | return text
125 |
126 | import csv
127 | import io
128 |
129 | @to_bytes
130 | def serialize_csv(series: str, data: List[Pair]) -> str:
131 | """
132 | >>> data = [Pair(2,3), Pair(5,7)]
133 | >>> serialize_csv("test", data)
134 | b'x,y\\r\\n2,3\\r\\n5,7\\r\\n'
135 | """
136 | buffer = io.StringIO()
137 | wtr = csv.DictWriter(buffer, Pair._fields)
138 | wtr.writeheader()
139 | wtr.writerows(r._asdict() for r in data)
140 | return buffer.getvalue()
141 |
142 | Serializer = Callable[[str, List[Pair]], bytes]
143 | serializers: Dict[str, Tuple[str, Serializer]]= {
144 | 'xml': ('application/xml', serialize_xml),
145 | 'html': ('text/html', serialize_html),
146 | 'json': ('application/json', serialize_json),
147 | 'csv': ('text/csv', serialize_csv),
148 | }
149 |
150 | def serialize(format: str, title: str, data: List[Pair]) -> Tuple[bytes, str]:
151 | """json/xml/csv/html serialization.
152 |
153 | >>> data = [Pair(2,3), Pair(5,7)]
154 | >>> serialize("json", "test", data)
155 | (b'[{"x": 2, "y": 3}, {"x": 5, "y": 7}]', 'application/json')
156 | """
157 | mime, function = serializers.get(
158 | format.lower(), ('text/html', serialize_html))
159 | return function(title, data), mime
160 |
161 | import string
162 |
163 | error_page = string.Template("""
164 |
165 | ${title}
166 |
167 | Error
168 | ${message}
169 | ${traceback}
170 |
171 |
172 | """)
173 |
174 | import re
175 | path_pat = re.compile(r"^/anscombe/(?P.*?)/?$")
176 |
177 | test_pattern = """
178 | >>> m1= path_pat.match( "/anscombe/I" )
179 | >>> m1.groupdict()
180 | {'dataset': 'I'}
181 | >>> m2= path_pat.match( "/anscombe/II/" )
182 | >>> m2.groupdict()
183 | {'dataset': 'II'}
184 | >>> m3= path_pat.match( "/anscombe/" )
185 | >>> m3.groupdict()
186 | {'dataset': ''}
187 | """
188 |
189 | from typing import Callable, List, Tuple, Iterable
190 | from mypy_extensions import DefaultArg
191 |
192 | SR_Func = Callable[[str, List[Tuple[str, str]], DefaultArg(Tuple)], None]
193 |
194 | import traceback
195 | import urllib.parse
196 | def anscombe_app(environ: Dict, start_response: SR_Func) -> Iterable[bytes]:
197 | log = environ['wsgi.errors']
198 | try:
199 | match = path_pat.match(environ['PATH_INFO'])
200 | set_id = match.group('dataset').upper()
201 | query = urllib.parse.parse_qs(environ['QUERY_STRING'])
202 | print(environ['PATH_INFO'], environ['QUERY_STRING'],
203 | match.groupdict(), file=log)
204 | log.flush()
205 |
206 | dataset = anscombe_filter(set_id, raw_data())
207 | content_bytes, mime = serialize(query['form'][0], set_id, dataset)
208 |
209 | headers = [
210 | ('Content-Type', mime),
211 | ('Content-Length', str(len(content_bytes))),
212 | ]
213 | start_response("200 OK", headers)
214 | return [content_bytes]
215 | except Exception as e: # pylint: disable=broad-except
216 | traceback.print_exc(file=log)
217 | tb = traceback.format_exc()
218 | content = error_page.substitute(
219 | title="Error", message=repr(e), traceback=tb)
220 | content_bytes = content.encode("utf-8")
221 | headers = [
222 | ('Content-Type', "text/html"),
223 | ('Content-Length', str(len(content_bytes))),
224 | ]
225 | start_response("404 NOT FOUND", headers)
226 | return [content_bytes]
227 |
228 | __test__ = {
229 | "test_pattern": test_pattern,
230 | }
231 |
232 | def server_demo():
233 | from wsgiref.simple_server import make_server
234 | httpd = make_server('', 8080, anscombe_app)
235 | print("Serving HTTP on port 8080...")
236 |
237 | # Respond to requests until process is killed
238 | httpd.serve_forever()
239 |
240 | def test():
241 | import doctest
242 | doctest.testmod(verbose=1)
243 |
244 | if __name__ == "__main__":
245 | test()
246 | # server_demo()
247 |
--------------------------------------------------------------------------------
/Chapter03/ch03_ex5.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 3, Example Set 5
5 | """
6 | # pylint: disable=line-too-long,wrong-import-position
7 |
8 | import csv
9 | from typing import TextIO, Iterator, List, Text, Iterable
10 |
11 | def row_iter(source: TextIO) -> Iterator[List[Text]]:
12 | """Read a CSV file and emit a sequence of rows.
13 |
14 | >>> import io
15 | >>> data= io.StringIO( "1\\t2\\t3\\n4\\t5\\t6\\n" )
16 | >>> list(row_iter(data))
17 | [['1', '2', '3'], ['4', '5', '6']]
18 | """
19 | rdr = csv.reader(source, delimiter="\t")
20 | return rdr
21 |
22 | from typing import Optional
23 | def float_none(data: Text) -> Optional[float]:
24 | """Float conversion: return None instead of ValueError exception.
25 |
26 | >>> float_none('abc')
27 | >>> float_none('1.23')
28 | 1.23
29 | """
30 | try:
31 | data_f = float(data)
32 | return data_f
33 | except ValueError:
34 | return None
35 |
36 | from typing import Callable, List, Optional
37 | def head_map_filter(row_iter: Iterator[List[Optional[Text]]]) -> Iterator[List[float]]:
38 | """Removing headers by applying a filter to get rows with 8 values.
39 |
40 | >>> rows= [ ["Anscombe's quartet"], ['I', 'II', 'III', 'IV'], ['x','y','x','y','x','y','x','y'], ['1','2','3','4','5','6','7','8']]
41 | >>> list(head_map_filter( rows ))
42 | [[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]]
43 | """
44 | R_Text = List[Optional[Text]]
45 | R_Float = List[Optional[float]]
46 |
47 | float_row: Callable[[R_Text], R_Float] \
48 | = lambda row: list(map(float_none, row))
49 |
50 | all_numeric: Callable[[R_Float], bool] \
51 | = lambda row: all(row) and len(row) == 8
52 |
53 | return filter(all_numeric, map(float_row, row_iter))
54 |
55 | def head_split_fixed(row_iter: Iterator[List[Text]]) -> Iterator[List[Text]]:
56 | """Removing a fixed sequence of headers.
57 |
58 | >>> rows= [ ["Anscombe's quartet"], ['I', 'II', 'III', 'IV'], ['x','y','x','y','x','y','x','y'], ['1','2','3','4','5','6','7','8']]
59 | >>> list(head_split_fixed( iter(rows) ))
60 | [['1', '2', '3', '4', '5', '6', '7', '8']]
61 | """
62 | title = next(row_iter)
63 | assert (len(title) == 1
64 | and title[0] == "Anscombe's quartet")
65 | heading = next(row_iter)
66 | assert (len(heading) == 4
67 | and heading == ['I', 'II', 'III', 'IV'])
68 | columns = next(row_iter)
69 | assert (len(columns) == 8
70 | and columns == ['x', 'y', 'x', 'y', 'x', 'y', 'x', 'y'])
71 | return row_iter
72 |
73 | def head_split_recurse(row_iter: Iterator[List[Text]]) -> Iterator[List[Text]]:
74 | """Removing headers recusively, looking for the last header.
75 |
76 | >>> rows= [ ["Anscombe's quartet"], ['I', 'II', 'III', 'IV'], ['x','y','x','y','x','y','x','y'], ['1','2','3','4','5','6','7','8']]
77 | >>> list(head_split_recurse( iter(rows) ))
78 | [['1', '2', '3', '4', '5', '6', '7', '8']]
79 | """
80 | data = next(row_iter)
81 | if len(data) == 8 and data == ['x', 'y', 'x', 'y', 'x', 'y', 'x', 'y']:
82 | return row_iter
83 | return head_split_recurse(row_iter)
84 |
85 | from typing import Tuple, cast, TypeVar
86 |
87 | T_ = TypeVar("T_")
88 | Pair = Tuple[T_, T_]
89 | def series(n: int, row_iter: Iterable[List[T_]]) -> Iterator[Pair]:
90 | """Turn one of the given Anscombe's series into two-tuple objects.
91 |
92 | >>> rows = [[1,2, 3,4, 5,6, 7,8],[9,10, 11,12, 13,14, 15,16]]
93 | >>> list(series(0, rows))
94 | [(1, 2), (9, 10)]
95 | >>> list(series(1, rows))
96 | [(3, 4), (11, 12)]
97 | """
98 | for row in row_iter:
99 | yield cast(Pair, tuple(row[n*2:n*2+2]))
100 |
101 | from typing import Callable, Iterable
102 | row_float: Callable[[Pair], Iterable[float]] = lambda row: map(float, row)
103 |
104 | test_parse_1 = """
105 | >>> with open("Anscombe.txt") as source:
106 | ... print(list(head_map_filter(row_iter(source))))
107 | [[10.0, 8.04, 10.0, 9.14, 10.0, 7.46, 8.0, 6.58], [8.0, 6.95, 8.0, 8.14, 8.0, 6.77, 8.0, 5.76], [13.0, 7.58, 13.0, 8.74, 13.0, 12.74, 8.0, 7.71], [9.0, 8.81, 9.0, 8.77, 9.0, 7.11, 8.0, 8.84], [11.0, 8.33, 11.0, 9.26, 11.0, 7.81, 8.0, 8.47], [14.0, 9.96, 14.0, 8.1, 14.0, 8.84, 8.0, 7.04], [6.0, 7.24, 6.0, 6.13, 6.0, 6.08, 8.0, 5.25], [4.0, 4.26, 4.0, 3.1, 4.0, 5.39, 19.0, 12.5], [12.0, 10.84, 12.0, 9.13, 12.0, 8.15, 8.0, 5.56], [7.0, 4.82, 7.0, 7.26, 7.0, 6.42, 8.0, 7.91], [5.0, 5.68, 5.0, 4.74, 5.0, 5.73, 8.0, 6.89]]
108 |
109 | >>> with open("Anscombe.txt") as source:
110 | ... print(list(head_split_fixed(row_iter(source))))
111 | [['10.0', '8.04', '10.0', '9.14', '10.0', '7.46', '8.0', '6.58'], ['8.0', '6.95', '8.0', '8.14', '8.0', '6.77', '8.0', '5.76'], ['13.0', '7.58', '13.0', '8.74', '13.0', '12.74', '8.0', '7.71'], ['9.0', '8.81', '9.0', '8.77', '9.0', '7.11', '8.0', '8.84'], ['11.0', '8.33', '11.0', '9.26', '11.0', '7.81', '8.0', '8.47'], ['14.0', '9.96', '14.0', '8.10', '14.0', '8.84', '8.0', '7.04'], ['6.0', '7.24', '6.0', '6.13', '6.0', '6.08', '8.0', '5.25'], ['4.0', '4.26', '4.0', '3.10', '4.0', '5.39', '19.0', '12.50'], ['12.0', '10.84', '12.0', '9.13', '12.0', '8.15', '8.0', '5.56'], ['7.0', '4.82', '7.0', '7.26', '7.0', '6.42', '8.0', '7.91'], ['5.0', '5.68', '5.0', '4.74', '5.0', '5.73', '8.0', '6.89']]
112 |
113 | >>> with open("Anscombe.txt") as source:
114 | ... print(list(head_split_recurse(row_iter(source))))
115 | [['10.0', '8.04', '10.0', '9.14', '10.0', '7.46', '8.0', '6.58'], ['8.0', '6.95', '8.0', '8.14', '8.0', '6.77', '8.0', '5.76'], ['13.0', '7.58', '13.0', '8.74', '13.0', '12.74', '8.0', '7.71'], ['9.0', '8.81', '9.0', '8.77', '9.0', '7.11', '8.0', '8.84'], ['11.0', '8.33', '11.0', '9.26', '11.0', '7.81', '8.0', '8.47'], ['14.0', '9.96', '14.0', '8.10', '14.0', '8.84', '8.0', '7.04'], ['6.0', '7.24', '6.0', '6.13', '6.0', '6.08', '8.0', '5.25'], ['4.0', '4.26', '4.0', '3.10', '4.0', '5.39', '19.0', '12.50'], ['12.0', '10.84', '12.0', '9.13', '12.0', '8.15', '8.0', '5.56'], ['7.0', '4.82', '7.0', '7.26', '7.0', '6.42', '8.0', '7.91'], ['5.0', '5.68', '5.0', '4.74', '5.0', '5.73', '8.0', '6.89']]
116 |
117 | """
118 |
119 | test_parse_2 = """
120 | >>> with open("Anscombe.txt") as source:
121 | ... print( list(series(0, head_split_recurse(row_iter(source)))) )
122 | [('10.0', '8.04'), ('8.0', '6.95'), ('13.0', '7.58'), ('9.0', '8.81'), ('11.0', '8.33'), ('14.0', '9.96'), ('6.0', '7.24'), ('4.0', '4.26'), ('12.0', '10.84'), ('7.0', '4.82'), ('5.0', '5.68')]
123 |
124 | >>> with open("Anscombe.txt") as source:
125 | ... print( list(series(0, head_map_filter(row_iter(source)))) )
126 | [(10.0, 8.04), (8.0, 6.95), (13.0, 7.58), (9.0, 8.81), (11.0, 8.33), (14.0, 9.96), (6.0, 7.24), (4.0, 4.26), (12.0, 10.84), (7.0, 4.82), (5.0, 5.68)]
127 |
128 | >>> with open("Anscombe.txt") as source:
129 | ... data = head_split_fixed(row_iter(source))
130 | ... print( list(series(0,data)) )
131 | [('10.0', '8.04'), ('8.0', '6.95'), ('13.0', '7.58'), ('9.0', '8.81'), ('11.0', '8.33'), ('14.0', '9.96'), ('6.0', '7.24'), ('4.0', '4.26'), ('12.0', '10.84'), ('7.0', '4.82'), ('5.0', '5.68')]
132 |
133 | >>> with open("Anscombe.txt") as source:
134 | ... data = head_split_fixed(row_iter(source))
135 | ... sample_I= tuple(series(0,data))
136 | ... print( sample_I )
137 | (('10.0', '8.04'), ('8.0', '6.95'), ('13.0', '7.58'), ('9.0', '8.81'), ('11.0', '8.33'), ('14.0', '9.96'), ('6.0', '7.24'), ('4.0', '4.26'), ('12.0', '10.84'), ('7.0', '4.82'), ('5.0', '5.68'))
138 |
139 | """
140 |
141 | test_mean = """
142 | >>> with open("Anscombe.txt") as source:
143 | ... data = tuple(head_split_fixed(row_iter(source)))
144 | ... sample_I = tuple(series(0,data))
145 | ... sample_II = tuple(series(1,data))
146 | ... sample_III = tuple(series(2,data))
147 | ... sample_IV = tuple(series(3,data))
148 |
149 | >>> for subset in sample_I, sample_II, sample_III, sample_III:
150 | ... mean = sum(float(pair[1]) for pair in subset)/len(subset)
151 | ... print( round(mean,3) )
152 | 7.501
153 | 7.501
154 | 7.5
155 | 7.5
156 | """
157 |
158 | __test__ = {
159 | "Basic Parse": test_parse_1,
160 | "Pick Series": test_parse_2,
161 | "Basic Mean": test_mean,
162 | }
163 |
164 | def test():
165 | import doctest
166 | doctest.testmod(verbose=1)
167 |
168 | if __name__ == "__main__":
169 | test()
170 |
--------------------------------------------------------------------------------
/Chapter03/ch03_ex3.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Functional Python Programming
3 |
4 | Chapter 3, Example Set 3
5 | """
6 |
7 | from typing import TextIO, Tuple, List, Iterator, TypeVar, Any, Iterable, Sequence
8 |
9 | def strip_head(source: TextIO, line: str) -> Tuple[TextIO, str]:
10 | """Purely recursive strip headings until a blank line.
11 |
12 | >>> import io
13 | >>> data = io.StringIO( "heading\\n\\nbody\\nmore\\n" )
14 | >>> tail, first = strip_head(data, data.readline())
15 | >>> first
16 | 'body\\n'
17 | >>> list(tail)
18 | ['more\\n']
19 |
20 | """
21 | if len(line.strip()) == 0:
22 | return source, source.readline()
23 | return strip_head(source, source.readline())
24 |
25 | def get_columns(source: TextIO, line: str) -> Iterator[str]:
26 | """When reading 1000.txt, parse columns and exclude the trailing line.
27 |
28 | >>> import io
29 | >>> data = io.StringIO( "body\\nmore\\nend.\\n" )
30 | >>> list( get_columns(data, data.readline() ) )
31 | ['body\\n', 'more\\n']
32 | """
33 | if line.strip() == "end.":
34 | return
35 | yield line
36 | yield from get_columns(source, source.readline())
37 |
38 | #Older code...
39 | #for data in get_columns( source, source.readline() ):
40 | # yield data
41 |
42 | def parse_i(source: TextIO) -> Iterator[int]:
43 | """Imperative parsing.
44 |
45 | >>> import io
46 | >>> data = io.StringIO('''\\
47 | ... The First 1,000 Primes
48 | ... (the 1,000th is 7919)
49 | ... For more information on primes see http://primes.utm.edu/
50 | ...
51 | ... 2 3 5 7 11 13 17 19 23 29
52 | ... 7841 7853 7867 7873 7877 7879 7883 7901 7907 7919
53 | ... end.
54 | ... ''')
55 | >>> list( parse_i(data))
56 | [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, 7919]
57 | """
58 | for c in get_columns(*strip_head(source, source.readline())):
59 | for number_text in c.split():
60 | yield int(number_text)
61 |
62 | def parse_g(source: TextIO) -> Iterator[int]:
63 | """Generator function parsing.
64 |
65 | >>> import io
66 | >>> data = io.StringIO('''\\
67 | ... The First 1,000 Primes
68 | ... (the 1,000th is 7919)
69 | ... For more information on primes see http://primes.utm.edu/
70 | ...
71 | ... 2 3 5 7 11 13 17 19 23 29
72 | ... 7841 7853 7867 7873 7877 7879 7883 7901 7907 7919
73 | ... end.
74 | ... ''')
75 | >>> list( parse_g(data))
76 | [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, 7919]
77 | """
78 | return (
79 | int(number_text)
80 | for c in get_columns(*strip_head(source, source.readline()))
81 | for number_text in c.split()
82 | )
83 |
84 | def flatten(data: Iterable[Iterable[Any]]) -> Iterable[Any]:
85 | for line in data:
86 | for x in line:
87 | yield x
88 |
89 |
90 | # Faster than isprimer, isprimeg
91 | # pylint: disable=wrong-import-position
92 | from Chapter02.ch02_ex1 import isprimei
93 | import time
94 | from functools import reduce
95 |
96 | def performance():
97 | with open("1000.txt") as source:
98 | primes = list(parse_g(source))
99 | assert len(primes) == 1000
100 |
101 | start = time.perf_counter()
102 | for repeat in range(1000):
103 | assert all(isprimei(x) for x in primes)
104 | print(time.perf_counter() - start)
105 |
106 | start = time.perf_counter()
107 | for repeat in range(1000):
108 | assert not any(not isprimei(x) for x in primes)
109 | print(time.perf_counter() - start)
110 |
111 | start = time.perf_counter()
112 | for repeat in range(1000):
113 | assert reduce(lambda x, y: x and y, (isprimei(x) for x in primes))
114 | print(time.perf_counter() - start)
115 |
116 | ItemType = TypeVar("ItemType")
117 | Flat = Sequence[ItemType]
118 | Grouped = List[Tuple[ItemType, ...]]
119 | def group_by_seq(n: int, sequence: Flat) -> Grouped:
120 | flat_iter = iter(sequence)
121 | full_sized_items = list(
122 | tuple(
123 | next(flat_iter)
124 | for i in range(n)
125 | )
126 | for row in range(len(sequence)//n)
127 | )
128 | trailer = tuple(flat_iter)
129 | if trailer:
130 | return full_sized_items + [trailer]
131 | else:
132 | return full_sized_items
133 |
134 | # ItemType = TypeVar("ItemType")
135 | Flat_Iter = Iterator[ItemType]
136 | Grouped_Iter = Iterator[Tuple[ItemType, ...]]
137 | def group_by_iter(n: int, iterable: Flat_Iter) -> Grouped_Iter:
138 | row = tuple(next(iterable) for i in range(n))
139 | while row:
140 | yield row
141 | row = tuple(next(iterable) for i in range(n))
142 |
143 | from itertools import zip_longest
144 | def group_by_slice(
145 | n: int,
146 | sequence: Sequence[ItemType]) -> Iterator[Tuple[ItemType, ...]]:
147 | return zip_longest(*(sequence[i::n] for i in range(n)))
148 |
149 |
150 | def digits(x: int, b: int) -> Iterator[int]:
151 | """Digits in given base. Recursive.
152 |
153 | >>> tuple(digits(126, 2))
154 | (0, 1, 1, 1, 1, 1, 1)
155 | >>> tuple(digits(126, 16))
156 | (14, 7)
157 | """
158 | if x == 0:
159 | return
160 | yield x % b
161 | yield from digits(x//b, b)
162 |
163 | def to_base(x: int, b: int) -> Iterator[int]:
164 | """Digits in a more typical order in a given base.
165 |
166 | >>> tuple(to_base(126, 2))
167 | (1, 1, 1, 1, 1, 1, 0)
168 | >>> bin(126)
169 | '0b1111110'
170 | >>> tuple(to_base(126, 16))
171 | (7, 14)
172 | >>> hex(126)
173 | '0x7e'
174 |
175 | >>> print( bin(126), tuple(to_base(126, 2)) )
176 | 0b1111110 (1, 1, 1, 1, 1, 1, 0)
177 | >>> print( hex(126), tuple(to_base(126, 16)) )
178 | 0x7e (7, 14)
179 | """
180 | return reversed(tuple(digits(x, b)))
181 |
182 | # pylint: disable=line-too-long
183 | __test__ = {
184 | "parser_test":
185 | """
186 | >>> text= ''' 2 3 5 7 11 13 17 19 23 29
187 | ... 31 37 41 43 47 53 59 61 67 71
188 | ... 179 181 191 193 197 199 211 223 227 229
189 | ... '''
190 | >>> data= list(v for line in text.splitlines() for v in line.split())
191 | >>> data
192 | ['2', '3', '5', '7', '11', '13', '17', '19', '23', '29', '31', '37', '41', '43', '47', '53', '59', '61', '67', '71', '179', '181', '191', '193', '197', '199', '211', '223', '227', '229']
193 |
194 | >>> file = text.splitlines()
195 | >>> blocked = list(line.split() for line in file)
196 | >>> blocked
197 | [['2', '3', '5', '7', '11', '13', '17', '19', '23', '29'], ['31', '37', '41', '43', '47', '53', '59', '61', '67', '71'], ['179', '181', '191', '193', '197', '199', '211', '223', '227', '229']]
198 |
199 | >>> (x for line in blocked for x in line) # doctest: +ELLIPSIS
200 | at ...>
201 | >>> list(_)
202 | ['2', '3', '5', '7', '11', '13', '17', '19', '23', '29', '31', '37', '41', '43', '47', '53', '59', '61', '67', '71', '179', '181', '191', '193', '197', '199', '211', '223', '227', '229']
203 | """,
204 | "grouping_test":
205 | """
206 | >>> with open("1000.txt") as source:
207 | ... flat= list(parse_g(source))
208 | >>> len(flat)
209 | 1000
210 |
211 | >>> group7_seq= group_by_seq(7, flat)
212 | >>> group7_seq[-1]
213 | (7877, 7879, 7883, 7901, 7907, 7919)
214 |
215 | >>> demo= list(x for line in group7_seq for x in line)
216 | >>> demo == flat
217 | True
218 |
219 | >>> group7_iter= list(group_by_iter(7, iter(flat)))
220 |
221 | >>> group7_iter[-1]
222 | (7877, 7879, 7883, 7901, 7907, 7919)
223 |
224 | >>> demo= list(x for line in group7_iter for x in line)
225 | >>> demo == flat
226 | True
227 |
228 | >>> all= list(group_by_slice(7, flat))
229 | >>> all[0]
230 | (2, 3, 5, 7, 11, 13, 17)
231 | >>> all[-1]
232 | (7877, 7879, 7883, 7901, 7907, 7919, None)
233 | """
234 | }
235 |
236 | def test():
237 | import doctest
238 | doctest.testmod(verbose=1)
239 |
240 | if __name__ == "__main__":
241 | # import sys
242 | # print(sys.path)
243 | test()
244 | # performance()
245 |
--------------------------------------------------------------------------------