├── cover.jpg ├── Chapter1 ├── fib1.py ├── fib2.py ├── fib3.py ├── fib4.py ├── fib5.py ├── calculating_pi.py ├── fib6.py ├── unbreakable_encryption.py ├── hanoi.py └── trivial_compression.py ├── Chapter4 ├── edge.py ├── weighted_edge.py ├── priority_queue.py ├── weighted_graph.py ├── mst.py ├── graph.py └── dijkstra.py ├── Chapter5 ├── chromosome.py ├── simple_equation.py ├── list_compression.py ├── send_more_money2.py └── genetic_algorithm.py ├── Chapter7 ├── neuron.py ├── util.py ├── wine_test.py ├── iris_test.py ├── layer.py ├── network.py ├── iris.csv └── wine.csv ├── Chapter8 ├── board.py ├── tictactoe_ai.py ├── connectfour_ai.py ├── tictactoe_tests.py ├── minimax.py ├── tictactoe.py └── connectfour.py ├── Chapter6 ├── data_point.py ├── mj.py ├── governors.py └── kmeans.py ├── .gitignore ├── Chapter9 ├── phone_number_mnemonics.py ├── tsp.py └── knapsack.py ├── Chapter3 ├── queens.py ├── send_more_money.py ├── map_coloring.py ├── csp.py └── word_search.py ├── Chapter2 ├── dna_search.py ├── missionaries.py ├── maze.py └── generic_search.py ├── README.md └── LICENSE /cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davecom/ClassicComputerScienceProblemsInPython/HEAD/cover.jpg -------------------------------------------------------------------------------- /Chapter1/fib1.py: -------------------------------------------------------------------------------- 1 | # fib1.py 2 | # From Classic Computer Science Problems in Python Chapter 1 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | def fib1(n: int) -> int: 19 | return fib1(n - 1) + fib1(n - 2) 20 | 21 | 22 | if __name__ == "__main__": 23 | print(fib1(5)) 24 | # Note that this example is purposefully wrong. 25 | -------------------------------------------------------------------------------- /Chapter1/fib2.py: -------------------------------------------------------------------------------- 1 | # fib2.py 2 | # From Classic Computer Science Problems in Python Chapter 1 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | def fib2(n: int) -> int: 19 | if n < 2: # base case 20 | return n 21 | return fib2(n - 2) + fib2(n - 1) # recursive case 22 | 23 | 24 | if __name__ == "__main__": 25 | print(fib2(5)) 26 | print(fib2(10)) -------------------------------------------------------------------------------- /Chapter1/fib3.py: -------------------------------------------------------------------------------- 1 | # fib3.py 2 | # From Classic Computer Science Problems in Python Chapter 1 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from typing import Dict 17 | memo: Dict[int, int] = {0: 0, 1: 1} # our base cases 18 | 19 | 20 | def fib3(n: int) -> int: 21 | if n not in memo: 22 | memo[n] = fib3(n - 1) + fib3(n - 2) # memoization 23 | return memo[n] 24 | 25 | 26 | if __name__ == "__main__": 27 | print(fib3(5)) 28 | print(fib3(50)) -------------------------------------------------------------------------------- /Chapter1/fib4.py: -------------------------------------------------------------------------------- 1 | # fib4.py 2 | # From Classic Computer Science Problems in Python Chapter 1 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from functools import lru_cache 17 | 18 | 19 | @lru_cache(maxsize=None) 20 | def fib4(n: int) -> int: # same definition as fib2() 21 | if n < 2: # base case 22 | return n 23 | return fib4(n - 2) + fib4(n - 1) # recursive case 24 | 25 | 26 | if __name__ == "__main__": 27 | print(fib4(5)) 28 | print(fib4(50)) 29 | -------------------------------------------------------------------------------- /Chapter4/edge.py: -------------------------------------------------------------------------------- 1 | # edge.py 2 | # From Classic Computer Science Problems in Python Chapter 4 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from __future__ import annotations 17 | from dataclasses import dataclass 18 | 19 | 20 | @dataclass 21 | class Edge: 22 | u: int # the "from" vertex 23 | v: int # the "to" vertex 24 | 25 | def reversed(self) -> Edge: 26 | return Edge(self.v, self.u) 27 | 28 | def __str__(self) -> str: 29 | return f"{self.u} -> {self.v}" 30 | -------------------------------------------------------------------------------- /Chapter1/fib5.py: -------------------------------------------------------------------------------- 1 | # fib5.py 2 | # From Classic Computer Science Problems in Python Chapter 1 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | def fib5(n: int) -> int: 17 | if n == 0: return n # special case 18 | last: int = 0 # initially set to fib(0) 19 | next: int = 1 # initially set to fib(1) 20 | for _ in range(1, n): 21 | last, next = next, last + next 22 | return next 23 | 24 | 25 | if __name__ == "__main__": 26 | print(fib5(2)) 27 | print(fib5(50)) 28 | 29 | -------------------------------------------------------------------------------- /Chapter1/calculating_pi.py: -------------------------------------------------------------------------------- 1 | # calculating_pi.py 2 | # From Classic Computer Science Problems in Python Chapter 1 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | def calculate_pi(n_terms: int) -> float: 19 | numerator: float = 4.0 20 | denominator: float = 1.0 21 | operation: float = 1.0 22 | pi: float = 0.0 23 | for _ in range(n_terms): 24 | pi += operation * (numerator / denominator) 25 | denominator += 2.0 26 | operation *= -1.0 27 | return pi 28 | 29 | 30 | if __name__ == "__main__": 31 | print(calculate_pi(1000000)) 32 | -------------------------------------------------------------------------------- /Chapter1/fib6.py: -------------------------------------------------------------------------------- 1 | # fib6.py 2 | # From Classic Computer Science Problems in Python Chapter 1 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from typing import Generator 17 | 18 | 19 | def fib6(n: int) -> Generator[int, None, None]: 20 | yield 0 # special case 21 | if n > 0: yield 1 # special case 22 | last: int = 0 # initially set to fib(0) 23 | next: int = 1 # initially set to fib(1) 24 | for _ in range(1, n): 25 | last, next = next, last + next 26 | yield next # main generation step 27 | 28 | 29 | if __name__ == "__main__": 30 | for i in fib6(50): 31 | print(i) 32 | -------------------------------------------------------------------------------- /Chapter4/weighted_edge.py: -------------------------------------------------------------------------------- 1 | # weighted_edge.py 2 | # From Classic Computer Science Problems in Python Chapter 4 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from __future__ import annotations 17 | from dataclasses import dataclass 18 | from edge import Edge 19 | 20 | 21 | @dataclass 22 | class WeightedEdge(Edge): 23 | weight: float 24 | 25 | def reversed(self) -> WeightedEdge: 26 | return WeightedEdge(self.v, self.u, self.weight) 27 | 28 | # so that we can order edges by weight to find the minimum weight edge 29 | def __lt__(self, other: WeightedEdge) -> bool: 30 | return self.weight < other.weight 31 | 32 | def __str__(self) -> str: 33 | return f"{self.u} {self.weight}> {self.v}" 34 | -------------------------------------------------------------------------------- /Chapter4/priority_queue.py: -------------------------------------------------------------------------------- 1 | # priority_queue.py 2 | # From Classic Computer Science Problems in Python Chapter 4 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from typing import TypeVar, Generic, List 17 | from heapq import heappush, heappop 18 | 19 | 20 | T = TypeVar('T') 21 | 22 | 23 | class PriorityQueue(Generic[T]): 24 | def __init__(self) -> None: 25 | self._container: List[T] = [] 26 | 27 | @property 28 | def empty(self) -> bool: 29 | return not self._container # not is true for empty container 30 | 31 | def push(self, item: T) -> None: 32 | heappush(self._container, item) # in by priority 33 | 34 | def pop(self) -> T: 35 | return heappop(self._container) # out by priority 36 | 37 | def __repr__(self) -> str: 38 | return repr(self._container) -------------------------------------------------------------------------------- /Chapter5/chromosome.py: -------------------------------------------------------------------------------- 1 | # chromosome.py 2 | # From Classic Computer Science Problems in Python Chapter 5 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from __future__ import annotations 17 | from typing import TypeVar, Tuple, Type 18 | from abc import ABC, abstractmethod 19 | 20 | T = TypeVar('T', bound='Chromosome') # for returning self 21 | 22 | 23 | # Base class for all chromosomes; all methods must be overridden 24 | class Chromosome(ABC): 25 | @abstractmethod 26 | def fitness(self) -> float: 27 | ... 28 | 29 | @classmethod 30 | @abstractmethod 31 | def random_instance(cls: Type[T]) -> T: 32 | ... 33 | 34 | @abstractmethod 35 | def crossover(self: T, other: T) -> Tuple[T, T]: 36 | ... 37 | 38 | @abstractmethod 39 | def mutate(self) -> None: 40 | ... 41 | -------------------------------------------------------------------------------- /Chapter7/neuron.py: -------------------------------------------------------------------------------- 1 | # neuron.py 2 | # From Classic Computer Science Problems in Python Chapter 7 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from typing import List, Callable 17 | from util import dot_product 18 | 19 | 20 | class Neuron: 21 | def __init__(self, weights: List[float], learning_rate: float, activation_function: Callable[[float], float], derivative_activation_function: Callable[[float], float]) -> None: 22 | self.weights: List[float] = weights 23 | self.activation_function: Callable[[float], float] = activation_function 24 | self.derivative_activation_function: Callable[[float], float] = derivative_activation_function 25 | self.learning_rate: float = learning_rate 26 | self.output_cache: float = 0.0 27 | self.delta: float = 0.0 28 | 29 | def output(self, inputs: List[float]) -> float: 30 | self.output_cache = dot_product(inputs, self.weights) 31 | return self.activation_function(self.output_cache) 32 | 33 | -------------------------------------------------------------------------------- /Chapter8/board.py: -------------------------------------------------------------------------------- 1 | # board.py 2 | # From Classic Computer Science Problems in Python Chapter 8 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from __future__ import annotations 17 | from typing import NewType, List 18 | from abc import ABC, abstractmethod 19 | 20 | Move = NewType('Move', int) 21 | 22 | 23 | class Piece: 24 | @property 25 | def opposite(self) -> Piece: 26 | raise NotImplementedError("Should be implemented by subclasses.") 27 | 28 | 29 | class Board(ABC): 30 | @property 31 | @abstractmethod 32 | def turn(self) -> Piece: 33 | ... 34 | 35 | @abstractmethod 36 | def move(self, location: Move) -> Board: 37 | ... 38 | 39 | @property 40 | @abstractmethod 41 | def legal_moves(self) -> List[Move]: 42 | ... 43 | 44 | @property 45 | @abstractmethod 46 | def is_win(self) -> bool: 47 | ... 48 | 49 | @property 50 | def is_draw(self) -> bool: 51 | return (not self.is_win) and (len(self.legal_moves) == 0) 52 | 53 | @abstractmethod 54 | def evaluate(self, player: Piece) -> float: 55 | ... 56 | 57 | -------------------------------------------------------------------------------- /Chapter7/util.py: -------------------------------------------------------------------------------- 1 | # util.py 2 | # From Classic Computer Science Problems in Python Chapter 7 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from typing import List 17 | from math import exp 18 | 19 | 20 | # dot product of two vectors 21 | def dot_product(xs: List[float], ys: List[float]) -> float: 22 | return sum(x * y for x, y in zip(xs, ys)) 23 | 24 | 25 | # the classic sigmoid activation function 26 | def sigmoid(x: float) -> float: 27 | return 1.0 / (1.0 + exp(-x)) 28 | 29 | 30 | def derivative_sigmoid(x: float) -> float: 31 | sig: float = sigmoid(x) 32 | return sig * (1 - sig) 33 | 34 | 35 | # assume all rows are of equal length 36 | # and feature scale each column to be in the range 0 - 1 37 | def normalize_by_feature_scaling(dataset: List[List[float]]) -> None: 38 | for col_num in range(len(dataset[0])): 39 | column: List[float] = [row[col_num] for row in dataset] 40 | maximum = max(column) 41 | minimum = min(column) 42 | for row_num in range(len(dataset)): 43 | dataset[row_num][col_num] = (dataset[row_num][col_num] - minimum) / (maximum - minimum) 44 | 45 | -------------------------------------------------------------------------------- /Chapter6/data_point.py: -------------------------------------------------------------------------------- 1 | # data_point.py 2 | # From Classic Computer Science Problems in Python Chapter 6 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from __future__ import annotations 17 | from typing import Iterator, Tuple, List, Iterable 18 | from math import sqrt 19 | 20 | 21 | class DataPoint: 22 | def __init__(self, initial: Iterable[float]) -> None: 23 | self._originals: Tuple[float, ...] = tuple(initial) 24 | self.dimensions: Tuple[float, ...] = tuple(initial) 25 | 26 | @property 27 | def num_dimensions(self) -> int: 28 | return len(self.dimensions) 29 | 30 | def distance(self, other: DataPoint) -> float: 31 | combined: Iterator[Tuple[float, float]] = zip(self.dimensions, other.dimensions) 32 | differences: List[float] = [(x - y) ** 2 for x, y in combined] 33 | return sqrt(sum(differences)) 34 | 35 | def __eq__(self, other: object) -> bool: 36 | if not isinstance(other, DataPoint): 37 | return NotImplemented 38 | return self.dimensions == other.dimensions 39 | 40 | def __repr__(self) -> str: 41 | return self._originals.__repr__() 42 | -------------------------------------------------------------------------------- /Chapter1/unbreakable_encryption.py: -------------------------------------------------------------------------------- 1 | # unbreakable_encryption.py 2 | # From Classic Computer Science Problems in Python Chapter 1 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from secrets import token_bytes 17 | from typing import Tuple 18 | 19 | 20 | def random_key(length: int) -> int: 21 | # generate length random bytes 22 | tb: bytes = token_bytes(length) 23 | # convert those bytes into a bit string and return it 24 | return int.from_bytes(tb, "big") 25 | 26 | 27 | def encrypt(original: str) -> Tuple[int, int]: 28 | original_bytes: bytes = original.encode() 29 | dummy: int = random_key(len(original_bytes)) 30 | original_key: int = int.from_bytes(original_bytes, "big") 31 | encrypted: int = original_key ^ dummy # XOR 32 | return dummy, encrypted 33 | 34 | 35 | def decrypt(key1: int, key2: int) -> str: 36 | decrypted: int = key1 ^ key2 # XOR 37 | temp: bytes = decrypted.to_bytes((decrypted.bit_length() + 7) // 8, "big") 38 | return temp.decode() 39 | 40 | 41 | if __name__ == "__main__": 42 | key1, key2 = encrypt("One Time Pad!") 43 | result: str = decrypt(key1, key2) 44 | print(result) 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /Chapter1/hanoi.py: -------------------------------------------------------------------------------- 1 | # hanoi.py 2 | # From Classic Computer Science Problems in Python Chapter 1 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from typing import TypeVar, Generic, List 17 | T = TypeVar('T') 18 | 19 | 20 | class Stack(Generic[T]): 21 | 22 | def __init__(self) -> None: 23 | self._container: List[T] = [] 24 | 25 | def push(self, item: T) -> None: 26 | self._container.append(item) 27 | 28 | def pop(self) -> T: 29 | return self._container.pop() 30 | 31 | def __repr__(self) -> str: 32 | return repr(self._container) 33 | 34 | 35 | num_discs: int = 3 36 | tower_a: Stack[int] = Stack() 37 | tower_b: Stack[int] = Stack() 38 | tower_c: Stack[int] = Stack() 39 | for i in range(1, num_discs + 1): 40 | tower_a.push(i) 41 | 42 | 43 | def hanoi(begin: Stack[int], end: Stack[int], temp: Stack[int], n: int) -> None: 44 | if n == 1: 45 | end.push(begin.pop()) 46 | else: 47 | hanoi(begin, temp, end, n - 1) 48 | hanoi(begin, end, temp, 1) 49 | hanoi(temp, end, begin, n - 1) 50 | 51 | 52 | if __name__ == "__main__": 53 | hanoi(tower_a, tower_c, tower_b, num_discs) 54 | print(tower_a) 55 | print(tower_b) 56 | print(tower_c) 57 | -------------------------------------------------------------------------------- /Chapter8/tictactoe_ai.py: -------------------------------------------------------------------------------- 1 | # tictactoe_ai.py 2 | # From Classic Computer Science Problems in Python Chapter 8 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from minimax import find_best_move 17 | from tictactoe import TTTBoard 18 | from board import Move, Board 19 | 20 | board: Board = TTTBoard() 21 | 22 | 23 | def get_player_move() -> Move: 24 | player_move: Move = Move(-1) 25 | while player_move not in board.legal_moves: 26 | play: int = int(input("Enter a legal square (0-8):")) 27 | player_move = Move(play) 28 | return player_move 29 | 30 | 31 | if __name__ == "__main__": 32 | # main game loop 33 | while True: 34 | human_move: Move = get_player_move() 35 | board = board.move(human_move) 36 | if board.is_win: 37 | print("Human wins!") 38 | break 39 | elif board.is_draw: 40 | print("Draw!") 41 | break 42 | computer_move: Move = find_best_move(board) 43 | print(f"Computer move is {computer_move}") 44 | board = board.move(computer_move) 45 | print(board) 46 | if board.is_win: 47 | print("Computer wins!") 48 | break 49 | elif board.is_draw: 50 | print("Draw!") 51 | break 52 | 53 | 54 | -------------------------------------------------------------------------------- /Chapter8/connectfour_ai.py: -------------------------------------------------------------------------------- 1 | # tictactoe_ai.py 2 | # From Classic Computer Science Problems in Python Chapter 8 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from minimax import find_best_move 17 | from connectfour import C4Board 18 | from board import Move, Board 19 | 20 | board: Board = C4Board() 21 | 22 | 23 | def get_player_move() -> Move: 24 | player_move: Move = Move(-1) 25 | while player_move not in board.legal_moves: 26 | play: int = int(input("Enter a legal column (0-6):")) 27 | player_move = Move(play) 28 | return player_move 29 | 30 | 31 | if __name__ == "__main__": 32 | # main game loop 33 | while True: 34 | human_move: Move = get_player_move() 35 | board = board.move(human_move) 36 | if board.is_win: 37 | print("Human wins!") 38 | break 39 | elif board.is_draw: 40 | print("Draw!") 41 | break 42 | computer_move: Move = find_best_move(board, 5) 43 | print(f"Computer move is {computer_move}") 44 | board = board.move(computer_move) 45 | print(board) 46 | if board.is_win: 47 | print("Computer wins!") 48 | break 49 | elif board.is_draw: 50 | print("Draw!") 51 | break 52 | 53 | 54 | -------------------------------------------------------------------------------- /Chapter9/phone_number_mnemonics.py: -------------------------------------------------------------------------------- 1 | # phone_number_mnemonics.py 2 | # From Classic Computer Science Problems in Python Chapter 9 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from typing import Dict, Tuple, Iterable, List 17 | from itertools import product 18 | 19 | phone_mapping: Dict[str, Tuple[str, ...]] = {"1": ("1",), 20 | "2": ("a", "b", "c"), 21 | "3": ("d", "e", "f"), 22 | "4": ("g", "h", "i"), 23 | "5": ("j", "k", "l"), 24 | "6": ("m", "n", "o"), 25 | "7": ("p", "q", "r", "s"), 26 | "8": ("t", "u", "v"), 27 | "9": ("w", "x", "y", "z"), 28 | "0": ("0",)} 29 | 30 | 31 | def possible_mnemonics(phone_number: str) -> Iterable[Tuple[str, ...]]: 32 | letter_tuples: List[Tuple[str, ...]] = [] 33 | for digit in phone_number: 34 | letter_tuples.append(phone_mapping.get(digit, (digit,))) 35 | return product(*letter_tuples) 36 | 37 | 38 | if __name__ == "__main__": 39 | phone_number: str = input("Enter a phone number:") 40 | print("Here are the potential mnemonics:") 41 | for mnemonic in possible_mnemonics(phone_number): 42 | print("".join(mnemonic)) 43 | -------------------------------------------------------------------------------- /Chapter6/mj.py: -------------------------------------------------------------------------------- 1 | # mj.py 2 | # From Classic Computer Science Problems in Python Chapter 6 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from __future__ import annotations 17 | from typing import List 18 | from data_point import DataPoint 19 | from kmeans import KMeans 20 | 21 | 22 | class Album(DataPoint): 23 | def __init__(self, name: str, year: int, length: float, tracks: float) -> None: 24 | super().__init__([length, tracks]) 25 | self.name = name 26 | self.year = year 27 | self.length = length 28 | self.tracks = tracks 29 | 30 | def __repr__(self) -> str: 31 | return f"{self.name}, {self.year}" 32 | 33 | 34 | if __name__ == "__main__": 35 | albums: List[Album] = [Album("Got to Be There", 1972, 35.45, 10), Album("Ben", 1972, 31.31, 10), 36 | Album("Music & Me", 1973, 32.09, 10), Album("Forever, Michael", 1975, 33.36, 10), 37 | Album("Off the Wall", 1979, 42.28, 10), Album("Thriller", 1982, 42.19, 9), 38 | Album("Bad", 1987, 48.16, 10), Album("Dangerous", 1991, 77.03, 14), 39 | Album("HIStory: Past, Present and Future, Book I", 1995, 148.58, 30), Album("Invincible", 2001, 77.05, 16)] 40 | kmeans: KMeans[Album] = KMeans(2, albums) 41 | clusters: List[KMeans.Cluster] = kmeans.run() 42 | for index, cluster in enumerate(clusters): 43 | print(f"Cluster {index} Avg Length {cluster.centroid.dimensions[0]} Avg Tracks {cluster.centroid.dimensions[1]}: {cluster.points}\n") 44 | -------------------------------------------------------------------------------- /Chapter3/queens.py: -------------------------------------------------------------------------------- 1 | # queens.py 2 | # From Classic Computer Science Problems in Python Chapter 3 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from csp import Constraint, CSP 17 | from typing import Dict, List, Optional 18 | 19 | 20 | class QueensConstraint(Constraint[int, int]): 21 | def __init__(self, columns: List[int]) -> None: 22 | super().__init__(columns) 23 | self.columns: List[int] = columns 24 | 25 | def satisfied(self, assignment: Dict[int, int]) -> bool: 26 | # q1c = queen 1 column, q1r = queen 1 row 27 | for q1c, q1r in assignment.items(): 28 | # q2c = queen 2 column 29 | for q2c in range(q1c + 1, len(self.columns) + 1): 30 | if q2c in assignment: 31 | q2r: int = assignment[q2c] # q2r = queen 2 row 32 | if q1r == q2r: # same row? 33 | return False 34 | if abs(q1r - q2r) == abs(q1c - q2c): # same diagonal? 35 | return False 36 | return True # no conflict 37 | 38 | 39 | if __name__ == "__main__": 40 | columns: List[int] = [1, 2, 3, 4, 5, 6, 7, 8] 41 | rows: Dict[int, List[int]] = {} 42 | for column in columns: 43 | rows[column] = [1, 2, 3, 4, 5, 6, 7, 8] 44 | csp: CSP[int, int] = CSP(columns, rows) 45 | csp.add_constraint(QueensConstraint(columns)) 46 | solution: Optional[Dict[int, int]] = csp.backtracking_search() 47 | if solution is None: 48 | print("No solution found!") 49 | else: 50 | print(solution) -------------------------------------------------------------------------------- /Chapter9/tsp.py: -------------------------------------------------------------------------------- 1 | # tsp.py 2 | # From Classic Computer Science Problems in Python Chapter 9 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from typing import Dict, List, Iterable, Tuple 17 | from itertools import permutations 18 | 19 | vt_distances: Dict[str, Dict[str, int]] = { 20 | "Rutland": 21 | {"Burlington": 67, 22 | "White River Junction": 46, 23 | "Bennington": 55, 24 | "Brattleboro": 75}, 25 | "Burlington": 26 | {"Rutland": 67, 27 | "White River Junction": 91, 28 | "Bennington": 122, 29 | "Brattleboro": 153}, 30 | "White River Junction": 31 | {"Rutland": 46, 32 | "Burlington": 91, 33 | "Bennington": 98, 34 | "Brattleboro": 65}, 35 | "Bennington": 36 | {"Rutland": 55, 37 | "Burlington": 122, 38 | "White River Junction": 98, 39 | "Brattleboro": 40}, 40 | "Brattleboro": 41 | {"Rutland": 75, 42 | "Burlington": 153, 43 | "White River Junction": 65, 44 | "Bennington": 40} 45 | } 46 | 47 | vt_cities: Iterable[str] = vt_distances.keys() 48 | city_permutations: Iterable[Tuple[str, ...]] = permutations(vt_cities) 49 | tsp_paths: List[Tuple[str, ...]] = [c + (c[0],) for c in city_permutations] 50 | 51 | if __name__ == "__main__": 52 | best_path: Tuple[str, ...] 53 | min_distance: int = 99999999999 # arbitrarily high number 54 | for path in tsp_paths: 55 | distance: int = 0 56 | last: str = path[0] 57 | for next in path[1:]: 58 | distance += vt_distances[last][next] 59 | last = next 60 | if distance < min_distance: 61 | min_distance = distance 62 | best_path = path 63 | print(f"The shortest path is {best_path} in {min_distance} miles.") 64 | 65 | 66 | -------------------------------------------------------------------------------- /Chapter8/tictactoe_tests.py: -------------------------------------------------------------------------------- 1 | # tictactoe_tests.py 2 | # From Classic Computer Science Problems in Python Chapter 8 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | import unittest 17 | from typing import List 18 | from minimax import find_best_move 19 | from tictactoe import TTTPiece, TTTBoard 20 | from board import Move 21 | 22 | 23 | class TTTMinimaxTestCase(unittest.TestCase): 24 | def test_easy_position(self): 25 | # win in 1 move 26 | to_win_easy_position: List[TTTPiece] = [TTTPiece.X, TTTPiece.O, TTTPiece.X, 27 | TTTPiece.X, TTTPiece.E, TTTPiece.O, 28 | TTTPiece.E, TTTPiece.E, TTTPiece.O] 29 | test_board1: TTTBoard = TTTBoard(to_win_easy_position, TTTPiece.X) 30 | answer1: Move = find_best_move(test_board1) 31 | self.assertEqual(answer1, 6) 32 | 33 | def test_block_position(self): 34 | # must block O's win 35 | to_block_position: List[TTTPiece] = [TTTPiece.X, TTTPiece.E, TTTPiece.E, 36 | TTTPiece.E, TTTPiece.E, TTTPiece.O, 37 | TTTPiece.E, TTTPiece.X, TTTPiece.O] 38 | test_board2: TTTBoard = TTTBoard(to_block_position, TTTPiece.X) 39 | answer2: Move = find_best_move(test_board2) 40 | self.assertEqual(answer2, 2) 41 | 42 | def test_hard_position(self): 43 | # find the best move to win 2 moves 44 | to_win_hard_position: List[TTTPiece] = [TTTPiece.X, TTTPiece.E, TTTPiece.E, 45 | TTTPiece.E, TTTPiece.E, TTTPiece.O, 46 | TTTPiece.O, TTTPiece.X, TTTPiece.E] 47 | test_board3: TTTBoard = TTTBoard(to_win_hard_position, TTTPiece.X) 48 | answer3: Move = find_best_move(test_board3) 49 | self.assertEqual(answer3, 1) 50 | 51 | 52 | if __name__ == '__main__': 53 | unittest.main() 54 | 55 | 56 | -------------------------------------------------------------------------------- /Chapter2/dna_search.py: -------------------------------------------------------------------------------- 1 | # dna_search.py 2 | # From Classic Computer Science Problems in Python Chapter 2 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from enum import IntEnum 17 | from typing import Tuple, List 18 | 19 | Nucleotide: IntEnum = IntEnum('Nucleotide', ('A', 'C', 'G', 'T')) 20 | Codon = Tuple[Nucleotide, Nucleotide, Nucleotide] # type alias for codons 21 | Gene = List[Codon] # type alias for genes 22 | 23 | gene_str: str = "ACGTGGCTCTCTAACGTACGTACGTACGGGGTTTATATATACCCTAGGACTCCCTTT" 24 | 25 | 26 | def string_to_gene(s: str) -> Gene: 27 | gene: Gene = [] 28 | for i in range(0, len(s), 3): 29 | if (i + 2) >= len(s): # don't run off end! 30 | return gene 31 | # initialize codon out of three nucleotides 32 | codon: Codon = (Nucleotide[s[i]], Nucleotide[s[i + 1]], Nucleotide[s[i + 2]]) 33 | gene.append(codon) # add codon to gene 34 | return gene 35 | 36 | 37 | my_gene: Gene = string_to_gene(gene_str) 38 | 39 | 40 | def linear_contains(gene: Gene, key_codon: Codon) -> bool: 41 | for codon in gene: 42 | if codon == key_codon: 43 | return True 44 | return False 45 | 46 | 47 | acg: Codon = (Nucleotide.A, Nucleotide.C, Nucleotide.G) 48 | gat: Codon = (Nucleotide.G, Nucleotide.A, Nucleotide.T) 49 | print(linear_contains(my_gene, acg)) # True 50 | print(linear_contains(my_gene, gat)) # False 51 | 52 | 53 | def binary_contains(gene: Gene, key_codon: Codon) -> bool: 54 | low: int = 0 55 | high: int = len(gene) - 1 56 | while low <= high: # while there is still a search space 57 | mid: int = (low + high) // 2 58 | if gene[mid] < key_codon: 59 | low = mid + 1 60 | elif gene[mid] > key_codon: 61 | high = mid - 1 62 | else: 63 | return True 64 | return False 65 | 66 | 67 | my_sorted_gene: Gene = sorted(my_gene) 68 | print(binary_contains(my_sorted_gene, acg)) # True 69 | print(binary_contains(my_sorted_gene, gat)) # False -------------------------------------------------------------------------------- /Chapter5/simple_equation.py: -------------------------------------------------------------------------------- 1 | # simple_equation.py 2 | # From Classic Computer Science Problems in Python Chapter 5 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from __future__ import annotations 17 | from typing import Tuple, List 18 | from chromosome import Chromosome 19 | from genetic_algorithm import GeneticAlgorithm 20 | from random import randrange, random 21 | from copy import deepcopy 22 | 23 | 24 | class SimpleEquation(Chromosome): 25 | def __init__(self, x: int, y: int) -> None: 26 | self.x: int = x 27 | self.y: int = y 28 | 29 | def fitness(self) -> float: # 6x - x^2 + 4y - y^2 30 | return 6 * self.x - self.x * self.x + 4 * self.y - self.y * self.y 31 | 32 | @classmethod 33 | def random_instance(cls) -> SimpleEquation: 34 | return SimpleEquation(randrange(100), randrange(100)) 35 | 36 | def crossover(self, other: SimpleEquation) -> Tuple[SimpleEquation, SimpleEquation]: 37 | child1: SimpleEquation = deepcopy(self) 38 | child2: SimpleEquation = deepcopy(other) 39 | child1.y = other.y 40 | child2.y = self.y 41 | return child1, child2 42 | 43 | def mutate(self) -> None: 44 | if random() > 0.5: # mutate x 45 | if random() > 0.5: 46 | self.x += 1 47 | else: 48 | self.x -= 1 49 | else: # otherwise mutate y 50 | if random() > 0.5: 51 | self.y += 1 52 | else: 53 | self.y -= 1 54 | 55 | def __str__(self) -> str: 56 | return f"X: {self.x} Y: {self.y} Fitness: {self.fitness()}" 57 | 58 | 59 | if __name__ == "__main__": 60 | initial_population: List[SimpleEquation] = [SimpleEquation.random_instance() for _ in range(20)] 61 | ga: GeneticAlgorithm[SimpleEquation] = GeneticAlgorithm(initial_population=initial_population, threshold=13.0, max_generations = 100, mutation_chance = 0.1, crossover_chance = 0.7) 62 | result: SimpleEquation = ga.run() 63 | print(result) 64 | -------------------------------------------------------------------------------- /Chapter3/send_more_money.py: -------------------------------------------------------------------------------- 1 | # send_more_money.py 2 | # From Classic Computer Science Problems in Python Chapter 3 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from csp import Constraint, CSP 17 | from typing import Dict, List, Optional 18 | 19 | 20 | class SendMoreMoneyConstraint(Constraint[str, int]): 21 | def __init__(self, letters: List[str]) -> None: 22 | super().__init__(letters) 23 | self.letters: List[str] = letters 24 | 25 | def satisfied(self, assignment: Dict[str, int]) -> bool: 26 | # if there are duplicate values then it's not a solution 27 | if len(set(assignment.values())) < len(assignment): 28 | return False 29 | 30 | # if all variables have been assigned, check if it adds correctly 31 | if len(assignment) == len(self.letters): 32 | s: int = assignment["S"] 33 | e: int = assignment["E"] 34 | n: int = assignment["N"] 35 | d: int = assignment["D"] 36 | m: int = assignment["M"] 37 | o: int = assignment["O"] 38 | r: int = assignment["R"] 39 | y: int = assignment["Y"] 40 | send: int = s * 1000 + e * 100 + n * 10 + d 41 | more: int = m * 1000 + o * 100 + r * 10 + e 42 | money: int = m * 10000 + o * 1000 + n * 100 + e * 10 + y 43 | return send + more == money 44 | return True # no conflict 45 | 46 | 47 | if __name__ == "__main__": 48 | letters: List[str] = ["S", "E", "N", "D", "M", "O", "R", "Y"] 49 | possible_digits: Dict[str, List[int]] = {} 50 | for letter in letters: 51 | possible_digits[letter] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 52 | possible_digits["M"] = [1] # so we don't get answers starting with a 0 53 | csp: CSP[str, int] = CSP(letters, possible_digits) 54 | csp.add_constraint(SendMoreMoneyConstraint(letters)) 55 | solution: Optional[Dict[str, int]] = csp.backtracking_search() 56 | if solution is None: 57 | print("No solution found!") 58 | else: 59 | print(solution) -------------------------------------------------------------------------------- /Chapter9/knapsack.py: -------------------------------------------------------------------------------- 1 | # knapsack.py 2 | # From Classic Computer Science Problems in Python Chapter 9 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from typing import NamedTuple, List 17 | 18 | 19 | class Item(NamedTuple): 20 | name: str 21 | weight: int 22 | value: float 23 | 24 | 25 | def knapsack(items: List[Item], max_capacity: int) -> List[Item]: 26 | # build up dynamic programming table 27 | table: List[List[float]] = [[0.0 for _ in range(max_capacity + 1)] for _ in range(len(items) + 1)] 28 | for i, item in enumerate(items): 29 | for capacity in range(1, max_capacity + 1): 30 | previous_items_value: float = table[i][capacity] 31 | if capacity >= item.weight: # item fits in knapsack 32 | value_freeing_weight_for_item: float = table[i][capacity - item.weight] 33 | # only take if more valuable than previous item 34 | table[i + 1][capacity] = max(value_freeing_weight_for_item + item.value, previous_items_value) 35 | else: # no room for this item 36 | table[i + 1][capacity] = previous_items_value 37 | # figure out solution from table 38 | solution: List[Item] = [] 39 | capacity = max_capacity 40 | for i in range(len(items), 0, -1): # work backwards 41 | # was this item used? 42 | if table[i - 1][capacity] != table[i][capacity]: 43 | solution.append(items[i - 1]) 44 | # if the item was used, remove its weight 45 | capacity -= items[i - 1].weight 46 | return solution 47 | 48 | 49 | if __name__ == "__main__": 50 | items: List[Item] = [Item("television", 50, 500), 51 | Item("candlesticks", 2, 300), 52 | Item("stereo", 35, 400), 53 | Item("laptop", 3, 1000), 54 | Item("food", 15, 50), 55 | Item("clothing", 20, 800), 56 | Item("jewelry", 1, 4000), 57 | Item("books", 100, 300), 58 | Item("printer", 18, 30), 59 | Item("refrigerator", 200, 700), 60 | Item("painting", 10, 1000)] 61 | print(knapsack(items, 75)) 62 | 63 | 64 | -------------------------------------------------------------------------------- /Chapter7/wine_test.py: -------------------------------------------------------------------------------- 1 | # wine_test.py 2 | # From Classic Computer Science Problems in Python Chapter 7 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | import csv 17 | from typing import List 18 | from util import normalize_by_feature_scaling 19 | from network import Network 20 | from random import shuffle 21 | 22 | if __name__ == "__main__": 23 | wine_parameters: List[List[float]] = [] 24 | wine_classifications: List[List[float]] = [] 25 | wine_species: List[int] = [] 26 | with open('wine.csv', mode='r') as wine_file: 27 | wines: List = list(csv.reader(wine_file, quoting=csv.QUOTE_NONNUMERIC)) 28 | shuffle(wines) # get our lines of data in random order 29 | for wine in wines: 30 | parameters: List[float] = [float(n) for n in wine[1:14]] 31 | wine_parameters.append(parameters) 32 | species: int = int(wine[0]) 33 | if species == 1: 34 | wine_classifications.append([1.0, 0.0, 0.0]) 35 | elif species == 2: 36 | wine_classifications.append([0.0, 1.0, 0.0]) 37 | else: 38 | wine_classifications.append([0.0, 0.0, 1.0]) 39 | wine_species.append(species) 40 | normalize_by_feature_scaling(wine_parameters) 41 | 42 | wine_network: Network = Network([13, 7, 3], 0.9) 43 | 44 | def wine_interpret_output(output: List[float]) -> int: 45 | if max(output) == output[0]: 46 | return 1 47 | elif max(output) == output[1]: 48 | return 2 49 | else: 50 | return 3 51 | 52 | # train over the first 150 wines 10 times 53 | wine_trainers: List[List[float]] = wine_parameters[0:150] 54 | wine_trainers_corrects: List[List[float]] = wine_classifications[0:150] 55 | for _ in range(10): 56 | wine_network.train(wine_trainers, wine_trainers_corrects) 57 | 58 | # test over the last 28 of the wines in the data set 59 | wine_testers: List[List[float]] = wine_parameters[150:178] 60 | wine_testers_corrects: List[int] = wine_species[150:178] 61 | wine_results = wine_network.validate(wine_testers, wine_testers_corrects, wine_interpret_output) 62 | print(f"{wine_results[0]} correct of {wine_results[1]} = {wine_results[2] * 100}%") 63 | 64 | 65 | -------------------------------------------------------------------------------- /Chapter7/iris_test.py: -------------------------------------------------------------------------------- 1 | # iris_test.py 2 | # From Classic Computer Science Problems in Python Chapter 7 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | import csv 17 | from typing import List 18 | from util import normalize_by_feature_scaling 19 | from network import Network 20 | from random import shuffle 21 | 22 | if __name__ == "__main__": 23 | iris_parameters: List[List[float]] = [] 24 | iris_classifications: List[List[float]] = [] 25 | iris_species: List[str] = [] 26 | with open('iris.csv', mode='r') as iris_file: 27 | irises: List = list(csv.reader(iris_file)) 28 | shuffle(irises) # get our lines of data in random order 29 | for iris in irises: 30 | parameters: List[float] = [float(n) for n in iris[0:4]] 31 | iris_parameters.append(parameters) 32 | species: str = iris[4] 33 | if species == "Iris-setosa": 34 | iris_classifications.append([1.0, 0.0, 0.0]) 35 | elif species == "Iris-versicolor": 36 | iris_classifications.append([0.0, 1.0, 0.0]) 37 | else: 38 | iris_classifications.append([0.0, 0.0, 1.0]) 39 | iris_species.append(species) 40 | normalize_by_feature_scaling(iris_parameters) 41 | 42 | iris_network: Network = Network([4, 6, 3], 0.3) 43 | 44 | def iris_interpret_output(output: List[float]) -> str: 45 | if max(output) == output[0]: 46 | return "Iris-setosa" 47 | elif max(output) == output[1]: 48 | return "Iris-versicolor" 49 | else: 50 | return "Iris-virginica" 51 | 52 | # train over the first 140 irises in the data set 50 times 53 | iris_trainers: List[List[float]] = iris_parameters[0:140] 54 | iris_trainers_corrects: List[List[float]] = iris_classifications[0:140] 55 | for _ in range(50): 56 | iris_network.train(iris_trainers, iris_trainers_corrects) 57 | 58 | # test over the last 10 of the irises in the data set 59 | iris_testers: List[List[float]] = iris_parameters[140:150] 60 | iris_testers_corrects: List[str] = iris_species[140:150] 61 | iris_results = iris_network.validate(iris_testers, iris_testers_corrects, iris_interpret_output) 62 | print(f"{iris_results[0]} correct of {iris_results[1]} = {iris_results[2] * 100}%") 63 | 64 | 65 | -------------------------------------------------------------------------------- /Chapter3/map_coloring.py: -------------------------------------------------------------------------------- 1 | # map_coloring.py 2 | # From Classic Computer Science Problems in Python Chapter 3 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from csp import Constraint, CSP 17 | from typing import Dict, List, Optional 18 | 19 | 20 | class MapColoringConstraint(Constraint[str, str]): 21 | def __init__(self, place1: str, place2: str) -> None: 22 | super().__init__([place1, place2]) 23 | self.place1: str = place1 24 | self.place2: str = place2 25 | 26 | def satisfied(self, assignment: Dict[str, str]) -> bool: 27 | # If either place is not in the assignment then it is not 28 | # yet possible for their colors to be conflicting 29 | if self.place1 not in assignment or self.place2 not in assignment: 30 | return True 31 | # check the color assigned to place1 is not the same as the 32 | # color assigned to place2 33 | return assignment[self.place1] != assignment[self.place2] 34 | 35 | 36 | if __name__ == "__main__": 37 | variables: List[str] = ["Western Australia", "Northern Territory", "South Australia", 38 | "Queensland", "New South Wales", "Victoria", "Tasmania"] 39 | domains: Dict[str, List[str]] = {} 40 | for variable in variables: 41 | domains[variable] = ["red", "green", "blue"] 42 | csp: CSP[str, str] = CSP(variables, domains) 43 | csp.add_constraint(MapColoringConstraint("Western Australia", "Northern Territory")) 44 | csp.add_constraint(MapColoringConstraint("Western Australia", "South Australia")) 45 | csp.add_constraint(MapColoringConstraint("South Australia", "Northern Territory")) 46 | csp.add_constraint(MapColoringConstraint("Queensland", "Northern Territory")) 47 | csp.add_constraint(MapColoringConstraint("Queensland", "South Australia")) 48 | csp.add_constraint(MapColoringConstraint("Queensland", "New South Wales")) 49 | csp.add_constraint(MapColoringConstraint("New South Wales", "South Australia")) 50 | csp.add_constraint(MapColoringConstraint("Victoria", "South Australia")) 51 | csp.add_constraint(MapColoringConstraint("Victoria", "New South Wales")) 52 | csp.add_constraint(MapColoringConstraint("Victoria", "Tasmania")) 53 | solution: Optional[Dict[str, str]] = csp.backtracking_search() 54 | if solution is None: 55 | print("No solution found!") 56 | else: 57 | print(solution) -------------------------------------------------------------------------------- /Chapter1/trivial_compression.py: -------------------------------------------------------------------------------- 1 | # trivial_compression.py 2 | # From Classic Computer Science Problems in Python Chapter 1 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | class CompressedGene: 19 | def __init__(self, gene: str) -> None: 20 | self._compress(gene) 21 | 22 | def _compress(self, gene: str) -> None: 23 | self.bit_string: int = 1 # start with sentinel 24 | for nucleotide in gene.upper(): 25 | self.bit_string <<= 2 # shift left two bits 26 | if nucleotide == "A": # change last two bits to 00 27 | self.bit_string |= 0b00 28 | elif nucleotide == "C": # change last two bits to 01 29 | self.bit_string |= 0b01 30 | elif nucleotide == "G": # change last two bits to 10 31 | self.bit_string |= 0b10 32 | elif nucleotide == "T": # change last two bits to 11 33 | self.bit_string |= 0b11 34 | else: 35 | raise ValueError("Invalid Nucleotide:{}".format(nucleotide)) 36 | 37 | def decompress(self) -> str: 38 | gene: str = "" 39 | for i in range(0, self.bit_string.bit_length() - 1, 2): # - 1 to exclude sentinel 40 | bits: int = self.bit_string >> i & 0b11 # get just 2 relevant bits 41 | if bits == 0b00: # A 42 | gene += "A" 43 | elif bits == 0b01: # C 44 | gene += "C" 45 | elif bits == 0b10: # G 46 | gene += "G" 47 | elif bits == 0b11: # T 48 | gene += "T" 49 | else: 50 | raise ValueError("Invalid bits:{}".format(bits)) 51 | return gene[::-1] # [::-1] reverses string by slicing backwards 52 | 53 | def __str__(self) -> str: # string representation for pretty printing 54 | return self.decompress() 55 | 56 | 57 | if __name__ == "__main__": 58 | from sys import getsizeof 59 | original: str = "TAGGGATTAACCGTTATATATATATAGCCATGGATCGATTATATAGGGATTAACCGTTATATATATATAGCCATGGATCGATTATA" * 100 60 | print("original is {} bytes".format(getsizeof(original))) 61 | compressed: CompressedGene = CompressedGene(original) # compress 62 | print("compressed is {} bytes".format(getsizeof(compressed.bit_string))) 63 | print(compressed) # decompress 64 | print("original and decompressed are the same: {}".format(original == compressed.decompress())) -------------------------------------------------------------------------------- /Chapter7/layer.py: -------------------------------------------------------------------------------- 1 | # layer.py 2 | # From Classic Computer Science Problems in Python Chapter 7 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from __future__ import annotations 17 | from typing import List, Callable, Optional 18 | from random import random 19 | from neuron import Neuron 20 | from util import dot_product 21 | 22 | 23 | class Layer: 24 | def __init__(self, previous_layer: Optional[Layer], num_neurons: int, learning_rate: float, activation_function: Callable[[float], float], derivative_activation_function: Callable[[float], float]) -> None: 25 | self.previous_layer: Optional[Layer] = previous_layer 26 | self.neurons: List[Neuron] = [] 27 | # the following could all be one large list comprehension, but gets a bit long that way 28 | for i in range(num_neurons): 29 | if previous_layer is None: 30 | random_weights: List[float] = [] 31 | else: 32 | random_weights = [random() for _ in range(len(previous_layer.neurons))] 33 | neuron: Neuron = Neuron(random_weights, learning_rate, activation_function, derivative_activation_function) 34 | self.neurons.append(neuron) 35 | self.output_cache: List[float] = [0.0 for _ in range(num_neurons)] 36 | 37 | def outputs(self, inputs: List[float]) -> List[float]: 38 | if self.previous_layer is None: 39 | self.output_cache = inputs 40 | else: 41 | self.output_cache = [n.output(inputs) for n in self.neurons] 42 | return self.output_cache 43 | 44 | # should only be called on output layer 45 | def calculate_deltas_for_output_layer(self, expected: List[float]) -> None: 46 | for n in range(len(self.neurons)): 47 | self.neurons[n].delta = self.neurons[n].derivative_activation_function(self.neurons[n].output_cache) * (expected[n] - self.output_cache[n]) 48 | 49 | # should not be called on output layer 50 | def calculate_deltas_for_hidden_layer(self, next_layer: Layer) -> None: 51 | for index, neuron in enumerate(self.neurons): 52 | next_weights: List[float] = [n.weights[index] for n in next_layer.neurons] 53 | next_deltas: List[float] = [n.delta for n in next_layer.neurons] 54 | sum_weights_and_deltas: float = dot_product(next_weights, next_deltas) 55 | neuron.delta = neuron.derivative_activation_function(neuron.output_cache) * sum_weights_and_deltas 56 | 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Classic Computer Science Problems in Python 2 | This repository contains source code to accompany the book *Classic Computer Science Problems in Python* by David Kopec. You will find the source organized by chapter. **As you read the book, each code listing contains a file name that corresponds to a file in this repository.** 3 | 4 | ![Classic Computer Science Problems in Python Cover](cover.jpg) 5 | 6 | ## Get the Book 7 | - [Manning](https://www.manning.com/books/classic-computer-science-problems-in-python?a_aid=oaksnow&a_bid=d326fe0b) the publisher sells both hard copy and DRM-free eBook editions 8 | - [Amazon](https://amzn.to/2ui96Op) if you buy the hard copy from Amazon, it will come with a way to download the eBook for free from the publisher 9 | 10 | ## Versioning and Packages 11 | The source code in this repository requires Python 3.7 and installation of the [typing_extensions](https://github.com/python/typing/tree/master/typing_extensions) package. Due to its extensive use of Python 3.7 features (data classes, advanced type hints, etc.), most of the source code will not work with earlier versions of Python. You can install the `typing_extensions` package with `pip3 install typing_extensions` or `pip install typing_extensions` depending on your Python/pip setup. 12 | 13 | ## Questions about the Book 14 | You can find general questions and descriptive information about the book on the [Classic Computer Science Problems](https://classicproblems.com/) website. Also, feel free to reach out to me on Twitter, [@davekopec](https://twitter.com/davekopec). If you think you found an error in the source code, please open an issue up here on GitHub. 15 | 16 | ## Free Content Based on the Book 17 | - [Article: Constraint-Satisfaction Problems in Python](https://freecontent.manning.com/constraint-satisfaction-problems-in-python/) 18 | 19 | ## License 20 | All of the source code in this repository is released under the Apache License version 2.0. See `LICENSE`. 21 | 22 | ## Other Books and Languages 23 | Official Books from the Series by @davecom 24 | - [Classic Computer Science Problems in Java](https://github.com/davecom/ClassicComputerScienceProblemsInJava) 25 | - [Classic Computer Science Problems in Swift](https://github.com/davecom/ClassicComputerScienceProblemsInSwift) 26 | 27 | My Latest Book 28 | - [Computer Science from Scratch: Building Interpreters, Art, Emulators and ML in Python](https://github.com/davecom/ComputerScienceFromScratch) 29 | 30 | Ports 31 | - [C++ implementation by @aray-andres](https://github.com/araya-andres/classic_computer_sci) 32 | - [Go implementation by @arlima](https://github.com/arlima/problemas_classicos_CC) 33 | - [PHP implementation by @SaschaKersken (German translator of CCSPiP)](https://github.com/SaschaKersken/ClassicComputerScienceProblemsInPhp) 34 | - [JavaScript implementation by @SaschaKersken (German translator of CCSPiP)](https://github.com/SaschaKersken/ClassicComputerScienceProblemsInJavaScript) 35 | - [Ruby implementation by @tj84](https://github.com/tj84/cs_problems) 36 | - [Rust implementation by @marpetercontribs](https://github.com/marpetercontribs/classic-computer-science-problems-in-rust) 37 | -------------------------------------------------------------------------------- /Chapter5/list_compression.py: -------------------------------------------------------------------------------- 1 | # list_compression.py 2 | # From Classic Computer Science Problems in Python Chapter 5 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from __future__ import annotations 17 | from typing import Tuple, List, Any 18 | from chromosome import Chromosome 19 | from genetic_algorithm import GeneticAlgorithm 20 | from random import shuffle, sample 21 | from copy import deepcopy 22 | from zlib import compress 23 | from sys import getsizeof 24 | from pickle import dumps 25 | 26 | # 165 bytes compressed 27 | PEOPLE: List[str] = ["Michael", "Sarah", "Joshua", "Narine", "David", "Sajid", "Melanie", "Daniel", "Wei", "Dean", "Brian", "Murat", "Lisa"] 28 | 29 | 30 | class ListCompression(Chromosome): 31 | def __init__(self, lst: List[Any]) -> None: 32 | self.lst: List[Any] = lst 33 | 34 | @property 35 | def bytes_compressed(self) -> int: 36 | return getsizeof(compress(dumps(self.lst))) 37 | 38 | def fitness(self) -> float: 39 | return 1 / self.bytes_compressed 40 | 41 | @classmethod 42 | def random_instance(cls) -> ListCompression: 43 | mylst: List[str] = deepcopy(PEOPLE) 44 | shuffle(mylst) 45 | return ListCompression(mylst) 46 | 47 | def crossover(self, other: ListCompression) -> Tuple[ListCompression, ListCompression]: 48 | child1: ListCompression = deepcopy(self) 49 | child2: ListCompression = deepcopy(other) 50 | idx1, idx2 = sample(range(len(self.lst)), k=2) 51 | l1, l2 = child1.lst[idx1], child2.lst[idx2] 52 | child1.lst[child1.lst.index(l2)], child1.lst[idx2] = child1.lst[idx2], l2 53 | child2.lst[child2.lst.index(l1)], child2.lst[idx1] = child2.lst[idx1], l1 54 | return child1, child2 55 | 56 | def mutate(self) -> None: # swap two locations 57 | idx1, idx2 = sample(range(len(self.lst)), k=2) 58 | self.lst[idx1], self.lst[idx2] = self.lst[idx2], self.lst[idx1] 59 | 60 | def __str__(self) -> str: 61 | return f"Order: {self.lst} Bytes: {self.bytes_compressed}" 62 | 63 | 64 | if __name__ == "__main__": 65 | initial_population: List[ListCompression] = [ListCompression.random_instance() for _ in range(100)] 66 | ga: GeneticAlgorithm[ListCompression] = GeneticAlgorithm(initial_population=initial_population, threshold=1.0, max_generations = 100, mutation_chance = 0.2, crossover_chance = 0.7, selection_type=GeneticAlgorithm.SelectionType.TOURNAMENT) 67 | result: ListCompression = ga.run() 68 | print(result) 69 | 70 | # Best I found 71 | # 546th generation with 1000 individuals in each 72 | # Order: ['Wei', 'Michael', 'Melanie', 'Daniel', 'Joshua', 'Narine', 'Lisa', 'Dean', 'Brian', 'David', 'Sajid', 'Sarah', 'Murat'] Bytes: 159 73 | -------------------------------------------------------------------------------- /Chapter8/minimax.py: -------------------------------------------------------------------------------- 1 | # minimax.py 2 | # From Classic Computer Science Problems in Python Chapter 8 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from __future__ import annotations 17 | from board import Piece, Board, Move 18 | 19 | 20 | # Find the best possible outcome for original player 21 | def minimax(board: Board, maximizing: bool, original_player: Piece, max_depth: int = 8) -> float: 22 | # Base case – terminal position or maximum depth reached 23 | if board.is_win or board.is_draw or max_depth == 0: 24 | return board.evaluate(original_player) 25 | 26 | # Recursive case - maximize your gains or minimize the opponent's gains 27 | if maximizing: 28 | best_eval: float = float("-inf") # arbitrarily low starting point 29 | for move in board.legal_moves: 30 | result: float = minimax(board.move(move), False, original_player, max_depth - 1) 31 | best_eval = max(result, best_eval) # we want the move with the highest evaluation 32 | return best_eval 33 | else: # minimizing 34 | worst_eval: float = float("inf") 35 | for move in board.legal_moves: 36 | result = minimax(board.move(move), True, original_player, max_depth - 1) 37 | worst_eval = min(result, worst_eval) # we want the move with the lowest evaluation 38 | return worst_eval 39 | 40 | 41 | def alphabeta(board: Board, maximizing: bool, original_player: Piece, max_depth: int = 8, alpha: float = float("-inf"), beta: float = float("inf")) -> float: 42 | # Base case – terminal position or maximum depth reached 43 | if board.is_win or board.is_draw or max_depth == 0: 44 | return board.evaluate(original_player) 45 | 46 | # Recursive case - maximize your gains or minimize the opponent's gains 47 | if maximizing: 48 | for move in board.legal_moves: 49 | result: float = alphabeta(board.move(move), False, original_player, max_depth - 1, alpha, beta) 50 | alpha = max(result, alpha) 51 | if beta <= alpha: 52 | break 53 | return alpha 54 | else: # minimizing 55 | for move in board.legal_moves: 56 | result = alphabeta(board.move(move), True, original_player, max_depth - 1, alpha, beta) 57 | beta = min(result, beta) 58 | if beta <= alpha: 59 | break 60 | return beta 61 | 62 | 63 | # Find the best possible move in the current position 64 | # looking up to max_depth ahead 65 | def find_best_move(board: Board, max_depth: int = 8) -> Move: 66 | best_eval: float = float("-inf") 67 | best_move: Move = Move(-1) 68 | for move in board.legal_moves: 69 | result: float = alphabeta(board.move(move), False, board.turn, max_depth) 70 | if result > best_eval: 71 | best_eval = result 72 | best_move = move 73 | return best_move -------------------------------------------------------------------------------- /Chapter8/tictactoe.py: -------------------------------------------------------------------------------- 1 | # tictactoe.py 2 | # From Classic Computer Science Problems in Python Chapter 8 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from __future__ import annotations 17 | from typing import List 18 | from enum import Enum 19 | from board import Piece, Board, Move 20 | 21 | 22 | class TTTPiece(Piece, Enum): 23 | X = "X" 24 | O = "O" 25 | E = " " # stand-in for empty 26 | 27 | @property 28 | def opposite(self) -> TTTPiece: 29 | if self == TTTPiece.X: 30 | return TTTPiece.O 31 | elif self == TTTPiece.O: 32 | return TTTPiece.X 33 | else: 34 | return TTTPiece.E 35 | 36 | def __str__(self) -> str: 37 | return self.value 38 | 39 | 40 | class TTTBoard(Board): 41 | def __init__(self, position: List[TTTPiece] = [TTTPiece.E] * 9, turn: TTTPiece = TTTPiece.X) -> None: 42 | self.position: List[TTTPiece] = position 43 | self._turn: TTTPiece = turn 44 | 45 | @property 46 | def turn(self) -> Piece: 47 | return self._turn 48 | 49 | def move(self, location: Move) -> Board: 50 | temp_position: List[TTTPiece] = self.position.copy() 51 | temp_position[location] = self._turn 52 | return TTTBoard(temp_position, self._turn.opposite) 53 | 54 | @property 55 | def legal_moves(self) -> List[Move]: 56 | return [Move(l) for l in range(len(self.position)) if self.position[l] == TTTPiece.E] 57 | 58 | @property 59 | def is_win(self) -> bool: 60 | # three row, three column, and then two diagonal checks 61 | return self.position[0] == self.position[1] and self.position[0] == self.position[2] and self.position[0] != TTTPiece.E or \ 62 | self.position[3] == self.position[4] and self.position[3] == self.position[5] and self.position[3] != TTTPiece.E or \ 63 | self.position[6] == self.position[7] and self.position[6] == self.position[8] and self.position[6] != TTTPiece.E or \ 64 | self.position[0] == self.position[3] and self.position[0] == self.position[6] and self.position[0] != TTTPiece.E or \ 65 | self.position[1] == self.position[4] and self.position[1] == self.position[7] and self.position[1] != TTTPiece.E or \ 66 | self.position[2] == self.position[5] and self.position[2] == self.position[8] and self.position[2] != TTTPiece.E or \ 67 | self.position[0] == self.position[4] and self.position[0] == self.position[8] and self.position[0] != TTTPiece.E or \ 68 | self.position[2] == self.position[4] and self.position[2] == self.position[6] and self.position[2] != TTTPiece.E 69 | 70 | def evaluate(self, player: Piece) -> float: 71 | if self.is_win and self.turn == player: 72 | return -1 73 | elif self.is_win and self.turn != player: 74 | return 1 75 | else: 76 | return 0 77 | 78 | def __repr__(self) -> str: 79 | return f"""{self.position[0]}|{self.position[1]}|{self.position[2]} 80 | ----- 81 | {self.position[3]}|{self.position[4]}|{self.position[5]} 82 | ----- 83 | {self.position[6]}|{self.position[7]}|{self.position[8]}""" 84 | -------------------------------------------------------------------------------- /Chapter3/csp.py: -------------------------------------------------------------------------------- 1 | # csp.py 2 | # From Classic Computer Science Problems in Python Chapter 3 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from typing import Generic, TypeVar, Dict, List, Optional 17 | from abc import ABC, abstractmethod 18 | 19 | V = TypeVar('V') # variable type 20 | D = TypeVar('D') # domain type 21 | 22 | 23 | # Base class for all constraints 24 | class Constraint(Generic[V, D], ABC): 25 | # The variables that the constraint is between 26 | def __init__(self, variables: List[V]) -> None: 27 | self.variables = variables 28 | 29 | # Must be overridden by subclasses 30 | @abstractmethod 31 | def satisfied(self, assignment: Dict[V, D]) -> bool: 32 | ... 33 | 34 | 35 | # A constraint satisfaction problem consists of variables of type V 36 | # that have ranges of values known as domains of type D and constraints 37 | # that determine whether a particular variable's domain selection is valid 38 | class CSP(Generic[V, D]): 39 | def __init__(self, variables: List[V], domains: Dict[V, List[D]]) -> None: 40 | self.variables: List[V] = variables # variables to be constrained 41 | self.domains: Dict[V, List[D]] = domains # domain of each variable 42 | self.constraints: Dict[V, List[Constraint[V, D]]] = {} 43 | for variable in self.variables: 44 | self.constraints[variable] = [] 45 | if variable not in self.domains: 46 | raise LookupError("Every variable should have a domain assigned to it.") 47 | 48 | def add_constraint(self, constraint: Constraint[V, D]) -> None: 49 | for variable in constraint.variables: 50 | if variable not in self.variables: 51 | raise LookupError("Variable in constraint not in CSP") 52 | else: 53 | self.constraints[variable].append(constraint) 54 | 55 | # Check if the value assignment is consistent by checking all constraints 56 | # for the given variable against it 57 | def consistent(self, variable: V, assignment: Dict[V, D]) -> bool: 58 | for constraint in self.constraints[variable]: 59 | if not constraint.satisfied(assignment): 60 | return False 61 | return True 62 | 63 | def backtracking_search(self, assignment: Dict[V, D] = {}) -> Optional[Dict[V, D]]: 64 | # assignment is complete if every variable is assigned (our base case) 65 | if len(assignment) == len(self.variables): 66 | return assignment 67 | 68 | # get all variables in the CSP but not in the assignment 69 | unassigned: List[V] = [v for v in self.variables if v not in assignment] 70 | 71 | # get the every possible domain value of the first unassigned variable 72 | first: V = unassigned[0] 73 | for value in self.domains[first]: 74 | local_assignment = assignment.copy() 75 | local_assignment[first] = value 76 | # if we're still consistent, we recurse (continue) 77 | if self.consistent(first, local_assignment): 78 | result: Optional[Dict[V, D]] = self.backtracking_search(local_assignment) 79 | # if we didn't find the result, we will end up backtracking 80 | if result is not None: 81 | return result 82 | return None 83 | -------------------------------------------------------------------------------- /Chapter6/governors.py: -------------------------------------------------------------------------------- 1 | # governors.py 2 | # From Classic Computer Science Problems in Python Chapter 6 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from __future__ import annotations 17 | from typing import List 18 | from data_point import DataPoint 19 | from kmeans import KMeans 20 | 21 | 22 | class Governor(DataPoint): 23 | def __init__(self, longitude: float, age: float, state: str) -> None: 24 | super().__init__([longitude, age]) 25 | self.longitude = longitude 26 | self.age = age 27 | self.state = state 28 | 29 | def __repr__(self) -> str: 30 | return f"{self.state}: (longitude: {self.longitude}, age: {self.age})" 31 | 32 | 33 | if __name__ == "__main__": 34 | governors: List[Governor] = [Governor(-86.79113, 72, "Alabama"), Governor(-152.404419, 66, "Alaska"), 35 | Governor(-111.431221, 53, "Arizona"), Governor(-92.373123, 66, "Arkansas"), 36 | Governor(-119.681564, 79, "California"), Governor(-105.311104, 65, "Colorado"), 37 | Governor(-72.755371, 61, "Connecticut"), Governor(-75.507141, 61, "Delaware"), 38 | Governor(-81.686783, 64, "Florida"), Governor(-83.643074, 74, "Georgia"), 39 | Governor(-157.498337, 60, "Hawaii"), Governor(-114.478828, 75, "Idaho"), 40 | Governor(-88.986137, 60, "Illinois"), Governor(-86.258278, 49, "Indiana"), 41 | Governor(-93.210526, 57, "Iowa"), Governor(-96.726486, 60, "Kansas"), 42 | Governor(-84.670067, 50, "Kentucky"), Governor(-91.867805, 50, "Louisiana"), 43 | Governor(-69.381927, 68, "Maine"), Governor(-76.802101, 61, "Maryland"), 44 | Governor(-71.530106, 60, "Massachusetts"), Governor(-84.536095, 58, "Michigan"), 45 | Governor(-93.900192, 70, "Minnesota"), Governor(-89.678696, 62, "Mississippi"), 46 | Governor(-92.288368, 43, "Missouri"), Governor(-110.454353, 51, "Montana"), 47 | Governor(-98.268082, 52, "Nebraska"), Governor(-117.055374, 53, "Nevada"), 48 | Governor(-71.563896, 42, "New Hampshire"), Governor(-74.521011, 54, "New Jersey"), 49 | Governor(-106.248482, 57, "New Mexico"), Governor(-74.948051, 59, "New York"), 50 | Governor(-79.806419, 60, "North Carolina"), Governor(-99.784012, 60, "North Dakota"), 51 | Governor(-82.764915, 65, "Ohio"), Governor(-96.928917, 62, "Oklahoma"), 52 | Governor(-122.070938, 56, "Oregon"), Governor(-77.209755, 68, "Pennsylvania"), 53 | Governor(-71.51178, 46, "Rhode Island"), Governor(-80.945007, 70, "South Carolina"), 54 | Governor(-99.438828, 64, "South Dakota"), Governor(-86.692345, 58, "Tennessee"), 55 | Governor(-97.563461, 59, "Texas"), Governor(-111.862434, 70, "Utah"), 56 | Governor(-72.710686, 58, "Vermont"), Governor(-78.169968, 60, "Virginia"), 57 | Governor(-121.490494, 66, "Washington"), Governor(-80.954453, 66, "West Virginia"), 58 | Governor(-89.616508, 49, "Wisconsin"), Governor(-107.30249, 55, "Wyoming")] 59 | kmeans: KMeans[Governor] = KMeans(2, governors) 60 | gov_clusters: List[KMeans.Cluster] = kmeans.run() 61 | for index, cluster in enumerate(gov_clusters): 62 | print(f"Cluster {index}: {cluster.points}\n") 63 | -------------------------------------------------------------------------------- /Chapter5/send_more_money2.py: -------------------------------------------------------------------------------- 1 | # send_more_money2.py 2 | # From Classic Computer Science Problems in Python Chapter 5 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from __future__ import annotations 17 | from typing import Tuple, List 18 | from chromosome import Chromosome 19 | from genetic_algorithm import GeneticAlgorithm 20 | from random import shuffle, sample 21 | from copy import deepcopy 22 | 23 | 24 | class SendMoreMoney2(Chromosome): 25 | def __init__(self, letters: List[str]) -> None: 26 | self.letters: List[str] = letters 27 | 28 | def fitness(self) -> float: 29 | s: int = self.letters.index("S") 30 | e: int = self.letters.index("E") 31 | n: int = self.letters.index("N") 32 | d: int = self.letters.index("D") 33 | m: int = self.letters.index("M") 34 | o: int = self.letters.index("O") 35 | r: int = self.letters.index("R") 36 | y: int = self.letters.index("Y") 37 | send: int = s * 1000 + e * 100 + n * 10 + d 38 | more: int = m * 1000 + o * 100 + r * 10 + e 39 | money: int = m * 10000 + o * 1000 + n * 100 + e * 10 + y 40 | difference: int = abs(money - (send + more)) 41 | return 1 / (difference + 1) 42 | 43 | @classmethod 44 | def random_instance(cls) -> SendMoreMoney2: 45 | letters = ["S", "E", "N", "D", "M", "O", "R", "Y", " ", " "] 46 | shuffle(letters) 47 | return SendMoreMoney2(letters) 48 | 49 | def crossover(self, other: SendMoreMoney2) -> Tuple[SendMoreMoney2, SendMoreMoney2]: 50 | child1: SendMoreMoney2 = deepcopy(self) 51 | child2: SendMoreMoney2 = deepcopy(other) 52 | idx1, idx2 = sample(range(len(self.letters)), k=2) 53 | l1, l2 = child1.letters[idx1], child2.letters[idx2] 54 | child1.letters[child1.letters.index(l2)], child1.letters[idx2] = child1.letters[idx2], l2 55 | child2.letters[child2.letters.index(l1)], child2.letters[idx1] = child2.letters[idx1], l1 56 | return child1, child2 57 | 58 | def mutate(self) -> None: # swap two letters' locations 59 | idx1, idx2 = sample(range(len(self.letters)), k=2) 60 | self.letters[idx1], self.letters[idx2] = self.letters[idx2], self.letters[idx1] 61 | 62 | def __str__(self) -> str: 63 | s: int = self.letters.index("S") 64 | e: int = self.letters.index("E") 65 | n: int = self.letters.index("N") 66 | d: int = self.letters.index("D") 67 | m: int = self.letters.index("M") 68 | o: int = self.letters.index("O") 69 | r: int = self.letters.index("R") 70 | y: int = self.letters.index("Y") 71 | send: int = s * 1000 + e * 100 + n * 10 + d 72 | more: int = m * 1000 + o * 100 + r * 10 + e 73 | money: int = m * 10000 + o * 1000 + n * 100 + e * 10 + y 74 | difference: int = abs(money - (send + more)) 75 | return f"{send} + {more} = {money} Difference: {difference}" 76 | 77 | 78 | if __name__ == "__main__": 79 | initial_population: List[SendMoreMoney2] = [SendMoreMoney2.random_instance() for _ in range(1000)] 80 | ga: GeneticAlgorithm[SendMoreMoney2] = GeneticAlgorithm(initial_population=initial_population, threshold=1.0, max_generations = 1000, mutation_chance = 0.2, crossover_chance = 0.7, selection_type=GeneticAlgorithm.SelectionType.ROULETTE) 81 | result: SendMoreMoney2 = ga.run() 82 | print(result) 83 | -------------------------------------------------------------------------------- /Chapter3/word_search.py: -------------------------------------------------------------------------------- 1 | # word_search.py 2 | # From Classic Computer Science Problems in Python Chapter 3 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from typing import NamedTuple, List, Dict, Optional 17 | from random import choice 18 | from string import ascii_uppercase 19 | from csp import CSP, Constraint 20 | 21 | Grid = List[List[str]] # type alias for grids 22 | 23 | 24 | class GridLocation(NamedTuple): 25 | row: int 26 | column: int 27 | 28 | 29 | def generate_grid(rows: int, columns: int) -> Grid: 30 | # initialize grid with random letters 31 | return [[choice(ascii_uppercase) for c in range(columns)] for r in range(rows)] 32 | 33 | 34 | def display_grid(grid: Grid) -> None: 35 | for row in grid: 36 | print("".join(row)) 37 | 38 | 39 | def generate_domain(word: str, grid: Grid) -> List[List[GridLocation]]: 40 | domain: List[List[GridLocation]] = [] 41 | height: int = len(grid) 42 | width: int = len(grid[0]) 43 | length: int = len(word) 44 | for row in range(height): 45 | for col in range(width): 46 | columns: range = range(col, col + length) 47 | rows: range = range(row, row + length) 48 | if col + length <= width: 49 | # left to right 50 | domain.append([GridLocation(row, c) for c in columns]) 51 | # diagonal towards bottom right 52 | if row + length <= height: 53 | domain.append([GridLocation(r, col + (r - row)) for r in rows]) 54 | if row + length <= height: 55 | # top to bottom 56 | domain.append([GridLocation(r, col) for r in rows]) 57 | # diagonal towards bottom left 58 | if col + 1 - length >= 0: 59 | domain.append([GridLocation(r, col - (r - row)) for r in rows]) 60 | return domain 61 | 62 | 63 | class WordSearchConstraint(Constraint[str, List[GridLocation]]): 64 | def __init__(self, words: List[str]) -> None: 65 | super().__init__(words) 66 | self.words: List[str] = words 67 | 68 | def satisfied(self, assignment: Dict[str, List[GridLocation]]) -> bool: 69 | # if there are any duplicates grid locations then there is an overlap 70 | all_locations = [locs for values in assignment.values() for locs in values] 71 | return len(set(all_locations)) == len(all_locations) 72 | 73 | 74 | if __name__ == "__main__": 75 | grid: Grid = generate_grid(9, 9) 76 | words: List[str] = ["MATTHEW", "JOE", "MARY", "SARAH", "SALLY"] 77 | locations: Dict[str, List[List[GridLocation]]] = {} 78 | for word in words: 79 | locations[word] = generate_domain(word, grid) 80 | csp: CSP[str, List[GridLocation]] = CSP(words, locations) 81 | csp.add_constraint(WordSearchConstraint(words)) 82 | solution: Optional[Dict[str, List[GridLocation]]] = csp.backtracking_search() 83 | if solution is None: 84 | print("No solution found!") 85 | else: 86 | for word, grid_locations in solution.items(): 87 | # random reverse half the time 88 | if choice([True, False]): 89 | grid_locations.reverse() 90 | for index, letter in enumerate(word): 91 | (row, col) = (grid_locations[index].row, grid_locations[index].column) 92 | grid[row][col] = letter 93 | display_grid(grid) 94 | -------------------------------------------------------------------------------- /Chapter4/weighted_graph.py: -------------------------------------------------------------------------------- 1 | # weighted_graph.py 2 | # From Classic Computer Science Problems in Python Chapter 4 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from typing import TypeVar, Generic, List, Tuple, Optional 17 | from graph import Graph 18 | from weighted_edge import WeightedEdge 19 | 20 | V = TypeVar('V') # type of the vertices in the graph 21 | 22 | 23 | class WeightedGraph(Generic[V], Graph[V]): 24 | def __init__(self, vertices: Optional[List[V]] = None) -> None: 25 | if vertices is None: 26 | vertices = [] 27 | self._vertices: List[V] = vertices 28 | self._edges: List[List[WeightedEdge]] = [[] for _ in vertices] 29 | 30 | def add_edge_by_indices(self, u: int, v: int, weight: float) -> None: 31 | edge: WeightedEdge = WeightedEdge(u, v, weight) 32 | self.add_edge(edge) # call superclass version 33 | 34 | def add_edge_by_vertices(self, first: V, second: V, weight: float) -> None: 35 | u: int = self._vertices.index(first) 36 | v: int = self._vertices.index(second) 37 | self.add_edge_by_indices(u, v, weight) 38 | 39 | def neighbors_for_index_with_weights(self, index: int) -> List[Tuple[V, float]]: 40 | distance_tuples: List[Tuple[V, float]] = [] 41 | for edge in self.edges_for_index(index): 42 | distance_tuples.append((self.vertex_at(edge.v), edge.weight)) 43 | return distance_tuples 44 | 45 | def __str__(self) -> str: 46 | desc: str = "" 47 | for i in range(self.vertex_count): 48 | desc += f"{self.vertex_at(i)} -> {self.neighbors_for_index_with_weights(i)}\n" 49 | return desc 50 | 51 | 52 | if __name__ == "__main__": 53 | city_graph2: WeightedGraph[str] = WeightedGraph(["Seattle", "San Francisco", "Los Angeles", "Riverside", "Phoenix", "Chicago", "Boston", "New York", "Atlanta", "Miami", "Dallas", "Houston", "Detroit", "Philadelphia", "Washington"]) 54 | 55 | city_graph2.add_edge_by_vertices("Seattle", "Chicago", 1737) 56 | city_graph2.add_edge_by_vertices("Seattle", "San Francisco", 678) 57 | city_graph2.add_edge_by_vertices("San Francisco", "Riverside", 386) 58 | city_graph2.add_edge_by_vertices("San Francisco", "Los Angeles", 348) 59 | city_graph2.add_edge_by_vertices("Los Angeles", "Riverside", 50) 60 | city_graph2.add_edge_by_vertices("Los Angeles", "Phoenix", 357) 61 | city_graph2.add_edge_by_vertices("Riverside", "Phoenix", 307) 62 | city_graph2.add_edge_by_vertices("Riverside", "Chicago", 1704) 63 | city_graph2.add_edge_by_vertices("Phoenix", "Dallas", 887) 64 | city_graph2.add_edge_by_vertices("Phoenix", "Houston", 1015) 65 | city_graph2.add_edge_by_vertices("Dallas", "Chicago", 805) 66 | city_graph2.add_edge_by_vertices("Dallas", "Atlanta", 721) 67 | city_graph2.add_edge_by_vertices("Dallas", "Houston", 225) 68 | city_graph2.add_edge_by_vertices("Houston", "Atlanta", 702) 69 | city_graph2.add_edge_by_vertices("Houston", "Miami", 968) 70 | city_graph2.add_edge_by_vertices("Atlanta", "Chicago", 588) 71 | city_graph2.add_edge_by_vertices("Atlanta", "Washington", 543) 72 | city_graph2.add_edge_by_vertices("Atlanta", "Miami", 604) 73 | city_graph2.add_edge_by_vertices("Miami", "Washington", 923) 74 | city_graph2.add_edge_by_vertices("Chicago", "Detroit", 238) 75 | city_graph2.add_edge_by_vertices("Detroit", "Boston", 613) 76 | city_graph2.add_edge_by_vertices("Detroit", "Washington", 396) 77 | city_graph2.add_edge_by_vertices("Detroit", "New York", 482) 78 | city_graph2.add_edge_by_vertices("Boston", "New York", 190) 79 | city_graph2.add_edge_by_vertices("New York", "Philadelphia", 81) 80 | city_graph2.add_edge_by_vertices("Philadelphia", "Washington", 123) 81 | 82 | print(city_graph2) 83 | -------------------------------------------------------------------------------- /Chapter7/network.py: -------------------------------------------------------------------------------- 1 | # network.py 2 | # From Classic Computer Science Problems in Python Chapter 7 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from __future__ import annotations 17 | from typing import List, Callable, TypeVar, Tuple 18 | from functools import reduce 19 | from layer import Layer 20 | from util import sigmoid, derivative_sigmoid 21 | 22 | T = TypeVar('T') # output type of interpretation of neural network 23 | 24 | 25 | class Network: 26 | def __init__(self, layer_structure: List[int], learning_rate: float, activation_function: Callable[[float], float] = sigmoid, derivative_activation_function: Callable[[float], float] = derivative_sigmoid) -> None: 27 | if len(layer_structure) < 3: 28 | raise ValueError("Error: Should be at least 3 layers (1 input, 1 hidden, 1 output)") 29 | self.layers: List[Layer] = [] 30 | # input layer 31 | input_layer: Layer = Layer(None, layer_structure[0], learning_rate, activation_function, derivative_activation_function) 32 | self.layers.append(input_layer) 33 | # hidden layers and output layer 34 | for previous, num_neurons in enumerate(layer_structure[1::]): 35 | next_layer = Layer(self.layers[previous], num_neurons, learning_rate, activation_function, derivative_activation_function) 36 | self.layers.append(next_layer) 37 | 38 | # Pushes input data to the first layer, then output from the first 39 | # as input to the second, second to the third, etc. 40 | def outputs(self, input: List[float]) -> List[float]: 41 | return reduce(lambda inputs, layer: layer.outputs(inputs), self.layers, input) 42 | 43 | # Figure out each neuron's changes based on the errors of the output 44 | # versus the expected outcome 45 | def backpropagate(self, expected: List[float]) -> None: 46 | # calculate delta for output layer neurons 47 | last_layer: int = len(self.layers) - 1 48 | self.layers[last_layer].calculate_deltas_for_output_layer(expected) 49 | # calculate delta for hidden layers in reverse order 50 | for l in range(last_layer - 1, 0, -1): 51 | self.layers[l].calculate_deltas_for_hidden_layer(self.layers[l + 1]) 52 | 53 | # backpropagate() doesn't actually change any weights 54 | # this function uses the deltas calculated in backpropagate() to 55 | # actually make changes to the weights 56 | def update_weights(self) -> None: 57 | for layer in self.layers[1:]: # skip input layer 58 | for neuron in layer.neurons: 59 | for w in range(len(neuron.weights)): 60 | neuron.weights[w] = neuron.weights[w] + (neuron.learning_rate * (layer.previous_layer.output_cache[w]) * neuron.delta) 61 | 62 | # train() uses the results of outputs() run over many inputs and compared 63 | # against expecteds to feed backpropagate() and update_weights() 64 | def train(self, inputs: List[List[float]], expecteds: List[List[float]]) -> None: 65 | for location, xs in enumerate(inputs): 66 | ys: List[float] = expecteds[location] 67 | outs: List[float] = self.outputs(xs) 68 | self.backpropagate(ys) 69 | self.update_weights() 70 | 71 | # for generalized results that require classification this function will return 72 | # the correct number of trials and the percentage correct out of the total 73 | def validate(self, inputs: List[List[float]], expecteds: List[T], interpret_output: Callable[[List[float]], T]) -> Tuple[int, int, float]: 74 | correct: int = 0 75 | for input, expected in zip(inputs, expecteds): 76 | result: T = interpret_output(self.outputs(input)) 77 | if result == expected: 78 | correct += 1 79 | percentage: float = correct / len(inputs) 80 | return correct, len(inputs), percentage 81 | 82 | -------------------------------------------------------------------------------- /Chapter2/missionaries.py: -------------------------------------------------------------------------------- 1 | # missionaries.py 2 | # From Classic Computer Science Problems in Python Chapter 2 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from __future__ import annotations 17 | from typing import List, Optional 18 | from generic_search import bfs, Node, node_to_path 19 | 20 | MAX_NUM: int = 3 21 | 22 | 23 | class MCState: 24 | def __init__(self, missionaries: int, cannibals: int, boat: bool) -> None: 25 | self.wm: int = missionaries # west bank missionaries 26 | self.wc: int = cannibals # west bank cannibals 27 | self.em: int = MAX_NUM - self.wm # east bank missionaries 28 | self.ec: int = MAX_NUM - self.wc # east bank cannibals 29 | self.boat: bool = boat 30 | 31 | def __str__(self) -> str: 32 | return ("On the west bank there are {} missionaries and {} cannibals.\n" 33 | "On the east bank there are {} missionaries and {} cannibals.\n" 34 | "The boat is on the {} bank.")\ 35 | .format(self.wm, self.wc, self.em, self.ec, ("west" if self.boat else "east")) 36 | 37 | def goal_test(self) -> bool: 38 | return self.is_legal and self.em == MAX_NUM and self.ec == MAX_NUM 39 | 40 | @property 41 | def is_legal(self) -> bool: 42 | if self.wm < self.wc and self.wm > 0: 43 | return False 44 | if self.em < self.ec and self.em > 0: 45 | return False 46 | return True 47 | 48 | def successors(self) -> List[MCState]: 49 | sucs: List[MCState] = [] 50 | if self.boat: # boat on west bank 51 | if self.wm > 1: 52 | sucs.append(MCState(self.wm - 2, self.wc, not self.boat)) 53 | if self.wm > 0: 54 | sucs.append(MCState(self.wm - 1, self.wc, not self.boat)) 55 | if self.wc > 1: 56 | sucs.append(MCState(self.wm, self.wc - 2, not self.boat)) 57 | if self.wc > 0: 58 | sucs.append(MCState(self.wm, self.wc - 1, not self.boat)) 59 | if (self.wc > 0) and (self.wm > 0): 60 | sucs.append(MCState(self.wm - 1, self.wc - 1, not self.boat)) 61 | else: # boat on east bank 62 | if self.em > 1: 63 | sucs.append(MCState(self.wm + 2, self.wc, not self.boat)) 64 | if self.em > 0: 65 | sucs.append(MCState(self.wm + 1, self.wc, not self.boat)) 66 | if self.ec > 1: 67 | sucs.append(MCState(self.wm, self.wc + 2, not self.boat)) 68 | if self.ec > 0: 69 | sucs.append(MCState(self.wm, self.wc + 1, not self.boat)) 70 | if (self.ec > 0) and (self.em > 0): 71 | sucs.append(MCState(self.wm + 1, self.wc + 1, not self.boat)) 72 | return [x for x in sucs if x.is_legal] 73 | 74 | 75 | def display_solution(path: List[MCState]): 76 | if len(path) == 0: # sanity check 77 | return 78 | old_state: MCState = path[0] 79 | print(old_state) 80 | for current_state in path[1:]: 81 | if current_state.boat: 82 | print("{} missionaries and {} cannibals moved from the east bank to the west bank.\n" 83 | .format(old_state.em - current_state.em, old_state.ec - current_state.ec)) 84 | else: 85 | print("{} missionaries and {} cannibals moved from the west bank to the east bank.\n" 86 | .format(old_state.wm - current_state.wm, old_state.wc - current_state.wc)) 87 | print(current_state) 88 | old_state = current_state 89 | 90 | 91 | if __name__ == "__main__": 92 | start: MCState = MCState(MAX_NUM, MAX_NUM, True) 93 | solution: Optional[Node[MCState]] = bfs(start, MCState.goal_test, MCState.successors) 94 | if solution is None: 95 | print("No solution found!") 96 | else: 97 | path: List[MCState] = node_to_path(solution) 98 | display_solution(path) 99 | -------------------------------------------------------------------------------- /Chapter5/genetic_algorithm.py: -------------------------------------------------------------------------------- 1 | # genetic_algorithm.py 2 | # From Classic Computer Science Problems in Python Chapter 5 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from __future__ import annotations 17 | from typing import TypeVar, Generic, List, Tuple, Callable 18 | from enum import Enum 19 | from random import choices, random 20 | from heapq import nlargest 21 | from statistics import mean 22 | from chromosome import Chromosome 23 | 24 | C = TypeVar('C', bound=Chromosome) # type of the chromosomes 25 | 26 | 27 | class GeneticAlgorithm(Generic[C]): 28 | SelectionType = Enum("SelectionType", "ROULETTE TOURNAMENT") 29 | 30 | def __init__(self, initial_population: List[C], threshold: float, max_generations: int = 100, mutation_chance: float = 0.01, crossover_chance: float = 0.7, selection_type: SelectionType = SelectionType.TOURNAMENT) -> None: 31 | self._population: List[C] = initial_population 32 | self._threshold: float = threshold 33 | self._max_generations: int = max_generations 34 | self._mutation_chance: float = mutation_chance 35 | self._crossover_chance: float = crossover_chance 36 | self._selection_type: GeneticAlgorithm.SelectionType = selection_type 37 | self._fitness_key: Callable = type(self._population[0]).fitness 38 | 39 | # Use the probability distribution wheel to pick 2 parents 40 | # Note: will not work with negative fitness results 41 | def _pick_roulette(self, wheel: List[float]) -> Tuple[C, C]: 42 | return tuple(choices(self._population, weights=wheel, k=2)) 43 | 44 | # Choose num_participants at random and take the best 2 45 | def _pick_tournament(self, num_participants: int) -> Tuple[C, C]: 46 | participants: List[C] = choices(self._population, k=num_participants) 47 | return tuple(nlargest(2, participants, key=self._fitness_key)) 48 | 49 | # Replace the population with a new generation of individuals 50 | def _reproduce_and_replace(self) -> None: 51 | new_population: List[C] = [] 52 | # keep going until we've filled the new generation 53 | while len(new_population) < len(self._population): 54 | # pick the 2 parents 55 | if self._selection_type == GeneticAlgorithm.SelectionType.ROULETTE: 56 | parents: Tuple[C, C] = self._pick_roulette([x.fitness() for x in self._population]) 57 | else: 58 | parents = self._pick_tournament(len(self._population) // 2) 59 | # potentially crossover the 2 parents 60 | if random() < self._crossover_chance: 61 | new_population.extend(parents[0].crossover(parents[1])) 62 | else: 63 | new_population.extend(parents) 64 | # if we had an odd number, we'll have 1 extra, so we remove it 65 | if len(new_population) > len(self._population): 66 | new_population.pop() 67 | self._population = new_population # replace reference 68 | 69 | # With _mutation_chance probability mutate each individual 70 | def _mutate(self) -> None: 71 | for individual in self._population: 72 | if random() < self._mutation_chance: 73 | individual.mutate() 74 | 75 | # Run the genetic algorithm for max_generations iterations 76 | # and return the best individual found 77 | def run(self) -> C: 78 | best: C = max(self._population, key=self._fitness_key) 79 | for generation in range(self._max_generations): 80 | # early exit if we beat threshold 81 | if best.fitness() >= self._threshold: 82 | return best 83 | print(f"Generation {generation} Best {best.fitness()} Avg {mean(map(self._fitness_key, self._population))}") 84 | self._reproduce_and_replace() 85 | self._mutate() 86 | highest: C = max(self._population, key=self._fitness_key) 87 | if highest.fitness() > best.fitness(): 88 | best = highest # found a new best 89 | return best # best we found in _max_generations 90 | 91 | -------------------------------------------------------------------------------- /Chapter4/mst.py: -------------------------------------------------------------------------------- 1 | # mst.py 2 | # From Classic Computer Science Problems in Python Chapter 4 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from typing import TypeVar, List, Optional 17 | from weighted_graph import WeightedGraph 18 | from weighted_edge import WeightedEdge 19 | from priority_queue import PriorityQueue 20 | 21 | V = TypeVar('V') # type of the vertices in the graph 22 | WeightedPath = List[WeightedEdge] # type alias for paths 23 | 24 | 25 | def total_weight(wp: WeightedPath) -> float: 26 | return sum([e.weight for e in wp]) 27 | 28 | 29 | def mst(wg: WeightedGraph[V], start: int = 0) -> Optional[WeightedPath]: 30 | if start > (wg.vertex_count - 1) or start < 0: 31 | return None 32 | result: WeightedPath = [] # holds the final MST 33 | pq: PriorityQueue[WeightedEdge] = PriorityQueue() 34 | visited: List[bool] = [False] * wg.vertex_count # where we've been 35 | 36 | def visit(index: int): 37 | visited[index] = True # mark as visited 38 | for edge in wg.edges_for_index(index): 39 | # add all edges coming from here to pq 40 | if not visited[edge.v]: 41 | pq.push(edge) 42 | 43 | visit(start) # the first vertex is where everything begins 44 | 45 | while not pq.empty: # keep going while there are edges to process 46 | edge = pq.pop() 47 | if visited[edge.v]: 48 | continue # don't ever revisit 49 | # this is the current smallest, so add it to solution 50 | result.append(edge) 51 | visit(edge.v) # visit where this connects 52 | 53 | return result 54 | 55 | 56 | def print_weighted_path(wg: WeightedGraph, wp: WeightedPath) -> None: 57 | for edge in wp: 58 | print(f"{wg.vertex_at(edge.u)} {edge.weight}> {wg.vertex_at(edge.v)}") 59 | print(f"Total Weight: {total_weight(wp)}") 60 | 61 | 62 | if __name__ == "__main__": 63 | city_graph2: WeightedGraph[str] = WeightedGraph(["Seattle", "San Francisco", "Los Angeles", "Riverside", "Phoenix", "Chicago", "Boston", "New York", "Atlanta", "Miami", "Dallas", "Houston", "Detroit", "Philadelphia", "Washington"]) 64 | 65 | city_graph2.add_edge_by_vertices("Seattle", "Chicago", 1737) 66 | city_graph2.add_edge_by_vertices("Seattle", "San Francisco", 678) 67 | city_graph2.add_edge_by_vertices("San Francisco", "Riverside", 386) 68 | city_graph2.add_edge_by_vertices("San Francisco", "Los Angeles", 348) 69 | city_graph2.add_edge_by_vertices("Los Angeles", "Riverside", 50) 70 | city_graph2.add_edge_by_vertices("Los Angeles", "Phoenix", 357) 71 | city_graph2.add_edge_by_vertices("Riverside", "Phoenix", 307) 72 | city_graph2.add_edge_by_vertices("Riverside", "Chicago", 1704) 73 | city_graph2.add_edge_by_vertices("Phoenix", "Dallas", 887) 74 | city_graph2.add_edge_by_vertices("Phoenix", "Houston", 1015) 75 | city_graph2.add_edge_by_vertices("Dallas", "Chicago", 805) 76 | city_graph2.add_edge_by_vertices("Dallas", "Atlanta", 721) 77 | city_graph2.add_edge_by_vertices("Dallas", "Houston", 225) 78 | city_graph2.add_edge_by_vertices("Houston", "Atlanta", 702) 79 | city_graph2.add_edge_by_vertices("Houston", "Miami", 968) 80 | city_graph2.add_edge_by_vertices("Atlanta", "Chicago", 588) 81 | city_graph2.add_edge_by_vertices("Atlanta", "Washington", 543) 82 | city_graph2.add_edge_by_vertices("Atlanta", "Miami", 604) 83 | city_graph2.add_edge_by_vertices("Miami", "Washington", 923) 84 | city_graph2.add_edge_by_vertices("Chicago", "Detroit", 238) 85 | city_graph2.add_edge_by_vertices("Detroit", "Boston", 613) 86 | city_graph2.add_edge_by_vertices("Detroit", "Washington", 396) 87 | city_graph2.add_edge_by_vertices("Detroit", "New York", 482) 88 | city_graph2.add_edge_by_vertices("Boston", "New York", 190) 89 | city_graph2.add_edge_by_vertices("New York", "Philadelphia", 81) 90 | city_graph2.add_edge_by_vertices("Philadelphia", "Washington", 123) 91 | 92 | result: Optional[WeightedPath] = mst(city_graph2) 93 | if result is None: 94 | print("No solution found!") 95 | else: 96 | print_weighted_path(city_graph2, result) -------------------------------------------------------------------------------- /Chapter7/iris.csv: -------------------------------------------------------------------------------- 1 | 5.1,3.5,1.4,0.2,Iris-setosa 2 | 4.9,3.0,1.4,0.2,Iris-setosa 3 | 4.7,3.2,1.3,0.2,Iris-setosa 4 | 4.6,3.1,1.5,0.2,Iris-setosa 5 | 5.0,3.6,1.4,0.2,Iris-setosa 6 | 5.4,3.9,1.7,0.4,Iris-setosa 7 | 4.6,3.4,1.4,0.3,Iris-setosa 8 | 5.0,3.4,1.5,0.2,Iris-setosa 9 | 4.4,2.9,1.4,0.2,Iris-setosa 10 | 4.9,3.1,1.5,0.1,Iris-setosa 11 | 5.4,3.7,1.5,0.2,Iris-setosa 12 | 4.8,3.4,1.6,0.2,Iris-setosa 13 | 4.8,3.0,1.4,0.1,Iris-setosa 14 | 4.3,3.0,1.1,0.1,Iris-setosa 15 | 5.8,4.0,1.2,0.2,Iris-setosa 16 | 5.7,4.4,1.5,0.4,Iris-setosa 17 | 5.4,3.9,1.3,0.4,Iris-setosa 18 | 5.1,3.5,1.4,0.3,Iris-setosa 19 | 5.7,3.8,1.7,0.3,Iris-setosa 20 | 5.1,3.8,1.5,0.3,Iris-setosa 21 | 5.4,3.4,1.7,0.2,Iris-setosa 22 | 5.1,3.7,1.5,0.4,Iris-setosa 23 | 4.6,3.6,1.0,0.2,Iris-setosa 24 | 5.1,3.3,1.7,0.5,Iris-setosa 25 | 4.8,3.4,1.9,0.2,Iris-setosa 26 | 5.0,3.0,1.6,0.2,Iris-setosa 27 | 5.0,3.4,1.6,0.4,Iris-setosa 28 | 5.2,3.5,1.5,0.2,Iris-setosa 29 | 5.2,3.4,1.4,0.2,Iris-setosa 30 | 4.7,3.2,1.6,0.2,Iris-setosa 31 | 4.8,3.1,1.6,0.2,Iris-setosa 32 | 5.4,3.4,1.5,0.4,Iris-setosa 33 | 5.2,4.1,1.5,0.1,Iris-setosa 34 | 5.5,4.2,1.4,0.2,Iris-setosa 35 | 4.9,3.1,1.5,0.2,Iris-setosa 36 | 5.0,3.2,1.2,0.2,Iris-setosa 37 | 5.5,3.5,1.3,0.2,Iris-setosa 38 | 4.9,3.6,1.4,0.1,Iris-setosa 39 | 4.4,3.0,1.3,0.2,Iris-setosa 40 | 5.1,3.4,1.5,0.2,Iris-setosa 41 | 5.0,3.5,1.3,0.3,Iris-setosa 42 | 4.5,2.3,1.3,0.3,Iris-setosa 43 | 4.4,3.2,1.3,0.2,Iris-setosa 44 | 5.0,3.5,1.6,0.6,Iris-setosa 45 | 5.1,3.8,1.9,0.4,Iris-setosa 46 | 4.8,3.0,1.4,0.3,Iris-setosa 47 | 5.1,3.8,1.6,0.2,Iris-setosa 48 | 4.6,3.2,1.4,0.2,Iris-setosa 49 | 5.3,3.7,1.5,0.2,Iris-setosa 50 | 5.0,3.3,1.4,0.2,Iris-setosa 51 | 7.0,3.2,4.7,1.4,Iris-versicolor 52 | 6.4,3.2,4.5,1.5,Iris-versicolor 53 | 6.9,3.1,4.9,1.5,Iris-versicolor 54 | 5.5,2.3,4.0,1.3,Iris-versicolor 55 | 6.5,2.8,4.6,1.5,Iris-versicolor 56 | 5.7,2.8,4.5,1.3,Iris-versicolor 57 | 6.3,3.3,4.7,1.6,Iris-versicolor 58 | 4.9,2.4,3.3,1.0,Iris-versicolor 59 | 6.6,2.9,4.6,1.3,Iris-versicolor 60 | 5.2,2.7,3.9,1.4,Iris-versicolor 61 | 5.0,2.0,3.5,1.0,Iris-versicolor 62 | 5.9,3.0,4.2,1.5,Iris-versicolor 63 | 6.0,2.2,4.0,1.0,Iris-versicolor 64 | 6.1,2.9,4.7,1.4,Iris-versicolor 65 | 5.6,2.9,3.6,1.3,Iris-versicolor 66 | 6.7,3.1,4.4,1.4,Iris-versicolor 67 | 5.6,3.0,4.5,1.5,Iris-versicolor 68 | 5.8,2.7,4.1,1.0,Iris-versicolor 69 | 6.2,2.2,4.5,1.5,Iris-versicolor 70 | 5.6,2.5,3.9,1.1,Iris-versicolor 71 | 5.9,3.2,4.8,1.8,Iris-versicolor 72 | 6.1,2.8,4.0,1.3,Iris-versicolor 73 | 6.3,2.5,4.9,1.5,Iris-versicolor 74 | 6.1,2.8,4.7,1.2,Iris-versicolor 75 | 6.4,2.9,4.3,1.3,Iris-versicolor 76 | 6.6,3.0,4.4,1.4,Iris-versicolor 77 | 6.8,2.8,4.8,1.4,Iris-versicolor 78 | 6.7,3.0,5.0,1.7,Iris-versicolor 79 | 6.0,2.9,4.5,1.5,Iris-versicolor 80 | 5.7,2.6,3.5,1.0,Iris-versicolor 81 | 5.5,2.4,3.8,1.1,Iris-versicolor 82 | 5.5,2.4,3.7,1.0,Iris-versicolor 83 | 5.8,2.7,3.9,1.2,Iris-versicolor 84 | 6.0,2.7,5.1,1.6,Iris-versicolor 85 | 5.4,3.0,4.5,1.5,Iris-versicolor 86 | 6.0,3.4,4.5,1.6,Iris-versicolor 87 | 6.7,3.1,4.7,1.5,Iris-versicolor 88 | 6.3,2.3,4.4,1.3,Iris-versicolor 89 | 5.6,3.0,4.1,1.3,Iris-versicolor 90 | 5.5,2.5,4.0,1.3,Iris-versicolor 91 | 5.5,2.6,4.4,1.2,Iris-versicolor 92 | 6.1,3.0,4.6,1.4,Iris-versicolor 93 | 5.8,2.6,4.0,1.2,Iris-versicolor 94 | 5.0,2.3,3.3,1.0,Iris-versicolor 95 | 5.6,2.7,4.2,1.3,Iris-versicolor 96 | 5.7,3.0,4.2,1.2,Iris-versicolor 97 | 5.7,2.9,4.2,1.3,Iris-versicolor 98 | 6.2,2.9,4.3,1.3,Iris-versicolor 99 | 5.1,2.5,3.0,1.1,Iris-versicolor 100 | 5.7,2.8,4.1,1.3,Iris-versicolor 101 | 6.3,3.3,6.0,2.5,Iris-virginica 102 | 5.8,2.7,5.1,1.9,Iris-virginica 103 | 7.1,3.0,5.9,2.1,Iris-virginica 104 | 6.3,2.9,5.6,1.8,Iris-virginica 105 | 6.5,3.0,5.8,2.2,Iris-virginica 106 | 7.6,3.0,6.6,2.1,Iris-virginica 107 | 4.9,2.5,4.5,1.7,Iris-virginica 108 | 7.3,2.9,6.3,1.8,Iris-virginica 109 | 6.7,2.5,5.8,1.8,Iris-virginica 110 | 7.2,3.6,6.1,2.5,Iris-virginica 111 | 6.5,3.2,5.1,2.0,Iris-virginica 112 | 6.4,2.7,5.3,1.9,Iris-virginica 113 | 6.8,3.0,5.5,2.1,Iris-virginica 114 | 5.7,2.5,5.0,2.0,Iris-virginica 115 | 5.8,2.8,5.1,2.4,Iris-virginica 116 | 6.4,3.2,5.3,2.3,Iris-virginica 117 | 6.5,3.0,5.5,1.8,Iris-virginica 118 | 7.7,3.8,6.7,2.2,Iris-virginica 119 | 7.7,2.6,6.9,2.3,Iris-virginica 120 | 6.0,2.2,5.0,1.5,Iris-virginica 121 | 6.9,3.2,5.7,2.3,Iris-virginica 122 | 5.6,2.8,4.9,2.0,Iris-virginica 123 | 7.7,2.8,6.7,2.0,Iris-virginica 124 | 6.3,2.7,4.9,1.8,Iris-virginica 125 | 6.7,3.3,5.7,2.1,Iris-virginica 126 | 7.2,3.2,6.0,1.8,Iris-virginica 127 | 6.2,2.8,4.8,1.8,Iris-virginica 128 | 6.1,3.0,4.9,1.8,Iris-virginica 129 | 6.4,2.8,5.6,2.1,Iris-virginica 130 | 7.2,3.0,5.8,1.6,Iris-virginica 131 | 7.4,2.8,6.1,1.9,Iris-virginica 132 | 7.9,3.8,6.4,2.0,Iris-virginica 133 | 6.4,2.8,5.6,2.2,Iris-virginica 134 | 6.3,2.8,5.1,1.5,Iris-virginica 135 | 6.1,2.6,5.6,1.4,Iris-virginica 136 | 7.7,3.0,6.1,2.3,Iris-virginica 137 | 6.3,3.4,5.6,2.4,Iris-virginica 138 | 6.4,3.1,5.5,1.8,Iris-virginica 139 | 6.0,3.0,4.8,1.8,Iris-virginica 140 | 6.9,3.1,5.4,2.1,Iris-virginica 141 | 6.7,3.1,5.6,2.4,Iris-virginica 142 | 6.9,3.1,5.1,2.3,Iris-virginica 143 | 5.8,2.7,5.1,1.9,Iris-virginica 144 | 6.8,3.2,5.9,2.3,Iris-virginica 145 | 6.7,3.3,5.7,2.5,Iris-virginica 146 | 6.7,3.0,5.2,2.3,Iris-virginica 147 | 6.3,2.5,5.0,1.9,Iris-virginica 148 | 6.5,3.0,5.2,2.0,Iris-virginica 149 | 6.2,3.4,5.4,2.3,Iris-virginica 150 | 5.9,3.0,5.1,1.8,Iris-virginica 151 | -------------------------------------------------------------------------------- /Chapter6/kmeans.py: -------------------------------------------------------------------------------- 1 | # kmeans.py 2 | # From Classic Computer Science Problems in Python Chapter 6 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from __future__ import annotations 17 | from typing import TypeVar, Generic, List, Sequence 18 | from copy import deepcopy 19 | from functools import partial 20 | from random import uniform 21 | from statistics import mean, pstdev 22 | from dataclasses import dataclass 23 | from data_point import DataPoint 24 | 25 | 26 | def zscores(original: Sequence[float]) -> List[float]: 27 | avg: float = mean(original) 28 | std: float = pstdev(original) 29 | if std == 0: # return all zeros if there is no variation 30 | return [0] * len(original) 31 | return [(x - avg) / std for x in original] 32 | 33 | 34 | Point = TypeVar('Point', bound=DataPoint) 35 | 36 | 37 | class KMeans(Generic[Point]): 38 | @dataclass 39 | class Cluster: 40 | points: List[Point] 41 | centroid: DataPoint 42 | 43 | def __init__(self, k: int, points: List[Point]) -> None: 44 | if k < 1: # k-means can't do negative or zero clusters 45 | raise ValueError("k must be >= 1") 46 | self._points: List[Point] = points 47 | self._zscore_normalize() 48 | # initialize empty clusters with random centroids 49 | self._clusters: List[KMeans.Cluster] = [] 50 | for _ in range(k): 51 | rand_point: DataPoint = self._random_point() 52 | cluster: KMeans.Cluster = KMeans.Cluster([], rand_point) 53 | self._clusters.append(cluster) 54 | 55 | @property 56 | def _centroids(self) -> List[DataPoint]: 57 | return [x.centroid for x in self._clusters] 58 | 59 | def _dimension_slice(self, dimension: int) -> List[float]: 60 | return [x.dimensions[dimension] for x in self._points] 61 | 62 | def _zscore_normalize(self) -> None: 63 | zscored: List[List[float]] = [[] for _ in range(len(self._points))] 64 | for dimension in range(self._points[0].num_dimensions): 65 | dimension_slice: List[float] = self._dimension_slice(dimension) 66 | for index, zscore in enumerate(zscores(dimension_slice)): 67 | zscored[index].append(zscore) 68 | for i in range(len(self._points)): 69 | self._points[i].dimensions = tuple(zscored[i]) 70 | 71 | def _random_point(self) -> DataPoint: 72 | rand_dimensions: List[float] = [] 73 | for dimension in range(self._points[0].num_dimensions): 74 | values: List[float] = self._dimension_slice(dimension) 75 | rand_value: float = uniform(min(values), max(values)) 76 | rand_dimensions.append(rand_value) 77 | return DataPoint(rand_dimensions) 78 | 79 | # Find the closest cluster centroid to each point and assign the point to that cluster 80 | def _assign_clusters(self) -> None: 81 | for point in self._points: 82 | closest: DataPoint = min(self._centroids, key=partial(DataPoint.distance, point)) 83 | idx: int = self._centroids.index(closest) 84 | cluster: KMeans.Cluster = self._clusters[idx] 85 | cluster.points.append(point) 86 | 87 | # Find the center of each cluster and move the centroid to there 88 | def _generate_centroids(self) -> None: 89 | for cluster in self._clusters: 90 | if len(cluster.points) == 0: # keep the same centroid if no points 91 | continue 92 | means: List[float] = [] 93 | for dimension in range(cluster.points[0].num_dimensions): 94 | dimension_slice: List[float] = [p.dimensions[dimension] for p in cluster.points] 95 | means.append(mean(dimension_slice)) 96 | cluster.centroid = DataPoint(means) 97 | 98 | def run(self, max_iterations: int = 100) -> List[KMeans.Cluster]: 99 | for iteration in range(max_iterations): 100 | for cluster in self._clusters: # clear all clusters 101 | cluster.points.clear() 102 | self._assign_clusters() # find cluster each point is closest to 103 | old_centroids: List[DataPoint] = deepcopy(self._centroids) # record 104 | self._generate_centroids() # find new centroids 105 | if old_centroids == self._centroids: # have centroids moved? 106 | print(f"Converged after {iteration} iterations") 107 | return self._clusters 108 | return self._clusters 109 | 110 | 111 | if __name__ == "__main__": 112 | point1: DataPoint = DataPoint([2.0, 1.0, 1.0]) 113 | point2: DataPoint = DataPoint([2.0, 2.0, 5.0]) 114 | point3: DataPoint = DataPoint([3.0, 1.5, 2.5]) 115 | kmeans_test: KMeans[DataPoint] = KMeans(2, [point1, point2, point3]) 116 | test_clusters: List[KMeans.Cluster] = kmeans_test.run() 117 | for index, cluster in enumerate(test_clusters): 118 | print(f"Cluster {index}: {cluster.points}") 119 | -------------------------------------------------------------------------------- /Chapter2/maze.py: -------------------------------------------------------------------------------- 1 | # maze.py 2 | # From Classic Computer Science Problems in Python Chapter 2 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from enum import Enum 17 | from typing import List, NamedTuple, Callable, Optional 18 | import random 19 | from math import sqrt 20 | from generic_search import dfs, bfs, node_to_path, astar, Node 21 | 22 | 23 | class Cell(str, Enum): 24 | EMPTY = " " 25 | BLOCKED = "X" 26 | START = "S" 27 | GOAL = "G" 28 | PATH = "*" 29 | 30 | 31 | class MazeLocation(NamedTuple): 32 | row: int 33 | column: int 34 | 35 | 36 | class Maze: 37 | def __init__(self, rows: int = 10, columns: int = 10, sparseness: float = 0.2, start: MazeLocation = MazeLocation(0, 0), goal: MazeLocation = MazeLocation(9, 9)) -> None: 38 | # initialize basic instance variables 39 | self._rows: int = rows 40 | self._columns: int = columns 41 | self.start: MazeLocation = start 42 | self.goal: MazeLocation = goal 43 | # fill the grid with empty cells 44 | self._grid: List[List[Cell]] = [[Cell.EMPTY for c in range(columns)] for r in range(rows)] 45 | # populate the grid with blocked cells 46 | self._randomly_fill(rows, columns, sparseness) 47 | # fill the start and goal locations in 48 | self._grid[start.row][start.column] = Cell.START 49 | self._grid[goal.row][goal.column] = Cell.GOAL 50 | 51 | def _randomly_fill(self, rows: int, columns: int, sparseness: float): 52 | for row in range(rows): 53 | for column in range(columns): 54 | if random.uniform(0, 1.0) < sparseness: 55 | self._grid[row][column] = Cell.BLOCKED 56 | 57 | # return a nicely formatted version of the maze for printing 58 | def __str__(self) -> str: 59 | output: str = "" 60 | for row in self._grid: 61 | output += "".join([c.value for c in row]) + "\n" 62 | return output 63 | 64 | def goal_test(self, ml: MazeLocation) -> bool: 65 | return ml == self.goal 66 | 67 | def successors(self, ml: MazeLocation) -> List[MazeLocation]: 68 | locations: List[MazeLocation] = [] 69 | if ml.row + 1 < self._rows and self._grid[ml.row + 1][ml.column] != Cell.BLOCKED: 70 | locations.append(MazeLocation(ml.row + 1, ml.column)) 71 | if ml.row - 1 >= 0 and self._grid[ml.row - 1][ml.column] != Cell.BLOCKED: 72 | locations.append(MazeLocation(ml.row - 1, ml.column)) 73 | if ml.column + 1 < self._columns and self._grid[ml.row][ml.column + 1] != Cell.BLOCKED: 74 | locations.append(MazeLocation(ml.row, ml.column + 1)) 75 | if ml.column - 1 >= 0 and self._grid[ml.row][ml.column - 1] != Cell.BLOCKED: 76 | locations.append(MazeLocation(ml.row, ml.column - 1)) 77 | return locations 78 | 79 | def mark(self, path: List[MazeLocation]): 80 | for maze_location in path: 81 | self._grid[maze_location.row][maze_location.column] = Cell.PATH 82 | self._grid[self.start.row][self.start.column] = Cell.START 83 | self._grid[self.goal.row][self.goal.column] = Cell.GOAL 84 | 85 | def clear(self, path: List[MazeLocation]): 86 | for maze_location in path: 87 | self._grid[maze_location.row][maze_location.column] = Cell.EMPTY 88 | self._grid[self.start.row][self.start.column] = Cell.START 89 | self._grid[self.goal.row][self.goal.column] = Cell.GOAL 90 | 91 | 92 | def euclidean_distance(goal: MazeLocation) -> Callable[[MazeLocation], float]: 93 | def distance(ml: MazeLocation) -> float: 94 | xdist: int = ml.column - goal.column 95 | ydist: int = ml.row - goal.row 96 | return sqrt((xdist * xdist) + (ydist * ydist)) 97 | return distance 98 | 99 | 100 | def manhattan_distance(goal: MazeLocation) -> Callable[[MazeLocation], float]: 101 | def distance(ml: MazeLocation) -> float: 102 | xdist: int = abs(ml.column - goal.column) 103 | ydist: int = abs(ml.row - goal.row) 104 | return (xdist + ydist) 105 | return distance 106 | 107 | 108 | if __name__ == "__main__": 109 | # Test DFS 110 | m: Maze = Maze() 111 | print(m) 112 | solution1: Optional[Node[MazeLocation]] = dfs(m.start, m.goal_test, m.successors) 113 | if solution1 is None: 114 | print("No solution found using depth-first search!") 115 | else: 116 | path1: List[MazeLocation] = node_to_path(solution1) 117 | m.mark(path1) 118 | print(m) 119 | m.clear(path1) 120 | # Test BFS 121 | solution2: Optional[Node[MazeLocation]] = bfs(m.start, m.goal_test, m.successors) 122 | if solution2 is None: 123 | print("No solution found using breadth-first search!") 124 | else: 125 | path2: List[MazeLocation] = node_to_path(solution2) 126 | m.mark(path2) 127 | print(m) 128 | m.clear(path2) 129 | # Test A* 130 | distance: Callable[[MazeLocation], float] = manhattan_distance(m.goal) 131 | solution3: Optional[Node[MazeLocation]] = astar(m.start, m.goal_test, m.successors, distance) 132 | if solution3 is None: 133 | print("No solution found using A*!") 134 | else: 135 | path3: List[MazeLocation] = node_to_path(solution3) 136 | m.mark(path3) 137 | print(m) -------------------------------------------------------------------------------- /Chapter4/graph.py: -------------------------------------------------------------------------------- 1 | # graph.py 2 | # From Classic Computer Science Problems in Python Chapter 4 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from typing import TypeVar, Generic, List, Optional 17 | from edge import Edge 18 | 19 | 20 | V = TypeVar('V') # type of the vertices in the graph 21 | 22 | 23 | class Graph(Generic[V]): 24 | def __init__(self, vertices: Optional[List[V]] = None) -> None: 25 | if vertices is None: 26 | vertices = [] 27 | self._vertices: List[V] = vertices 28 | self._edges: List[List[Edge]] = [[] for _ in vertices] 29 | 30 | @property 31 | def vertex_count(self) -> int: 32 | return len(self._vertices) # Number of vertices 33 | 34 | @property 35 | def edge_count(self) -> int: 36 | return sum(map(len, self._edges)) # Number of edges 37 | 38 | # Add a vertex to the graph and return its index 39 | def add_vertex(self, vertex: V) -> int: 40 | self._vertices.append(vertex) 41 | self._edges.append([]) # add empty list for containing edges 42 | return self.vertex_count - 1 # return index of added vertex 43 | 44 | # This is an undirected graph, 45 | # so we always add edges in both directions 46 | def add_edge(self, edge: Edge) -> None: 47 | self._edges[edge.u].append(edge) 48 | self._edges[edge.v].append(edge.reversed()) 49 | 50 | # Add an edge using vertex indices (convenience method) 51 | def add_edge_by_indices(self, u: int, v: int) -> None: 52 | edge: Edge = Edge(u, v) 53 | self.add_edge(edge) 54 | 55 | # Add an edge by looking up vertex indices (convenience method) 56 | def add_edge_by_vertices(self, first: V, second: V) -> None: 57 | u: int = self._vertices.index(first) 58 | v: int = self._vertices.index(second) 59 | self.add_edge_by_indices(u, v) 60 | 61 | # Find the vertex at a specific index 62 | def vertex_at(self, index: int) -> V: 63 | return self._vertices[index] 64 | 65 | # Find the index of a vertex in the graph 66 | def index_of(self, vertex: V) -> int: 67 | return self._vertices.index(vertex) 68 | 69 | # Find the vertices that a vertex at some index is connected to 70 | def neighbors_for_index(self, index: int) -> List[V]: 71 | return list(map(self.vertex_at, [e.v for e in self._edges[index]])) 72 | 73 | # Lookup a vertice's index and find its neighbors (convenience method) 74 | def neighbors_for_vertex(self, vertex: V) -> List[V]: 75 | return self.neighbors_for_index(self.index_of(vertex)) 76 | 77 | # Return all of the edges associated with a vertex at some index 78 | def edges_for_index(self, index: int) -> List[Edge]: 79 | return self._edges[index] 80 | 81 | # Lookup the index of a vertex and return its edges (convenience method) 82 | def edges_for_vertex(self, vertex: V) -> List[Edge]: 83 | return self.edges_for_index(self.index_of(vertex)) 84 | 85 | # Make it easy to pretty-print a Graph 86 | def __str__(self) -> str: 87 | desc: str = "" 88 | for i in range(self.vertex_count): 89 | desc += f"{self.vertex_at(i)} -> {self.neighbors_for_index(i)}\n" 90 | return desc 91 | 92 | 93 | if __name__ == "__main__": 94 | # test basic Graph construction 95 | city_graph: Graph[str] = Graph(["Seattle", "San Francisco", "Los Angeles", "Riverside", "Phoenix", "Chicago", "Boston", "New York", "Atlanta", "Miami", "Dallas", "Houston", "Detroit", "Philadelphia", "Washington"]) 96 | city_graph.add_edge_by_vertices("Seattle", "Chicago") 97 | city_graph.add_edge_by_vertices("Seattle", "San Francisco") 98 | city_graph.add_edge_by_vertices("San Francisco", "Riverside") 99 | city_graph.add_edge_by_vertices("San Francisco", "Los Angeles") 100 | city_graph.add_edge_by_vertices("Los Angeles", "Riverside") 101 | city_graph.add_edge_by_vertices("Los Angeles", "Phoenix") 102 | city_graph.add_edge_by_vertices("Riverside", "Phoenix") 103 | city_graph.add_edge_by_vertices("Riverside", "Chicago") 104 | city_graph.add_edge_by_vertices("Phoenix", "Dallas") 105 | city_graph.add_edge_by_vertices("Phoenix", "Houston") 106 | city_graph.add_edge_by_vertices("Dallas", "Chicago") 107 | city_graph.add_edge_by_vertices("Dallas", "Atlanta") 108 | city_graph.add_edge_by_vertices("Dallas", "Houston") 109 | city_graph.add_edge_by_vertices("Houston", "Atlanta") 110 | city_graph.add_edge_by_vertices("Houston", "Miami") 111 | city_graph.add_edge_by_vertices("Atlanta", "Chicago") 112 | city_graph.add_edge_by_vertices("Atlanta", "Washington") 113 | city_graph.add_edge_by_vertices("Atlanta", "Miami") 114 | city_graph.add_edge_by_vertices("Miami", "Washington") 115 | city_graph.add_edge_by_vertices("Chicago", "Detroit") 116 | city_graph.add_edge_by_vertices("Detroit", "Boston") 117 | city_graph.add_edge_by_vertices("Detroit", "Washington") 118 | city_graph.add_edge_by_vertices("Detroit", "New York") 119 | city_graph.add_edge_by_vertices("Boston", "New York") 120 | city_graph.add_edge_by_vertices("New York", "Philadelphia") 121 | city_graph.add_edge_by_vertices("Philadelphia", "Washington") 122 | print(city_graph) 123 | 124 | # Reuse BFS from Chapter 2 on city_graph 125 | import sys 126 | sys.path.insert(0, '..') # so we can access the Chapter2 package in the parent directory 127 | from Chapter2.generic_search import bfs, Node, node_to_path 128 | 129 | bfs_result: Optional[Node[V]] = bfs("Boston", lambda x: x == "Miami", city_graph.neighbors_for_vertex) 130 | if bfs_result is None: 131 | print("No solution found using breadth-first search!") 132 | else: 133 | path: List[V] = node_to_path(bfs_result) 134 | print("Path from Boston to Miami:") 135 | print(path) 136 | 137 | -------------------------------------------------------------------------------- /Chapter4/dijkstra.py: -------------------------------------------------------------------------------- 1 | # dijkstra.py 2 | # From Classic Computer Science Problems in Python Chapter 4 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from __future__ import annotations 17 | from typing import TypeVar, List, Optional, Tuple, Dict 18 | from dataclasses import dataclass 19 | from mst import WeightedPath, print_weighted_path 20 | from weighted_graph import WeightedGraph 21 | from weighted_edge import WeightedEdge 22 | from priority_queue import PriorityQueue 23 | 24 | V = TypeVar('V') # type of the vertices in the graph 25 | 26 | 27 | @dataclass 28 | class DijkstraNode: 29 | vertex: int 30 | distance: float 31 | 32 | def __lt__(self, other: DijkstraNode) -> bool: 33 | return self.distance < other.distance 34 | 35 | def __eq__(self, other: DijkstraNode) -> bool: 36 | return self.distance == other.distance 37 | 38 | 39 | def dijkstra(wg: WeightedGraph[V], root: V) -> Tuple[List[Optional[float]], Dict[int, WeightedEdge]]: 40 | first: int = wg.index_of(root) # find starting index 41 | # distances are unknown at first 42 | distances: List[Optional[float]] = [None] * wg.vertex_count 43 | distances[first] = 0 # the root is 0 away from the root 44 | path_dict: Dict[int, WeightedEdge] = {} # how we got to each vertex 45 | pq: PriorityQueue[DijkstraNode] = PriorityQueue() 46 | pq.push(DijkstraNode(first, 0)) 47 | 48 | while not pq.empty: 49 | u: int = pq.pop().vertex # explore the next closest vertex 50 | dist_u: float = distances[u] # should already have seen it 51 | # look at every edge/vertex from the vertex in question 52 | for we in wg.edges_for_index(u): 53 | # the old distance to this vertex 54 | dist_v: float = distances[we.v] 55 | # no old distance or found shorter path 56 | if dist_v is None or dist_v > we.weight + dist_u: 57 | # update distance to this vertex 58 | distances[we.v] = we.weight + dist_u 59 | # update the edge on the shortest path to this vertex 60 | path_dict[we.v] = we 61 | # explore it soon 62 | pq.push(DijkstraNode(we.v, we.weight + dist_u)) 63 | 64 | return distances, path_dict 65 | 66 | 67 | # Helper function to get easier access to dijkstra results 68 | def distance_array_to_vertex_dict(wg: WeightedGraph[V], distances: List[Optional[float]]) -> Dict[V, Optional[float]]: 69 | distance_dict: Dict[V, Optional[float]] = {} 70 | for i in range(len(distances)): 71 | distance_dict[wg.vertex_at(i)] = distances[i] 72 | return distance_dict 73 | 74 | 75 | # Takes a dictionary of edges to reach each node and returns a list of 76 | # edges that goes from `start` to `end` 77 | def path_dict_to_path(start: int, end: int, path_dict: Dict[int, WeightedEdge]) -> WeightedPath: 78 | if len(path_dict) == 0: 79 | return [] 80 | edge_path: WeightedPath = [] 81 | e: WeightedEdge = path_dict[end] 82 | edge_path.append(e) 83 | while e.u != start: 84 | e = path_dict[e.u] 85 | edge_path.append(e) 86 | return list(reversed(edge_path)) 87 | 88 | 89 | if __name__ == "__main__": 90 | city_graph2: WeightedGraph[str] = WeightedGraph(["Seattle", "San Francisco", "Los Angeles", "Riverside", "Phoenix", "Chicago", "Boston", "New York", "Atlanta", "Miami", "Dallas", "Houston", "Detroit", "Philadelphia", "Washington"]) 91 | 92 | city_graph2.add_edge_by_vertices("Seattle", "Chicago", 1737) 93 | city_graph2.add_edge_by_vertices("Seattle", "San Francisco", 678) 94 | city_graph2.add_edge_by_vertices("San Francisco", "Riverside", 386) 95 | city_graph2.add_edge_by_vertices("San Francisco", "Los Angeles", 348) 96 | city_graph2.add_edge_by_vertices("Los Angeles", "Riverside", 50) 97 | city_graph2.add_edge_by_vertices("Los Angeles", "Phoenix", 357) 98 | city_graph2.add_edge_by_vertices("Riverside", "Phoenix", 307) 99 | city_graph2.add_edge_by_vertices("Riverside", "Chicago", 1704) 100 | city_graph2.add_edge_by_vertices("Phoenix", "Dallas", 887) 101 | city_graph2.add_edge_by_vertices("Phoenix", "Houston", 1015) 102 | city_graph2.add_edge_by_vertices("Dallas", "Chicago", 805) 103 | city_graph2.add_edge_by_vertices("Dallas", "Atlanta", 721) 104 | city_graph2.add_edge_by_vertices("Dallas", "Houston", 225) 105 | city_graph2.add_edge_by_vertices("Houston", "Atlanta", 702) 106 | city_graph2.add_edge_by_vertices("Houston", "Miami", 968) 107 | city_graph2.add_edge_by_vertices("Atlanta", "Chicago", 588) 108 | city_graph2.add_edge_by_vertices("Atlanta", "Washington", 543) 109 | city_graph2.add_edge_by_vertices("Atlanta", "Miami", 604) 110 | city_graph2.add_edge_by_vertices("Miami", "Washington", 923) 111 | city_graph2.add_edge_by_vertices("Chicago", "Detroit", 238) 112 | city_graph2.add_edge_by_vertices("Detroit", "Boston", 613) 113 | city_graph2.add_edge_by_vertices("Detroit", "Washington", 396) 114 | city_graph2.add_edge_by_vertices("Detroit", "New York", 482) 115 | city_graph2.add_edge_by_vertices("Boston", "New York", 190) 116 | city_graph2.add_edge_by_vertices("New York", "Philadelphia", 81) 117 | city_graph2.add_edge_by_vertices("Philadelphia", "Washington", 123) 118 | 119 | distances, path_dict = dijkstra(city_graph2, "Los Angeles") 120 | name_distance: Dict[str, Optional[int]] = distance_array_to_vertex_dict(city_graph2, distances) 121 | print("Distances from Los Angeles:") 122 | for key, value in name_distance.items(): 123 | print(f"{key} : {value}") 124 | print("") # blank line 125 | 126 | print("Shortest path from Los Angeles to Boston:") 127 | path: WeightedPath = path_dict_to_path(city_graph2.index_of("Los Angeles"), city_graph2.index_of("Boston"), path_dict) 128 | print_weighted_path(city_graph2, path) 129 | -------------------------------------------------------------------------------- /Chapter8/connectfour.py: -------------------------------------------------------------------------------- 1 | # connectfour.py 2 | # From Classic Computer Science Problems in Python Chapter 8 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from __future__ import annotations 17 | from typing import List, Optional, Tuple 18 | from enum import Enum 19 | from board import Piece, Board, Move 20 | 21 | 22 | class C4Piece(Piece, Enum): 23 | B = "B" 24 | R = "R" 25 | E = " " # stand-in for empty 26 | 27 | @property 28 | def opposite(self) -> C4Piece: 29 | if self == C4Piece.B: 30 | return C4Piece.R 31 | elif self == C4Piece.R: 32 | return C4Piece.B 33 | else: 34 | return C4Piece.E 35 | 36 | def __str__(self) -> str: 37 | return self.value 38 | 39 | 40 | def generate_segments(num_columns: int, num_rows: int, segment_length: int) -> List[List[Tuple[int, int]]]: 41 | segments: List[List[Tuple[int, int]]] = [] 42 | # generate the vertical segments 43 | for c in range(num_columns): 44 | for r in range(num_rows - segment_length + 1): 45 | segment: List[Tuple[int, int]] = [] 46 | for t in range(segment_length): 47 | segment.append((c, r + t)) 48 | segments.append(segment) 49 | 50 | # generate the horizontal segments 51 | for c in range(num_columns - segment_length + 1): 52 | for r in range(num_rows): 53 | segment = [] 54 | for t in range(segment_length): 55 | segment.append((c + t, r)) 56 | segments.append(segment) 57 | 58 | # generate the bottom left to top right diagonal segments 59 | for c in range(num_columns - segment_length + 1): 60 | for r in range(num_rows - segment_length + 1): 61 | segment = [] 62 | for t in range(segment_length): 63 | segment.append((c + t, r + t)) 64 | segments.append(segment) 65 | 66 | # generate the top left to bottom right diagonal segments 67 | for c in range(num_columns - segment_length + 1): 68 | for r in range(segment_length - 1, num_rows): 69 | segment = [] 70 | for t in range(segment_length): 71 | segment.append((c + t, r - t)) 72 | segments.append(segment) 73 | return segments 74 | 75 | 76 | class C4Board(Board): 77 | NUM_ROWS: int = 6 78 | NUM_COLUMNS: int = 7 79 | SEGMENT_LENGTH: int = 4 80 | SEGMENTS: List[List[Tuple[int, int]]] = generate_segments(NUM_COLUMNS, NUM_ROWS, SEGMENT_LENGTH) 81 | 82 | class Column: 83 | def __init__(self) -> None: 84 | self._container: List[C4Piece] = [] 85 | 86 | @property 87 | def full(self) -> bool: 88 | return len(self._container) == C4Board.NUM_ROWS 89 | 90 | def push(self, item: C4Piece) -> None: 91 | if self.full: 92 | raise OverflowError("Trying to push piece to full column") 93 | self._container.append(item) 94 | 95 | def __getitem__(self, index: int) -> C4Piece: 96 | if index > len(self._container) - 1: 97 | return C4Piece.E 98 | return self._container[index] 99 | 100 | def __repr__(self) -> str: 101 | return repr(self._container) 102 | 103 | def copy(self) -> C4Board.Column: 104 | temp: C4Board.Column = C4Board.Column() 105 | temp._container = self._container.copy() 106 | return temp 107 | 108 | def __init__(self, position: Optional[List[C4Board.Column]] = None, turn: C4Piece = C4Piece.B) -> None: 109 | if position is None: 110 | self.position: List[C4Board.Column] = [C4Board.Column() for _ in range(C4Board.NUM_COLUMNS)] 111 | else: 112 | self.position = position 113 | self._turn: C4Piece = turn 114 | 115 | @property 116 | def turn(self) -> Piece: 117 | return self._turn 118 | 119 | def move(self, location: Move) -> Board: 120 | temp_position: List[C4Board.Column] = self.position.copy() 121 | for c in range(C4Board.NUM_COLUMNS): 122 | temp_position[c] = self.position[c].copy() 123 | temp_position[location].push(self._turn) 124 | return C4Board(temp_position, self._turn.opposite) 125 | 126 | @property 127 | def legal_moves(self) -> List[Move]: 128 | return [Move(c) for c in range(C4Board.NUM_COLUMNS) if not self.position[c].full] 129 | 130 | # Returns the count of black & red pieces in a segment 131 | def _count_segment(self, segment: List[Tuple[int, int]]) -> Tuple[int, int]: 132 | black_count: int = 0 133 | red_count: int = 0 134 | for column, row in segment: 135 | if self.position[column][row] == C4Piece.B: 136 | black_count += 1 137 | elif self.position[column][row] == C4Piece.R: 138 | red_count += 1 139 | return black_count, red_count 140 | 141 | @property 142 | def is_win(self) -> bool: 143 | for segment in C4Board.SEGMENTS: 144 | black_count, red_count = self._count_segment(segment) 145 | if black_count == 4 or red_count == 4: 146 | return True 147 | return False 148 | 149 | def _evaluate_segment(self, segment: List[Tuple[int, int]], player: Piece) -> float: 150 | black_count, red_count = self._count_segment(segment) 151 | if red_count > 0 and black_count > 0: 152 | return 0 # mixed segments are neutral 153 | count: int = max(red_count, black_count) 154 | score: float = 0 155 | if count == 2: 156 | score = 1 157 | elif count == 3: 158 | score = 100 159 | elif count == 4: 160 | score = 1000000 161 | color: C4Piece = C4Piece.B 162 | if red_count > black_count: 163 | color = C4Piece.R 164 | if color != player: 165 | return -score 166 | return score 167 | 168 | def evaluate(self, player: Piece) -> float: 169 | total: float = 0 170 | for segment in C4Board.SEGMENTS: 171 | total += self._evaluate_segment(segment, player) 172 | return total 173 | 174 | def __repr__(self) -> str: 175 | display: str = "" 176 | for r in reversed(range(C4Board.NUM_ROWS)): 177 | display += "|" 178 | for c in range(C4Board.NUM_COLUMNS): 179 | display += f"{self.position[c][r]}" + "|" 180 | display += "\n" 181 | return display 182 | 183 | -------------------------------------------------------------------------------- /Chapter2/generic_search.py: -------------------------------------------------------------------------------- 1 | # generic_search.py 2 | # From Classic Computer Science Problems in Python Chapter 2 3 | # Copyright 2018 David Kopec 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from __future__ import annotations 17 | from typing import TypeVar, Iterable, Sequence, Generic, List, Callable, Set, Deque, Dict, Any, Optional 18 | from typing_extensions import Protocol 19 | from heapq import heappush, heappop 20 | 21 | T = TypeVar('T') 22 | 23 | 24 | def linear_contains(iterable: Iterable[T], key: T) -> bool: 25 | for item in iterable: 26 | if item == key: 27 | return True 28 | return False 29 | 30 | 31 | C = TypeVar("C", bound="Comparable") 32 | 33 | 34 | class Comparable(Protocol): 35 | def __eq__(self, other: Any) -> bool: 36 | ... 37 | 38 | def __lt__(self: C, other: C) -> bool: 39 | ... 40 | 41 | def __gt__(self: C, other: C) -> bool: 42 | return (not self < other) and self != other 43 | 44 | def __le__(self: C, other: C) -> bool: 45 | return self < other or self == other 46 | 47 | def __ge__(self: C, other: C) -> bool: 48 | return not self < other 49 | 50 | 51 | def binary_contains(sequence: Sequence[C], key: C) -> bool: 52 | low: int = 0 53 | high: int = len(sequence) - 1 54 | while low <= high: # while there is still a search space 55 | mid: int = (low + high) // 2 56 | if sequence[mid] < key: 57 | low = mid + 1 58 | elif sequence[mid] > key: 59 | high = mid - 1 60 | else: 61 | return True 62 | return False 63 | 64 | 65 | class Stack(Generic[T]): 66 | def __init__(self) -> None: 67 | self._container: List[T] = [] 68 | 69 | @property 70 | def empty(self) -> bool: 71 | return not self._container # not is true for empty container 72 | 73 | def push(self, item: T) -> None: 74 | self._container.append(item) 75 | 76 | def pop(self) -> T: 77 | return self._container.pop() # LIFO 78 | 79 | def __repr__(self) -> str: 80 | return repr(self._container) 81 | 82 | 83 | class Node(Generic[T]): 84 | def __init__(self, state: T, parent: Optional[Node], cost: float = 0.0, heuristic: float = 0.0) -> None: 85 | self.state: T = state 86 | self.parent: Optional[Node] = parent 87 | self.cost: float = cost 88 | self.heuristic: float = heuristic 89 | 90 | def __lt__(self, other: Node) -> bool: 91 | return (self.cost + self.heuristic) < (other.cost + other.heuristic) 92 | 93 | 94 | def dfs(initial: T, goal_test: Callable[[T], bool], successors: Callable[[T], List[T]]) -> Optional[Node[T]]: 95 | # frontier is where we've yet to go 96 | frontier: Stack[Node[T]] = Stack() 97 | frontier.push(Node(initial, None)) 98 | # explored is where we've been 99 | explored: Set[T] = {initial} 100 | 101 | # keep going while there is more to explore 102 | while not frontier.empty: 103 | current_node: Node[T] = frontier.pop() 104 | current_state: T = current_node.state 105 | # if we found the goal, we're done 106 | if goal_test(current_state): 107 | return current_node 108 | # check where we can go next and haven't explored 109 | for child in successors(current_state): 110 | if child in explored: # skip children we already explored 111 | continue 112 | explored.add(child) 113 | frontier.push(Node(child, current_node)) 114 | return None # went through everything and never found goal 115 | 116 | 117 | def node_to_path(node: Node[T]) -> List[T]: 118 | path: List[T] = [node.state] 119 | # work backwards from end to front 120 | while node.parent is not None: 121 | node = node.parent 122 | path.append(node.state) 123 | path.reverse() 124 | return path 125 | 126 | 127 | class Queue(Generic[T]): 128 | def __init__(self) -> None: 129 | self._container: Deque[T] = Deque() 130 | 131 | @property 132 | def empty(self) -> bool: 133 | return not self._container # not is true for empty container 134 | 135 | def push(self, item: T) -> None: 136 | self._container.append(item) 137 | 138 | def pop(self) -> T: 139 | return self._container.popleft() # FIFO 140 | 141 | def __repr__(self) -> str: 142 | return repr(self._container) 143 | 144 | 145 | def bfs(initial: T, goal_test: Callable[[T], bool], successors: Callable[[T], List[T]]) -> Optional[Node[T]]: 146 | # frontier is where we've yet to go 147 | frontier: Queue[Node[T]] = Queue() 148 | frontier.push(Node(initial, None)) 149 | # explored is where we've been 150 | explored: Set[T] = {initial} 151 | 152 | # keep going while there is more to explore 153 | while not frontier.empty: 154 | current_node: Node[T] = frontier.pop() 155 | current_state: T = current_node.state 156 | # if we found the goal, we're done 157 | if goal_test(current_state): 158 | return current_node 159 | # check where we can go next and haven't explored 160 | for child in successors(current_state): 161 | if child in explored: # skip children we already explored 162 | continue 163 | explored.add(child) 164 | frontier.push(Node(child, current_node)) 165 | return None # went through everything and never found goal 166 | 167 | 168 | class PriorityQueue(Generic[T]): 169 | def __init__(self) -> None: 170 | self._container: List[T] = [] 171 | 172 | @property 173 | def empty(self) -> bool: 174 | return not self._container # not is true for empty container 175 | 176 | def push(self, item: T) -> None: 177 | heappush(self._container, item) # in by priority 178 | 179 | def pop(self) -> T: 180 | return heappop(self._container) # out by priority 181 | 182 | def __repr__(self) -> str: 183 | return repr(self._container) 184 | 185 | 186 | def astar(initial: T, goal_test: Callable[[T], bool], successors: Callable[[T], List[T]], heuristic: Callable[[T], float]) -> Optional[Node[T]]: 187 | # frontier is where we've yet to go 188 | frontier: PriorityQueue[Node[T]] = PriorityQueue() 189 | frontier.push(Node(initial, None, 0.0, heuristic(initial))) 190 | # explored is where we've been 191 | explored: Dict[T, float] = {initial: 0.0} 192 | 193 | # keep going while there is more to explore 194 | while not frontier.empty: 195 | current_node: Node[T] = frontier.pop() 196 | current_state: T = current_node.state 197 | # if we found the goal, we're done 198 | if goal_test(current_state): 199 | return current_node 200 | # check where we can go next and haven't explored 201 | for child in successors(current_state): 202 | new_cost: float = current_node.cost + 1 # 1 assumes a grid, need a cost function for more sophisticated apps 203 | 204 | if child not in explored or explored[child] > new_cost: 205 | explored[child] = new_cost 206 | frontier.push(Node(child, current_node, new_cost, heuristic(child))) 207 | return None # went through everything and never found goal 208 | 209 | 210 | if __name__ == "__main__": 211 | print(linear_contains([1, 5, 15, 15, 15, 15, 20], 5)) # True 212 | print(binary_contains(["a", "d", "e", "f", "z"], "f")) # True 213 | print(binary_contains(["john", "mark", "ronald", "sarah"], "sheila")) # False -------------------------------------------------------------------------------- /Chapter7/wine.csv: -------------------------------------------------------------------------------- 1 | 1,14.23,1.71,2.43,15.6,127,2.8,3.06,.28,2.29,5.64,1.04,3.92,1065 2 | 1,13.2,1.78,2.14,11.2,100,2.65,2.76,.26,1.28,4.38,1.05,3.4,1050 3 | 1,13.16,2.36,2.67,18.6,101,2.8,3.24,.3,2.81,5.68,1.03,3.17,1185 4 | 1,14.37,1.95,2.5,16.8,113,3.85,3.49,.24,2.18,7.8,.86,3.45,1480 5 | 1,13.24,2.59,2.87,21,118,2.8,2.69,.39,1.82,4.32,1.04,2.93,735 6 | 1,14.2,1.76,2.45,15.2,112,3.27,3.39,.34,1.97,6.75,1.05,2.85,1450 7 | 1,14.39,1.87,2.45,14.6,96,2.5,2.52,.3,1.98,5.25,1.02,3.58,1290 8 | 1,14.06,2.15,2.61,17.6,121,2.6,2.51,.31,1.25,5.05,1.06,3.58,1295 9 | 1,14.83,1.64,2.17,14,97,2.8,2.98,.29,1.98,5.2,1.08,2.85,1045 10 | 1,13.86,1.35,2.27,16,98,2.98,3.15,.22,1.85,7.22,1.01,3.55,1045 11 | 1,14.1,2.16,2.3,18,105,2.95,3.32,.22,2.38,5.75,1.25,3.17,1510 12 | 1,14.12,1.48,2.32,16.8,95,2.2,2.43,.26,1.57,5,1.17,2.82,1280 13 | 1,13.75,1.73,2.41,16,89,2.6,2.76,.29,1.81,5.6,1.15,2.9,1320 14 | 1,14.75,1.73,2.39,11.4,91,3.1,3.69,.43,2.81,5.4,1.25,2.73,1150 15 | 1,14.38,1.87,2.38,12,102,3.3,3.64,.29,2.96,7.5,1.2,3,1547 16 | 1,13.63,1.81,2.7,17.2,112,2.85,2.91,.3,1.46,7.3,1.28,2.88,1310 17 | 1,14.3,1.92,2.72,20,120,2.8,3.14,.33,1.97,6.2,1.07,2.65,1280 18 | 1,13.83,1.57,2.62,20,115,2.95,3.4,.4,1.72,6.6,1.13,2.57,1130 19 | 1,14.19,1.59,2.48,16.5,108,3.3,3.93,.32,1.86,8.7,1.23,2.82,1680 20 | 1,13.64,3.1,2.56,15.2,116,2.7,3.03,.17,1.66,5.1,.96,3.36,845 21 | 1,14.06,1.63,2.28,16,126,3,3.17,.24,2.1,5.65,1.09,3.71,780 22 | 1,12.93,3.8,2.65,18.6,102,2.41,2.41,.25,1.98,4.5,1.03,3.52,770 23 | 1,13.71,1.86,2.36,16.6,101,2.61,2.88,.27,1.69,3.8,1.11,4,1035 24 | 1,12.85,1.6,2.52,17.8,95,2.48,2.37,.26,1.46,3.93,1.09,3.63,1015 25 | 1,13.5,1.81,2.61,20,96,2.53,2.61,.28,1.66,3.52,1.12,3.82,845 26 | 1,13.05,2.05,3.22,25,124,2.63,2.68,.47,1.92,3.58,1.13,3.2,830 27 | 1,13.39,1.77,2.62,16.1,93,2.85,2.94,.34,1.45,4.8,.92,3.22,1195 28 | 1,13.3,1.72,2.14,17,94,2.4,2.19,.27,1.35,3.95,1.02,2.77,1285 29 | 1,13.87,1.9,2.8,19.4,107,2.95,2.97,.37,1.76,4.5,1.25,3.4,915 30 | 1,14.02,1.68,2.21,16,96,2.65,2.33,.26,1.98,4.7,1.04,3.59,1035 31 | 1,13.73,1.5,2.7,22.5,101,3,3.25,.29,2.38,5.7,1.19,2.71,1285 32 | 1,13.58,1.66,2.36,19.1,106,2.86,3.19,.22,1.95,6.9,1.09,2.88,1515 33 | 1,13.68,1.83,2.36,17.2,104,2.42,2.69,.42,1.97,3.84,1.23,2.87,990 34 | 1,13.76,1.53,2.7,19.5,132,2.95,2.74,.5,1.35,5.4,1.25,3,1235 35 | 1,13.51,1.8,2.65,19,110,2.35,2.53,.29,1.54,4.2,1.1,2.87,1095 36 | 1,13.48,1.81,2.41,20.5,100,2.7,2.98,.26,1.86,5.1,1.04,3.47,920 37 | 1,13.28,1.64,2.84,15.5,110,2.6,2.68,.34,1.36,4.6,1.09,2.78,880 38 | 1,13.05,1.65,2.55,18,98,2.45,2.43,.29,1.44,4.25,1.12,2.51,1105 39 | 1,13.07,1.5,2.1,15.5,98,2.4,2.64,.28,1.37,3.7,1.18,2.69,1020 40 | 1,14.22,3.99,2.51,13.2,128,3,3.04,.2,2.08,5.1,.89,3.53,760 41 | 1,13.56,1.71,2.31,16.2,117,3.15,3.29,.34,2.34,6.13,.95,3.38,795 42 | 1,13.41,3.84,2.12,18.8,90,2.45,2.68,.27,1.48,4.28,.91,3,1035 43 | 1,13.88,1.89,2.59,15,101,3.25,3.56,.17,1.7,5.43,.88,3.56,1095 44 | 1,13.24,3.98,2.29,17.5,103,2.64,2.63,.32,1.66,4.36,.82,3,680 45 | 1,13.05,1.77,2.1,17,107,3,3,.28,2.03,5.04,.88,3.35,885 46 | 1,14.21,4.04,2.44,18.9,111,2.85,2.65,.3,1.25,5.24,.87,3.33,1080 47 | 1,14.38,3.59,2.28,16,102,3.25,3.17,.27,2.19,4.9,1.04,3.44,1065 48 | 1,13.9,1.68,2.12,16,101,3.1,3.39,.21,2.14,6.1,.91,3.33,985 49 | 1,14.1,2.02,2.4,18.8,103,2.75,2.92,.32,2.38,6.2,1.07,2.75,1060 50 | 1,13.94,1.73,2.27,17.4,108,2.88,3.54,.32,2.08,8.90,1.12,3.1,1260 51 | 1,13.05,1.73,2.04,12.4,92,2.72,3.27,.17,2.91,7.2,1.12,2.91,1150 52 | 1,13.83,1.65,2.6,17.2,94,2.45,2.99,.22,2.29,5.6,1.24,3.37,1265 53 | 1,13.82,1.75,2.42,14,111,3.88,3.74,.32,1.87,7.05,1.01,3.26,1190 54 | 1,13.77,1.9,2.68,17.1,115,3,2.79,.39,1.68,6.3,1.13,2.93,1375 55 | 1,13.74,1.67,2.25,16.4,118,2.6,2.9,.21,1.62,5.85,.92,3.2,1060 56 | 1,13.56,1.73,2.46,20.5,116,2.96,2.78,.2,2.45,6.25,.98,3.03,1120 57 | 1,14.22,1.7,2.3,16.3,118,3.2,3,.26,2.03,6.38,.94,3.31,970 58 | 1,13.29,1.97,2.68,16.8,102,3,3.23,.31,1.66,6,1.07,2.84,1270 59 | 1,13.72,1.43,2.5,16.7,108,3.4,3.67,.19,2.04,6.8,.89,2.87,1285 60 | 2,12.37,.94,1.36,10.6,88,1.98,.57,.28,.42,1.95,1.05,1.82,520 61 | 2,12.33,1.1,2.28,16,101,2.05,1.09,.63,.41,3.27,1.25,1.67,680 62 | 2,12.64,1.36,2.02,16.8,100,2.02,1.41,.53,.62,5.75,.98,1.59,450 63 | 2,13.67,1.25,1.92,18,94,2.1,1.79,.32,.73,3.8,1.23,2.46,630 64 | 2,12.37,1.13,2.16,19,87,3.5,3.1,.19,1.87,4.45,1.22,2.87,420 65 | 2,12.17,1.45,2.53,19,104,1.89,1.75,.45,1.03,2.95,1.45,2.23,355 66 | 2,12.37,1.21,2.56,18.1,98,2.42,2.65,.37,2.08,4.6,1.19,2.3,678 67 | 2,13.11,1.01,1.7,15,78,2.98,3.18,.26,2.28,5.3,1.12,3.18,502 68 | 2,12.37,1.17,1.92,19.6,78,2.11,2,.27,1.04,4.68,1.12,3.48,510 69 | 2,13.34,.94,2.36,17,110,2.53,1.3,.55,.42,3.17,1.02,1.93,750 70 | 2,12.21,1.19,1.75,16.8,151,1.85,1.28,.14,2.5,2.85,1.28,3.07,718 71 | 2,12.29,1.61,2.21,20.4,103,1.1,1.02,.37,1.46,3.05,.906,1.82,870 72 | 2,13.86,1.51,2.67,25,86,2.95,2.86,.21,1.87,3.38,1.36,3.16,410 73 | 2,13.49,1.66,2.24,24,87,1.88,1.84,.27,1.03,3.74,.98,2.78,472 74 | 2,12.99,1.67,2.6,30,139,3.3,2.89,.21,1.96,3.35,1.31,3.5,985 75 | 2,11.96,1.09,2.3,21,101,3.38,2.14,.13,1.65,3.21,.99,3.13,886 76 | 2,11.66,1.88,1.92,16,97,1.61,1.57,.34,1.15,3.8,1.23,2.14,428 77 | 2,13.03,.9,1.71,16,86,1.95,2.03,.24,1.46,4.6,1.19,2.48,392 78 | 2,11.84,2.89,2.23,18,112,1.72,1.32,.43,.95,2.65,.96,2.52,500 79 | 2,12.33,.99,1.95,14.8,136,1.9,1.85,.35,2.76,3.4,1.06,2.31,750 80 | 2,12.7,3.87,2.4,23,101,2.83,2.55,.43,1.95,2.57,1.19,3.13,463 81 | 2,12,.92,2,19,86,2.42,2.26,.3,1.43,2.5,1.38,3.12,278 82 | 2,12.72,1.81,2.2,18.8,86,2.2,2.53,.26,1.77,3.9,1.16,3.14,714 83 | 2,12.08,1.13,2.51,24,78,2,1.58,.4,1.4,2.2,1.31,2.72,630 84 | 2,13.05,3.86,2.32,22.5,85,1.65,1.59,.61,1.62,4.8,.84,2.01,515 85 | 2,11.84,.89,2.58,18,94,2.2,2.21,.22,2.35,3.05,.79,3.08,520 86 | 2,12.67,.98,2.24,18,99,2.2,1.94,.3,1.46,2.62,1.23,3.16,450 87 | 2,12.16,1.61,2.31,22.8,90,1.78,1.69,.43,1.56,2.45,1.33,2.26,495 88 | 2,11.65,1.67,2.62,26,88,1.92,1.61,.4,1.34,2.6,1.36,3.21,562 89 | 2,11.64,2.06,2.46,21.6,84,1.95,1.69,.48,1.35,2.8,1,2.75,680 90 | 2,12.08,1.33,2.3,23.6,70,2.2,1.59,.42,1.38,1.74,1.07,3.21,625 91 | 2,12.08,1.83,2.32,18.5,81,1.6,1.5,.52,1.64,2.4,1.08,2.27,480 92 | 2,12,1.51,2.42,22,86,1.45,1.25,.5,1.63,3.6,1.05,2.65,450 93 | 2,12.69,1.53,2.26,20.7,80,1.38,1.46,.58,1.62,3.05,.96,2.06,495 94 | 2,12.29,2.83,2.22,18,88,2.45,2.25,.25,1.99,2.15,1.15,3.3,290 95 | 2,11.62,1.99,2.28,18,98,3.02,2.26,.17,1.35,3.25,1.16,2.96,345 96 | 2,12.47,1.52,2.2,19,162,2.5,2.27,.32,3.28,2.6,1.16,2.63,937 97 | 2,11.81,2.12,2.74,21.5,134,1.6,.99,.14,1.56,2.5,.95,2.26,625 98 | 2,12.29,1.41,1.98,16,85,2.55,2.5,.29,1.77,2.9,1.23,2.74,428 99 | 2,12.37,1.07,2.1,18.5,88,3.52,3.75,.24,1.95,4.5,1.04,2.77,660 100 | 2,12.29,3.17,2.21,18,88,2.85,2.99,.45,2.81,2.3,1.42,2.83,406 101 | 2,12.08,2.08,1.7,17.5,97,2.23,2.17,.26,1.4,3.3,1.27,2.96,710 102 | 2,12.6,1.34,1.9,18.5,88,1.45,1.36,.29,1.35,2.45,1.04,2.77,562 103 | 2,12.34,2.45,2.46,21,98,2.56,2.11,.34,1.31,2.8,.8,3.38,438 104 | 2,11.82,1.72,1.88,19.5,86,2.5,1.64,.37,1.42,2.06,.94,2.44,415 105 | 2,12.51,1.73,1.98,20.5,85,2.2,1.92,.32,1.48,2.94,1.04,3.57,672 106 | 2,12.42,2.55,2.27,22,90,1.68,1.84,.66,1.42,2.7,.86,3.3,315 107 | 2,12.25,1.73,2.12,19,80,1.65,2.03,.37,1.63,3.4,1,3.17,510 108 | 2,12.72,1.75,2.28,22.5,84,1.38,1.76,.48,1.63,3.3,.88,2.42,488 109 | 2,12.22,1.29,1.94,19,92,2.36,2.04,.39,2.08,2.7,.86,3.02,312 110 | 2,11.61,1.35,2.7,20,94,2.74,2.92,.29,2.49,2.65,.96,3.26,680 111 | 2,11.46,3.74,1.82,19.5,107,3.18,2.58,.24,3.58,2.9,.75,2.81,562 112 | 2,12.52,2.43,2.17,21,88,2.55,2.27,.26,1.22,2,.9,2.78,325 113 | 2,11.76,2.68,2.92,20,103,1.75,2.03,.6,1.05,3.8,1.23,2.5,607 114 | 2,11.41,.74,2.5,21,88,2.48,2.01,.42,1.44,3.08,1.1,2.31,434 115 | 2,12.08,1.39,2.5,22.5,84,2.56,2.29,.43,1.04,2.9,.93,3.19,385 116 | 2,11.03,1.51,2.2,21.5,85,2.46,2.17,.52,2.01,1.9,1.71,2.87,407 117 | 2,11.82,1.47,1.99,20.8,86,1.98,1.6,.3,1.53,1.95,.95,3.33,495 118 | 2,12.42,1.61,2.19,22.5,108,2,2.09,.34,1.61,2.06,1.06,2.96,345 119 | 2,12.77,3.43,1.98,16,80,1.63,1.25,.43,.83,3.4,.7,2.12,372 120 | 2,12,3.43,2,19,87,2,1.64,.37,1.87,1.28,.93,3.05,564 121 | 2,11.45,2.4,2.42,20,96,2.9,2.79,.32,1.83,3.25,.8,3.39,625 122 | 2,11.56,2.05,3.23,28.5,119,3.18,5.08,.47,1.87,6,.93,3.69,465 123 | 2,12.42,4.43,2.73,26.5,102,2.2,2.13,.43,1.71,2.08,.92,3.12,365 124 | 2,13.05,5.8,2.13,21.5,86,2.62,2.65,.3,2.01,2.6,.73,3.1,380 125 | 2,11.87,4.31,2.39,21,82,2.86,3.03,.21,2.91,2.8,.75,3.64,380 126 | 2,12.07,2.16,2.17,21,85,2.6,2.65,.37,1.35,2.76,.86,3.28,378 127 | 2,12.43,1.53,2.29,21.5,86,2.74,3.15,.39,1.77,3.94,.69,2.84,352 128 | 2,11.79,2.13,2.78,28.5,92,2.13,2.24,.58,1.76,3,.97,2.44,466 129 | 2,12.37,1.63,2.3,24.5,88,2.22,2.45,.4,1.9,2.12,.89,2.78,342 130 | 2,12.04,4.3,2.38,22,80,2.1,1.75,.42,1.35,2.6,.79,2.57,580 131 | 3,12.86,1.35,2.32,18,122,1.51,1.25,.21,.94,4.1,.76,1.29,630 132 | 3,12.88,2.99,2.4,20,104,1.3,1.22,.24,.83,5.4,.74,1.42,530 133 | 3,12.81,2.31,2.4,24,98,1.15,1.09,.27,.83,5.7,.66,1.36,560 134 | 3,12.7,3.55,2.36,21.5,106,1.7,1.2,.17,.84,5,.78,1.29,600 135 | 3,12.51,1.24,2.25,17.5,85,2,.58,.6,1.25,5.45,.75,1.51,650 136 | 3,12.6,2.46,2.2,18.5,94,1.62,.66,.63,.94,7.1,.73,1.58,695 137 | 3,12.25,4.72,2.54,21,89,1.38,.47,.53,.8,3.85,.75,1.27,720 138 | 3,12.53,5.51,2.64,25,96,1.79,.6,.63,1.1,5,.82,1.69,515 139 | 3,13.49,3.59,2.19,19.5,88,1.62,.48,.58,.88,5.7,.81,1.82,580 140 | 3,12.84,2.96,2.61,24,101,2.32,.6,.53,.81,4.92,.89,2.15,590 141 | 3,12.93,2.81,2.7,21,96,1.54,.5,.53,.75,4.6,.77,2.31,600 142 | 3,13.36,2.56,2.35,20,89,1.4,.5,.37,.64,5.6,.7,2.47,780 143 | 3,13.52,3.17,2.72,23.5,97,1.55,.52,.5,.55,4.35,.89,2.06,520 144 | 3,13.62,4.95,2.35,20,92,2,.8,.47,1.02,4.4,.91,2.05,550 145 | 3,12.25,3.88,2.2,18.5,112,1.38,.78,.29,1.14,8.21,.65,2,855 146 | 3,13.16,3.57,2.15,21,102,1.5,.55,.43,1.3,4,.6,1.68,830 147 | 3,13.88,5.04,2.23,20,80,.98,.34,.4,.68,4.9,.58,1.33,415 148 | 3,12.87,4.61,2.48,21.5,86,1.7,.65,.47,.86,7.65,.54,1.86,625 149 | 3,13.32,3.24,2.38,21.5,92,1.93,.76,.45,1.25,8.42,.55,1.62,650 150 | 3,13.08,3.9,2.36,21.5,113,1.41,1.39,.34,1.14,9.40,.57,1.33,550 151 | 3,13.5,3.12,2.62,24,123,1.4,1.57,.22,1.25,8.60,.59,1.3,500 152 | 3,12.79,2.67,2.48,22,112,1.48,1.36,.24,1.26,10.8,.48,1.47,480 153 | 3,13.11,1.9,2.75,25.5,116,2.2,1.28,.26,1.56,7.1,.61,1.33,425 154 | 3,13.23,3.3,2.28,18.5,98,1.8,.83,.61,1.87,10.52,.56,1.51,675 155 | 3,12.58,1.29,2.1,20,103,1.48,.58,.53,1.4,7.6,.58,1.55,640 156 | 3,13.17,5.19,2.32,22,93,1.74,.63,.61,1.55,7.9,.6,1.48,725 157 | 3,13.84,4.12,2.38,19.5,89,1.8,.83,.48,1.56,9.01,.57,1.64,480 158 | 3,12.45,3.03,2.64,27,97,1.9,.58,.63,1.14,7.5,.67,1.73,880 159 | 3,14.34,1.68,2.7,25,98,2.8,1.31,.53,2.7,13,.57,1.96,660 160 | 3,13.48,1.67,2.64,22.5,89,2.6,1.1,.52,2.29,11.75,.57,1.78,620 161 | 3,12.36,3.83,2.38,21,88,2.3,.92,.5,1.04,7.65,.56,1.58,520 162 | 3,13.69,3.26,2.54,20,107,1.83,.56,.5,.8,5.88,.96,1.82,680 163 | 3,12.85,3.27,2.58,22,106,1.65,.6,.6,.96,5.58,.87,2.11,570 164 | 3,12.96,3.45,2.35,18.5,106,1.39,.7,.4,.94,5.28,.68,1.75,675 165 | 3,13.78,2.76,2.3,22,90,1.35,.68,.41,1.03,9.58,.7,1.68,615 166 | 3,13.73,4.36,2.26,22.5,88,1.28,.47,.52,1.15,6.62,.78,1.75,520 167 | 3,13.45,3.7,2.6,23,111,1.7,.92,.43,1.46,10.68,.85,1.56,695 168 | 3,12.82,3.37,2.3,19.5,88,1.48,.66,.4,.97,10.26,.72,1.75,685 169 | 3,13.58,2.58,2.69,24.5,105,1.55,.84,.39,1.54,8.66,.74,1.8,750 170 | 3,13.4,4.6,2.86,25,112,1.98,.96,.27,1.11,8.5,.67,1.92,630 171 | 3,12.2,3.03,2.32,19,96,1.25,.49,.4,.73,5.5,.66,1.83,510 172 | 3,12.77,2.39,2.28,19.5,86,1.39,.51,.48,.64,9.899999,.57,1.63,470 173 | 3,14.16,2.51,2.48,20,91,1.68,.7,.44,1.24,9.7,.62,1.71,660 174 | 3,13.71,5.65,2.45,20.5,95,1.68,.61,.52,1.06,7.7,.64,1.74,740 175 | 3,13.4,3.91,2.48,23,102,1.8,.75,.43,1.41,7.3,.7,1.56,750 176 | 3,13.27,4.28,2.26,20,120,1.59,.69,.43,1.35,10.2,.59,1.56,835 177 | 3,13.17,2.59,2.37,20,120,1.65,.68,.53,1.46,9.3,.6,1.62,840 178 | 3,14.13,4.1,2.74,24.5,96,2.05,.76,.56,1.35,9.2,.61,1.6,560 179 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------