├── 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 | Creative Commons License 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 |
30 |
31 | 32 |
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 |
147 |

Chapter 15

148 |

The WSGI Demo App

149 |

All Code

150 |

Run Test Suite

151 |

This Code

152 |
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'2357' 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 | 92 | 93 | ${rows} 94 | 95 |
xy
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'...23\\n57... 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 | --------------------------------------------------------------------------------