From 14de0af42ad615e9813ca2eb1054406730ca0be0 Mon Sep 17 00:00:00 2001 From: Johnny Metz Date: Sat, 22 Jun 2019 22:42:27 -0700 Subject: [PATCH 1/4] Reformat with black --- Chapter1/fib2.py | 2 +- Chapter1/fib3.py | 3 +- Chapter1/fib5.py | 4 +- Chapter1/fib6.py | 3 +- Chapter1/hanoi.py | 4 +- Chapter1/trivial_compression.py | 11 ++++- Chapter2/dna_search.py | 4 +- Chapter2/generic_search.py | 41 +++++++++++++--- Chapter2/maze.py | 35 ++++++++++---- Chapter2/missionaries.py | 37 ++++++++------ Chapter3/csp.py | 12 +++-- Chapter3/map_coloring.py | 13 +++-- Chapter3/queens.py | 10 ++-- Chapter3/send_more_money.py | 4 +- Chapter4/dijkstra.py | 54 ++++++++++++++++----- Chapter4/edge.py | 4 +- Chapter4/graph.py | 40 ++++++++++++---- Chapter4/mst.py | 40 +++++++++++----- Chapter4/priority_queue.py | 4 +- Chapter4/weighted_graph.py | 28 +++++++++-- Chapter5/chromosome.py | 2 +- Chapter5/genetic_algorithm.py | 27 +++++++---- Chapter5/list_compression.py | 35 ++++++++++++-- Chapter5/send_more_money2.py | 25 ++++++++-- Chapter5/simple_equation.py | 18 +++++-- Chapter6/governors.py | 77 ++++++++++++++++++++---------- Chapter6/kmeans.py | 26 +++++----- Chapter6/mj.py | 21 +++++--- Chapter7/iris_test.py | 10 ++-- Chapter7/layer.py | 26 ++++++++-- Chapter7/network.py | 46 ++++++++++++++---- Chapter7/neuron.py | 13 +++-- Chapter7/util.py | 5 +- Chapter7/wine_test.py | 10 ++-- Chapter8/board.py | 3 +- Chapter8/connectfour.py | 25 ++++++---- Chapter8/connectfour_ai.py | 2 - Chapter8/minimax.py | 39 +++++++++++---- Chapter8/tictactoe.py | 44 ++++++++++++----- Chapter8/tictactoe_ai.py | 2 - Chapter8/tictactoe_tests.py | 46 +++++++++++++----- Chapter9/knapsack.py | 40 +++++++++------- Chapter9/phone_number_mnemonics.py | 22 +++++---- Chapter9/tsp.py | 59 ++++++++++++----------- 44 files changed, 682 insertions(+), 294 deletions(-) diff --git a/Chapter1/fib2.py b/Chapter1/fib2.py index cf572c8..048d4eb 100644 --- a/Chapter1/fib2.py +++ b/Chapter1/fib2.py @@ -23,4 +23,4 @@ def fib2(n: int) -> int: if __name__ == "__main__": print(fib2(5)) - print(fib2(10)) \ No newline at end of file + print(fib2(10)) diff --git a/Chapter1/fib3.py b/Chapter1/fib3.py index 63b190f..a32cb81 100644 --- a/Chapter1/fib3.py +++ b/Chapter1/fib3.py @@ -14,6 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. from typing import Dict + memo: Dict[int, int] = {0: 0, 1: 1} # our base cases @@ -25,4 +26,4 @@ def fib3(n: int) -> int: if __name__ == "__main__": print(fib3(5)) - print(fib3(50)) \ No newline at end of file + print(fib3(50)) diff --git a/Chapter1/fib5.py b/Chapter1/fib5.py index adde39d..dbb819e 100644 --- a/Chapter1/fib5.py +++ b/Chapter1/fib5.py @@ -14,7 +14,8 @@ # See the License for the specific language governing permissions and # limitations under the License. def fib5(n: int) -> int: - if n == 0: return n # special case + if n == 0: + return n # special case last: int = 0 # initially set to fib(0) next: int = 1 # initially set to fib(1) for _ in range(1, n): @@ -25,4 +26,3 @@ def fib5(n: int) -> int: if __name__ == "__main__": print(fib5(2)) print(fib5(50)) - diff --git a/Chapter1/fib6.py b/Chapter1/fib6.py index 0f036fd..61a2ce7 100644 --- a/Chapter1/fib6.py +++ b/Chapter1/fib6.py @@ -18,7 +18,8 @@ def fib6(n: int) -> Generator[int, None, None]: yield 0 # special case - if n > 0: yield 1 # special case + if n > 0: + yield 1 # special case last: int = 0 # initially set to fib(0) next: int = 1 # initially set to fib(1) for _ in range(1, n): diff --git a/Chapter1/hanoi.py b/Chapter1/hanoi.py index 48ab600..30a00ee 100644 --- a/Chapter1/hanoi.py +++ b/Chapter1/hanoi.py @@ -14,11 +14,11 @@ # See the License for the specific language governing permissions and # limitations under the License. from typing import TypeVar, Generic, List -T = TypeVar('T') +T = TypeVar("T") -class Stack(Generic[T]): +class Stack(Generic[T]): def __init__(self) -> None: self._container: List[T] = [] diff --git a/Chapter1/trivial_compression.py b/Chapter1/trivial_compression.py index 3cf2a9f..d296176 100644 --- a/Chapter1/trivial_compression.py +++ b/Chapter1/trivial_compression.py @@ -36,7 +36,9 @@ def _compress(self, gene: str) -> None: def decompress(self) -> str: gene: str = "" - for i in range(0, self.bit_string.bit_length() - 1, 2): # - 1 to exclude sentinel + for i in range( + 0, self.bit_string.bit_length() - 1, 2 + ): # - 1 to exclude sentinel bits: int = self.bit_string >> i & 0b11 # get just 2 relevant bits if bits == 0b00: # A gene += "A" @@ -56,9 +58,14 @@ def __str__(self) -> str: # string representation for pretty printing if __name__ == "__main__": from sys import getsizeof + original: str = "TAGGGATTAACCGTTATATATATATAGCCATGGATCGATTATATAGGGATTAACCGTTATATATATATAGCCATGGATCGATTATA" * 100 print("original is {} bytes".format(getsizeof(original))) compressed: CompressedGene = CompressedGene(original) # compress print("compressed is {} bytes".format(getsizeof(compressed.bit_string))) print(compressed) # decompress - print("original and decompressed are the same: {}".format(original == compressed.decompress())) \ No newline at end of file + print( + "original and decompressed are the same: {}".format( + original == compressed.decompress() + ) + ) diff --git a/Chapter2/dna_search.py b/Chapter2/dna_search.py index 1c3c0ce..3e05af8 100644 --- a/Chapter2/dna_search.py +++ b/Chapter2/dna_search.py @@ -16,7 +16,7 @@ from enum import IntEnum from typing import Tuple, List -Nucleotide: IntEnum = IntEnum('Nucleotide', ('A', 'C', 'G', 'T')) +Nucleotide: IntEnum = IntEnum("Nucleotide", ("A", "C", "G", "T")) Codon = Tuple[Nucleotide, Nucleotide, Nucleotide] # type alias for codons Gene = List[Codon] # type alias for genes @@ -66,4 +66,4 @@ def binary_contains(gene: Gene, key_codon: Codon) -> bool: my_sorted_gene: Gene = sorted(my_gene) print(binary_contains(my_sorted_gene, acg)) # True -print(binary_contains(my_sorted_gene, gat)) # False \ No newline at end of file +print(binary_contains(my_sorted_gene, gat)) # False diff --git a/Chapter2/generic_search.py b/Chapter2/generic_search.py index 6e0ceb7..99b642c 100644 --- a/Chapter2/generic_search.py +++ b/Chapter2/generic_search.py @@ -14,11 +14,23 @@ # See the License for the specific language governing permissions and # limitations under the License. from __future__ import annotations -from typing import TypeVar, Iterable, Sequence, Generic, List, Callable, Set, Deque, Dict, Any, Optional +from typing import ( + TypeVar, + Iterable, + Sequence, + Generic, + List, + Callable, + Set, + Deque, + Dict, + Any, + Optional, +) from typing_extensions import Protocol from heapq import heappush, heappop -T = TypeVar('T') +T = TypeVar("T") def linear_contains(iterable: Iterable[T], key: T) -> bool: @@ -81,7 +93,13 @@ def __repr__(self) -> str: class Node(Generic[T]): - def __init__(self, state: T, parent: Optional[Node], cost: float = 0.0, heuristic: float = 0.0) -> None: + def __init__( + self, + state: T, + parent: Optional[Node], + cost: float = 0.0, + heuristic: float = 0.0, + ) -> None: self.state: T = state self.parent: Optional[Node] = parent self.cost: float = cost @@ -91,7 +109,9 @@ def __lt__(self, other: Node) -> bool: return (self.cost + self.heuristic) < (other.cost + other.heuristic) -def dfs(initial: T, goal_test: Callable[[T], bool], successors: Callable[[T], List[T]]) -> Optional[Node[T]]: +def dfs( + initial: T, goal_test: Callable[[T], bool], successors: Callable[[T], List[T]] +) -> Optional[Node[T]]: # frontier is where we've yet to go frontier: Stack[Node[T]] = Stack() frontier.push(Node(initial, None)) @@ -142,7 +162,9 @@ def __repr__(self) -> str: return repr(self._container) -def bfs(initial: T, goal_test: Callable[[T], bool], successors: Callable[[T], List[T]]) -> Optional[Node[T]]: +def bfs( + initial: T, goal_test: Callable[[T], bool], successors: Callable[[T], List[T]] +) -> Optional[Node[T]]: # frontier is where we've yet to go frontier: Queue[Node[T]] = Queue() frontier.push(Node(initial, None)) @@ -183,7 +205,12 @@ def __repr__(self) -> str: return repr(self._container) -def astar(initial: T, goal_test: Callable[[T], bool], successors: Callable[[T], List[T]], heuristic: Callable[[T], float]) -> Optional[Node[T]]: +def astar( + initial: T, + goal_test: Callable[[T], bool], + successors: Callable[[T], List[T]], + heuristic: Callable[[T], float], +) -> Optional[Node[T]]: # frontier is where we've yet to go frontier: PriorityQueue[Node[T]] = PriorityQueue() frontier.push(Node(initial, None, 0.0, heuristic(initial))) @@ -210,4 +237,4 @@ def astar(initial: T, goal_test: Callable[[T], bool], successors: Callable[[T], if __name__ == "__main__": print(linear_contains([1, 5, 15, 15, 15, 15, 20], 5)) # True print(binary_contains(["a", "d", "e", "f", "z"], "f")) # True - print(binary_contains(["john", "mark", "ronald", "sarah"], "sheila")) # False \ No newline at end of file + print(binary_contains(["john", "mark", "ronald", "sarah"], "sheila")) # False diff --git a/Chapter2/maze.py b/Chapter2/maze.py index 6283ba1..0636382 100644 --- a/Chapter2/maze.py +++ b/Chapter2/maze.py @@ -34,14 +34,23 @@ class MazeLocation(NamedTuple): class Maze: - def __init__(self, rows: int = 10, columns: int = 10, sparseness: float = 0.2, start: MazeLocation = MazeLocation(0, 0), goal: MazeLocation = MazeLocation(9, 9)) -> None: + def __init__( + self, + rows: int = 10, + columns: int = 10, + sparseness: float = 0.2, + start: MazeLocation = MazeLocation(0, 0), + goal: MazeLocation = MazeLocation(9, 9), + ) -> None: # initialize basic instance variables self._rows: int = rows self._columns: int = columns self.start: MazeLocation = start self.goal: MazeLocation = goal # fill the grid with empty cells - self._grid: List[List[Cell]] = [[Cell.EMPTY for c in range(columns)] for r in range(rows)] + self._grid: List[List[Cell]] = [ + [Cell.EMPTY for c in range(columns)] for r in range(rows) + ] # populate the grid with blocked cells self._randomly_fill(rows, columns, sparseness) # fill the start and goal locations in @@ -66,11 +75,17 @@ def goal_test(self, ml: MazeLocation) -> bool: def successors(self, ml: MazeLocation) -> List[MazeLocation]: locations: List[MazeLocation] = [] - if ml.row + 1 < self._rows and self._grid[ml.row + 1][ml.column] != Cell.BLOCKED: + if ( + ml.row + 1 < self._rows + and self._grid[ml.row + 1][ml.column] != Cell.BLOCKED + ): locations.append(MazeLocation(ml.row + 1, ml.column)) if ml.row - 1 >= 0 and self._grid[ml.row - 1][ml.column] != Cell.BLOCKED: locations.append(MazeLocation(ml.row - 1, ml.column)) - if ml.column + 1 < self._columns and self._grid[ml.row][ml.column + 1] != Cell.BLOCKED: + if ( + ml.column + 1 < self._columns + and self._grid[ml.row][ml.column + 1] != Cell.BLOCKED + ): locations.append(MazeLocation(ml.row, ml.column + 1)) if ml.column - 1 >= 0 and self._grid[ml.row][ml.column - 1] != Cell.BLOCKED: locations.append(MazeLocation(ml.row, ml.column - 1)) @@ -81,7 +96,7 @@ def mark(self, path: List[MazeLocation]): self._grid[maze_location.row][maze_location.column] = Cell.PATH self._grid[self.start.row][self.start.column] = Cell.START self._grid[self.goal.row][self.goal.column] = Cell.GOAL - + def clear(self, path: List[MazeLocation]): for maze_location in path: self._grid[maze_location.row][maze_location.column] = Cell.EMPTY @@ -94,6 +109,7 @@ def distance(ml: MazeLocation) -> float: xdist: int = ml.column - goal.column ydist: int = ml.row - goal.row return sqrt((xdist * xdist) + (ydist * ydist)) + return distance @@ -101,7 +117,8 @@ def manhattan_distance(goal: MazeLocation) -> Callable[[MazeLocation], float]: def distance(ml: MazeLocation) -> float: xdist: int = abs(ml.column - goal.column) ydist: int = abs(ml.row - goal.row) - return (xdist + ydist) + return xdist + ydist + return distance @@ -128,10 +145,12 @@ def distance(ml: MazeLocation) -> float: m.clear(path2) # Test A* distance: Callable[[MazeLocation], float] = manhattan_distance(m.goal) - solution3: Optional[Node[MazeLocation]] = astar(m.start, m.goal_test, m.successors, distance) + solution3: Optional[Node[MazeLocation]] = astar( + m.start, m.goal_test, m.successors, distance + ) if solution3 is None: print("No solution found using A*!") else: path3: List[MazeLocation] = node_to_path(solution3) m.mark(path3) - print(m) \ No newline at end of file + print(m) diff --git a/Chapter2/missionaries.py b/Chapter2/missionaries.py index 92a5692..b73a887 100644 --- a/Chapter2/missionaries.py +++ b/Chapter2/missionaries.py @@ -22,17 +22,18 @@ class MCState: def __init__(self, missionaries: int, cannibals: int, boat: bool) -> None: - self.wm: int = missionaries # west bank missionaries - self.wc: int = cannibals # west bank cannibals + self.wm: int = missionaries # west bank missionaries + self.wc: int = cannibals # west bank cannibals self.em: int = MAX_NUM - self.wm # east bank missionaries self.ec: int = MAX_NUM - self.wc # east bank cannibals self.boat: bool = boat def __str__(self) -> str: - return ("On the west bank there are {} missionaries and {} cannibals.\n" - "On the east bank there are {} missionaries and {} cannibals.\n" - "The boat is on the {} bank.")\ - .format(self.wm, self.wc, self.em, self.ec, ("west" if self.boat else "east")) + return ( + "On the west bank there are {} missionaries and {} cannibals.\n" + "On the east bank there are {} missionaries and {} cannibals.\n" + "The boat is on the {} bank." + ).format(self.wm, self.wc, self.em, self.ec, ("west" if self.boat else "east")) def goal_test(self) -> bool: return self.is_legal and self.em == MAX_NUM and self.ec == MAX_NUM @@ -47,7 +48,7 @@ def is_legal(self) -> bool: def successors(self) -> List[MCState]: sucs: List[MCState] = [] - if self.boat: # boat on west bank + if self.boat: # boat on west bank if self.wm > 1: sucs.append(MCState(self.wm - 2, self.wc, not self.boat)) if self.wm > 0: @@ -58,7 +59,7 @@ def successors(self) -> List[MCState]: sucs.append(MCState(self.wm, self.wc - 1, not self.boat)) if (self.wc > 0) and (self.wm > 0): sucs.append(MCState(self.wm - 1, self.wc - 1, not self.boat)) - else: # boat on east bank + else: # boat on east bank if self.em > 1: sucs.append(MCState(self.wm + 2, self.wc, not self.boat)) if self.em > 0: @@ -73,24 +74,32 @@ def successors(self) -> List[MCState]: def display_solution(path: List[MCState]): - if len(path) == 0: # sanity check + if len(path) == 0: # sanity check return old_state: MCState = path[0] print(old_state) for current_state in path[1:]: if current_state.boat: - print("{} missionaries and {} cannibals moved from the east bank to the west bank.\n" - .format(old_state.em - current_state.em, old_state.ec - current_state.ec)) + print( + "{} missionaries and {} cannibals moved from the east bank to the west bank.\n".format( + old_state.em - current_state.em, old_state.ec - current_state.ec + ) + ) else: - print("{} missionaries and {} cannibals moved from the west bank to the east bank.\n" - .format(old_state.wm - current_state.wm, old_state.wc - current_state.wc)) + print( + "{} missionaries and {} cannibals moved from the west bank to the east bank.\n".format( + old_state.wm - current_state.wm, old_state.wc - current_state.wc + ) + ) print(current_state) old_state = current_state if __name__ == "__main__": start: MCState = MCState(MAX_NUM, MAX_NUM, True) - solution: Optional[Node[MCState]] = bfs(start, MCState.goal_test, MCState.successors) + solution: Optional[Node[MCState]] = bfs( + start, MCState.goal_test, MCState.successors + ) if solution is None: print("No solution found!") else: diff --git a/Chapter3/csp.py b/Chapter3/csp.py index c8f9ce3..69ce2a6 100644 --- a/Chapter3/csp.py +++ b/Chapter3/csp.py @@ -16,8 +16,8 @@ from typing import Generic, TypeVar, Dict, List, Optional from abc import ABC, abstractmethod -V = TypeVar('V') # variable type -D = TypeVar('D') # domain type +V = TypeVar("V") # variable type +D = TypeVar("D") # domain type # Base class for all constraints @@ -37,8 +37,8 @@ def satisfied(self, assignment: Dict[V, D]) -> bool: # that determine whether a particular variable's domain selection is valid class CSP(Generic[V, D]): def __init__(self, variables: List[V], domains: Dict[V, List[D]]) -> None: - self.variables: List[V] = variables # variables to be constrained - self.domains: Dict[V, List[D]] = domains # domain of each variable + self.variables: List[V] = variables # variables to be constrained + self.domains: Dict[V, List[D]] = domains # domain of each variable self.constraints: Dict[V, List[Constraint[V, D]]] = {} for variable in self.variables: self.constraints[variable] = [] @@ -75,7 +75,9 @@ def backtracking_search(self, assignment: Dict[V, D] = {}) -> Optional[Dict[V, D local_assignment[first] = value # if we're still consistent, we recurse (continue) if self.consistent(first, local_assignment): - result: Optional[Dict[V, D]] = self.backtracking_search(local_assignment) + result: Optional[Dict[V, D]] = self.backtracking_search( + local_assignment + ) # if we didn't find the result, we will end up backtracking if result is not None: return result diff --git a/Chapter3/map_coloring.py b/Chapter3/map_coloring.py index 4332f11..462c5d1 100644 --- a/Chapter3/map_coloring.py +++ b/Chapter3/map_coloring.py @@ -34,8 +34,15 @@ def satisfied(self, assignment: Dict[str, str]) -> bool: if __name__ == "__main__": - variables: List[str] = ["Western Australia", "Northern Territory", "South Australia", - "Queensland", "New South Wales", "Victoria", "Tasmania"] + variables: List[str] = [ + "Western Australia", + "Northern Territory", + "South Australia", + "Queensland", + "New South Wales", + "Victoria", + "Tasmania", + ] domains: Dict[str, List[str]] = {} for variable in variables: domains[variable] = ["red", "green", "blue"] @@ -54,4 +61,4 @@ def satisfied(self, assignment: Dict[str, str]) -> bool: if solution is None: print("No solution found!") else: - print(solution) \ No newline at end of file + print(solution) diff --git a/Chapter3/queens.py b/Chapter3/queens.py index 31a82a5..011f578 100644 --- a/Chapter3/queens.py +++ b/Chapter3/queens.py @@ -28,12 +28,12 @@ def satisfied(self, assignment: Dict[int, int]) -> bool: # q2c = queen 2 column for q2c in range(q1c + 1, len(self.columns) + 1): if q2c in assignment: - q2r: int = assignment[q2c] # q2r = queen 2 row - if q1r == q2r: # same row? + q2r: int = assignment[q2c] # q2r = queen 2 row + if q1r == q2r: # same row? return False - if abs(q1r - q2r) == abs(q1c - q2c): # same diagonal? + if abs(q1r - q2r) == abs(q1c - q2c): # same diagonal? return False - return True # no conflict + return True # no conflict if __name__ == "__main__": @@ -47,4 +47,4 @@ def satisfied(self, assignment: Dict[int, int]) -> bool: if solution is None: print("No solution found!") else: - print(solution) \ No newline at end of file + print(solution) diff --git a/Chapter3/send_more_money.py b/Chapter3/send_more_money.py index 2071c10..516ddf8 100644 --- a/Chapter3/send_more_money.py +++ b/Chapter3/send_more_money.py @@ -41,7 +41,7 @@ def satisfied(self, assignment: Dict[str, int]) -> bool: more: int = m * 1000 + o * 100 + r * 10 + e money: int = m * 10000 + o * 1000 + n * 100 + e * 10 + y return send + more == money - return True # no conflict + return True # no conflict if __name__ == "__main__": @@ -56,4 +56,4 @@ def satisfied(self, assignment: Dict[str, int]) -> bool: if solution is None: print("No solution found!") else: - print(solution) \ No newline at end of file + print(solution) diff --git a/Chapter4/dijkstra.py b/Chapter4/dijkstra.py index 9bdee27..f8b2407 100644 --- a/Chapter4/dijkstra.py +++ b/Chapter4/dijkstra.py @@ -21,7 +21,7 @@ from weighted_edge import WeightedEdge from priority_queue import PriorityQueue -V = TypeVar('V') # type of the vertices in the graph +V = TypeVar("V") # type of the vertices in the graph @dataclass @@ -36,18 +36,20 @@ def __eq__(self, other: DijkstraNode) -> bool: return self.distance == other.distance -def dijkstra(wg: WeightedGraph[V], root: V) -> Tuple[List[Optional[float]], Dict[int, WeightedEdge]]: - first: int = wg.index_of(root) # find starting index +def dijkstra( + wg: WeightedGraph[V], root: V +) -> Tuple[List[Optional[float]], Dict[int, WeightedEdge]]: + first: int = wg.index_of(root) # find starting index # distances are unknown at first distances: List[Optional[float]] = [None] * wg.vertex_count - distances[first] = 0 # the root is 0 away from the root - path_dict: Dict[int, WeightedEdge] = {} # how we got to each vertex + distances[first] = 0 # the root is 0 away from the root + path_dict: Dict[int, WeightedEdge] = {} # how we got to each vertex pq: PriorityQueue[DijkstraNode] = PriorityQueue() pq.push(DijkstraNode(first, 0)) while not pq.empty: - u: int = pq.pop().vertex # explore the next closest vertex - dist_u: float = distances[u] # should already have seen it + u: int = pq.pop().vertex # explore the next closest vertex + dist_u: float = distances[u] # should already have seen it # look at every edge/vertex from the vertex in question for we in wg.edges_for_index(u): # the old distance to this vertex @@ -65,7 +67,9 @@ def dijkstra(wg: WeightedGraph[V], root: V) -> Tuple[List[Optional[float]], Dict # Helper function to get easier access to dijkstra results -def distance_array_to_vertex_dict(wg: WeightedGraph[V], distances: List[Optional[float]]) -> Dict[V, Optional[float]]: +def distance_array_to_vertex_dict( + wg: WeightedGraph[V], distances: List[Optional[float]] +) -> Dict[V, Optional[float]]: distance_dict: Dict[V, Optional[float]] = {} for i in range(len(distances)): distance_dict[wg.vertex_at(i)] = distances[i] @@ -74,7 +78,9 @@ def distance_array_to_vertex_dict(wg: WeightedGraph[V], distances: List[Optional # Takes a dictionary of edges to reach each node and returns a list of # edges that goes from `start` to `end` -def path_dict_to_path(start: int, end: int, path_dict: Dict[int, WeightedEdge]) -> WeightedPath: +def path_dict_to_path( + start: int, end: int, path_dict: Dict[int, WeightedEdge] +) -> WeightedPath: if len(path_dict) == 0: return [] edge_path: WeightedPath = [] @@ -87,7 +93,25 @@ def path_dict_to_path(start: int, end: int, path_dict: Dict[int, WeightedEdge]) if __name__ == "__main__": - city_graph2: WeightedGraph[str] = WeightedGraph(["Seattle", "San Francisco", "Los Angeles", "Riverside", "Phoenix", "Chicago", "Boston", "New York", "Atlanta", "Miami", "Dallas", "Houston", "Detroit", "Philadelphia", "Washington"]) + city_graph2: WeightedGraph[str] = WeightedGraph( + [ + "Seattle", + "San Francisco", + "Los Angeles", + "Riverside", + "Phoenix", + "Chicago", + "Boston", + "New York", + "Atlanta", + "Miami", + "Dallas", + "Houston", + "Detroit", + "Philadelphia", + "Washington", + ] + ) city_graph2.add_edge_by_vertices("Seattle", "Chicago", 1737) city_graph2.add_edge_by_vertices("Seattle", "San Francisco", 678) @@ -117,12 +141,16 @@ def path_dict_to_path(start: int, end: int, path_dict: Dict[int, WeightedEdge]) city_graph2.add_edge_by_vertices("Philadelphia", "Washington", 123) distances, path_dict = dijkstra(city_graph2, "Los Angeles") - name_distance: Dict[str, Optional[int]] = distance_array_to_vertex_dict(city_graph2, distances) + name_distance: Dict[str, Optional[int]] = distance_array_to_vertex_dict( + city_graph2, distances + ) print("Distances from Los Angeles:") for key, value in name_distance.items(): print(f"{key} : {value}") - print("") # blank line + print("") # blank line print("Shortest path from Los Angeles to Boston:") - path: WeightedPath = path_dict_to_path(city_graph2.index_of("Los Angeles"), city_graph2.index_of("Boston"), path_dict) + path: WeightedPath = path_dict_to_path( + city_graph2.index_of("Los Angeles"), city_graph2.index_of("Boston"), path_dict + ) print_weighted_path(city_graph2, path) diff --git a/Chapter4/edge.py b/Chapter4/edge.py index d3887d8..2d7c479 100644 --- a/Chapter4/edge.py +++ b/Chapter4/edge.py @@ -19,8 +19,8 @@ @dataclass class Edge: - u: int # the "from" vertex - v: int # the "to" vertex + u: int # the "from" vertex + v: int # the "to" vertex def reversed(self) -> Edge: return Edge(self.v, self.u) diff --git a/Chapter4/graph.py b/Chapter4/graph.py index f08601e..cf4b220 100644 --- a/Chapter4/graph.py +++ b/Chapter4/graph.py @@ -17,7 +17,7 @@ from edge import Edge -V = TypeVar('V') # type of the vertices in the graph +V = TypeVar("V") # type of the vertices in the graph class Graph(Generic[V]): @@ -27,17 +27,17 @@ def __init__(self, vertices: List[V] = []) -> None: @property def vertex_count(self) -> int: - return len(self._vertices) # Number of vertices + return len(self._vertices) # Number of vertices @property def edge_count(self) -> int: - return sum(map(len, self._edges)) # Number of edges + return sum(map(len, self._edges)) # Number of edges # Add a vertex to the graph and return its index def add_vertex(self, vertex: V) -> int: self._vertices.append(vertex) - self._edges.append([]) # add empty list for containing edges - return self.vertex_count - 1 # return index of added vertex + self._edges.append([]) # add empty list for containing edges + return self.vertex_count - 1 # return index of added vertex # This is an undirected graph, # so we always add edges in both directions @@ -90,7 +90,25 @@ def __str__(self) -> str: if __name__ == "__main__": # test basic Graph construction - city_graph: Graph[str] = Graph(["Seattle", "San Francisco", "Los Angeles", "Riverside", "Phoenix", "Chicago", "Boston", "New York", "Atlanta", "Miami", "Dallas", "Houston", "Detroit", "Philadelphia", "Washington"]) + city_graph: Graph[str] = Graph( + [ + "Seattle", + "San Francisco", + "Los Angeles", + "Riverside", + "Phoenix", + "Chicago", + "Boston", + "New York", + "Atlanta", + "Miami", + "Dallas", + "Houston", + "Detroit", + "Philadelphia", + "Washington", + ] + ) city_graph.add_edge_by_vertices("Seattle", "Chicago") city_graph.add_edge_by_vertices("Seattle", "San Francisco") city_graph.add_edge_by_vertices("San Francisco", "Riverside") @@ -121,14 +139,18 @@ def __str__(self) -> str: # Reuse BFS from Chapter 2 on city_graph import sys - sys.path.insert(0, '..') # so we can access the Chapter2 package in the parent directory + + sys.path.insert( + 0, ".." + ) # so we can access the Chapter2 package in the parent directory from Chapter2.generic_search import bfs, Node, node_to_path - bfs_result: Optional[Node[V]] = bfs("Boston", lambda x: x == "Miami", city_graph.neighbors_for_vertex) + bfs_result: Optional[Node[V]] = bfs( + "Boston", lambda x: x == "Miami", city_graph.neighbors_for_vertex + ) if bfs_result is None: print("No solution found using breadth-first search!") else: path: List[V] = node_to_path(bfs_result) print("Path from Boston to Miami:") print(path) - diff --git a/Chapter4/mst.py b/Chapter4/mst.py index 0fbaeaf..a017e34 100644 --- a/Chapter4/mst.py +++ b/Chapter4/mst.py @@ -18,8 +18,8 @@ from weighted_edge import WeightedEdge from priority_queue import PriorityQueue -V = TypeVar('V') # type of the vertices in the graph -WeightedPath = List[WeightedEdge] # type alias for paths +V = TypeVar("V") # type of the vertices in the graph +WeightedPath = List[WeightedEdge] # type alias for paths def total_weight(wp: WeightedPath) -> float: @@ -29,26 +29,26 @@ def total_weight(wp: WeightedPath) -> float: def mst(wg: WeightedGraph[V], start: int = 0) -> Optional[WeightedPath]: if start > (wg.vertex_count - 1) or start < 0: return None - result: WeightedPath = [] # holds the final MST + result: WeightedPath = [] # holds the final MST pq: PriorityQueue[WeightedEdge] = PriorityQueue() - visited: [bool] = [False] * wg.vertex_count # where we've been + visited: [bool] = [False] * wg.vertex_count # where we've been def visit(index: int): - visited[index] = True # mark as visited + visited[index] = True # mark as visited for edge in wg.edges_for_index(index): # add all edges coming from here to pq if not visited[edge.v]: pq.push(edge) - visit(start) # the first vertex is where everything begins + visit(start) # the first vertex is where everything begins - while not pq.empty: # keep going while there are edges to process + while not pq.empty: # keep going while there are edges to process edge = pq.pop() if visited[edge.v]: - continue # don't ever revisit + continue # don't ever revisit # this is the current smallest, so add it to solution result.append(edge) - visit(edge.v) # visit where this connects + visit(edge.v) # visit where this connects return result @@ -60,7 +60,25 @@ def print_weighted_path(wg: WeightedGraph, wp: WeightedPath) -> None: if __name__ == "__main__": - city_graph2: WeightedGraph[str] = WeightedGraph(["Seattle", "San Francisco", "Los Angeles", "Riverside", "Phoenix", "Chicago", "Boston", "New York", "Atlanta", "Miami", "Dallas", "Houston", "Detroit", "Philadelphia", "Washington"]) + city_graph2: WeightedGraph[str] = WeightedGraph( + [ + "Seattle", + "San Francisco", + "Los Angeles", + "Riverside", + "Phoenix", + "Chicago", + "Boston", + "New York", + "Atlanta", + "Miami", + "Dallas", + "Houston", + "Detroit", + "Philadelphia", + "Washington", + ] + ) city_graph2.add_edge_by_vertices("Seattle", "Chicago", 1737) city_graph2.add_edge_by_vertices("Seattle", "San Francisco", 678) @@ -93,4 +111,4 @@ def print_weighted_path(wg: WeightedGraph, wp: WeightedPath) -> None: if result is None: print("No solution found!") else: - print_weighted_path(city_graph2, result) \ No newline at end of file + print_weighted_path(city_graph2, result) diff --git a/Chapter4/priority_queue.py b/Chapter4/priority_queue.py index a8dfc81..80dbfa1 100644 --- a/Chapter4/priority_queue.py +++ b/Chapter4/priority_queue.py @@ -17,7 +17,7 @@ from heapq import heappush, heappop -T = TypeVar('T') +T = TypeVar("T") class PriorityQueue(Generic[T]): @@ -35,4 +35,4 @@ def pop(self) -> T: return heappop(self._container) # out by priority def __repr__(self) -> str: - return repr(self._container) \ No newline at end of file + return repr(self._container) diff --git a/Chapter4/weighted_graph.py b/Chapter4/weighted_graph.py index 0acb273..e7929ff 100644 --- a/Chapter4/weighted_graph.py +++ b/Chapter4/weighted_graph.py @@ -17,7 +17,7 @@ from graph import Graph from weighted_edge import WeightedEdge -V = TypeVar('V') # type of the vertices in the graph +V = TypeVar("V") # type of the vertices in the graph class WeightedGraph(Generic[V], Graph[V]): @@ -27,7 +27,7 @@ def __init__(self, vertices: List[V] = []) -> None: def add_edge_by_indices(self, u: int, v: int, weight: float) -> None: edge: WeightedEdge = WeightedEdge(u, v, weight) - self.add_edge(edge) # call superclass version + self.add_edge(edge) # call superclass version def add_edge_by_vertices(self, first: V, second: V, weight: float) -> None: u: int = self._vertices.index(first) @@ -43,12 +43,32 @@ def neighbors_for_index_with_weights(self, index: int) -> List[Tuple[V, float]]: def __str__(self) -> str: desc: str = "" for i in range(self.vertex_count): - desc += f"{self.vertex_at(i)} -> {self.neighbors_for_index_with_weights(i)}\n" + desc += ( + f"{self.vertex_at(i)} -> {self.neighbors_for_index_with_weights(i)}\n" + ) return desc if __name__ == "__main__": - city_graph2: WeightedGraph[str] = WeightedGraph(["Seattle", "San Francisco", "Los Angeles", "Riverside", "Phoenix", "Chicago", "Boston", "New York", "Atlanta", "Miami", "Dallas", "Houston", "Detroit", "Philadelphia", "Washington"]) + city_graph2: WeightedGraph[str] = WeightedGraph( + [ + "Seattle", + "San Francisco", + "Los Angeles", + "Riverside", + "Phoenix", + "Chicago", + "Boston", + "New York", + "Atlanta", + "Miami", + "Dallas", + "Houston", + "Detroit", + "Philadelphia", + "Washington", + ] + ) city_graph2.add_edge_by_vertices("Seattle", "Chicago", 1737) city_graph2.add_edge_by_vertices("Seattle", "San Francisco", 678) diff --git a/Chapter5/chromosome.py b/Chapter5/chromosome.py index 3b26a40..3ba8cc5 100644 --- a/Chapter5/chromosome.py +++ b/Chapter5/chromosome.py @@ -17,7 +17,7 @@ from typing import TypeVar, Tuple, Type from abc import ABC, abstractmethod -T = TypeVar('T', bound='Chromosome') # for returning self +T = TypeVar("T", bound="Chromosome") # for returning self # Base class for all chromosomes; all methods must be overridden diff --git a/Chapter5/genetic_algorithm.py b/Chapter5/genetic_algorithm.py index 7304882..8705896 100644 --- a/Chapter5/genetic_algorithm.py +++ b/Chapter5/genetic_algorithm.py @@ -21,13 +21,21 @@ from statistics import mean from chromosome import Chromosome -C = TypeVar('C', bound=Chromosome) # type of the chromosomes +C = TypeVar("C", bound=Chromosome) # type of the chromosomes class GeneticAlgorithm(Generic[C]): SelectionType = Enum("SelectionType", "ROULETTE TOURNAMENT") - 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: + 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: self._population: List[C] = initial_population self._threshold: float = threshold self._max_generations: int = max_generations @@ -53,7 +61,9 @@ def _reproduce_and_replace(self) -> None: while len(new_population) < len(self._population): # pick the 2 parents if self._selection_type == GeneticAlgorithm.SelectionType.ROULETTE: - parents: Tuple[C, C] = self._pick_roulette([x.fitness() for x in self._population]) + parents: Tuple[C, C] = self._pick_roulette( + [x.fitness() for x in self._population] + ) else: parents = self._pick_tournament(len(self._population) // 2) # potentially crossover the 2 parents @@ -64,7 +74,7 @@ def _reproduce_and_replace(self) -> None: # if we had an odd number, we'll have 1 extra, so we remove it if len(new_population) > len(self._population): new_population.pop() - self._population = new_population # replace reference + self._population = new_population # replace reference # With _mutation_chance probability mutate each individual def _mutate(self) -> None: @@ -80,11 +90,12 @@ def run(self) -> C: # early exit if we beat threshold if best.fitness() >= self._threshold: return best - print(f"Generation {generation} Best {best.fitness()} Avg {mean(map(self._fitness_key, self._population))}") + print( + f"Generation {generation} Best {best.fitness()} Avg {mean(map(self._fitness_key, self._population))}" + ) self._reproduce_and_replace() self._mutate() highest: C = max(self._population, key=self._fitness_key) if highest.fitness() > best.fitness(): - best = highest # found a new best - return best # best we found in _max_generations - + best = highest # found a new best + return best # best we found in _max_generations diff --git a/Chapter5/list_compression.py b/Chapter5/list_compression.py index c85e32d..6b519af 100644 --- a/Chapter5/list_compression.py +++ b/Chapter5/list_compression.py @@ -24,7 +24,21 @@ from pickle import dumps # 165 bytes compressed -PEOPLE: List[str] = ["Michael", "Sarah", "Joshua", "Narine", "David", "Sajid", "Melanie", "Daniel", "Wei", "Dean", "Brian", "Murat", "Lisa"] +PEOPLE: List[str] = [ + "Michael", + "Sarah", + "Joshua", + "Narine", + "David", + "Sajid", + "Melanie", + "Daniel", + "Wei", + "Dean", + "Brian", + "Murat", + "Lisa", +] class ListCompression(Chromosome): @@ -44,7 +58,9 @@ def random_instance(cls) -> ListCompression: shuffle(mylst) return ListCompression(mylst) - def crossover(self, other: ListCompression) -> Tuple[ListCompression, ListCompression]: + def crossover( + self, other: ListCompression + ) -> Tuple[ListCompression, ListCompression]: child1: ListCompression = deepcopy(self) child2: ListCompression = deepcopy(other) idx1, idx2 = sample(range(len(self.lst)), k=2) @@ -53,7 +69,7 @@ def crossover(self, other: ListCompression) -> Tuple[ListCompression, ListCompre child2.lst[child2.lst.index(l1)], child2.lst[idx1] = child2.lst[idx1], l1 return child1, child2 - def mutate(self) -> None: # swap two locations + def mutate(self) -> None: # swap two locations idx1, idx2 = sample(range(len(self.lst)), k=2) self.lst[idx1], self.lst[idx2] = self.lst[idx2], self.lst[idx1] @@ -62,8 +78,17 @@ def __str__(self) -> str: if __name__ == "__main__": - initial_population: List[ListCompression] = [ListCompression.random_instance() for _ in range(100)] - 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) + initial_population: List[ListCompression] = [ + ListCompression.random_instance() for _ in range(100) + ] + 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, + ) result: ListCompression = ga.run() print(result) diff --git a/Chapter5/send_more_money2.py b/Chapter5/send_more_money2.py index 9b43b48..af26bcc 100644 --- a/Chapter5/send_more_money2.py +++ b/Chapter5/send_more_money2.py @@ -51,11 +51,17 @@ def crossover(self, other: SendMoreMoney2) -> Tuple[SendMoreMoney2, SendMoreMone child2: SendMoreMoney2 = deepcopy(other) idx1, idx2 = sample(range(len(self.letters)), k=2) l1, l2 = child1.letters[idx1], child2.letters[idx2] - child1.letters[child1.letters.index(l2)], child1.letters[idx2] = child1.letters[idx2], l2 - child2.letters[child2.letters.index(l1)], child2.letters[idx1] = child2.letters[idx1], l1 + child1.letters[child1.letters.index(l2)], child1.letters[idx2] = ( + child1.letters[idx2], + l2, + ) + child2.letters[child2.letters.index(l1)], child2.letters[idx1] = ( + child2.letters[idx1], + l1, + ) return child1, child2 - def mutate(self) -> None: # swap two letters' locations + def mutate(self) -> None: # swap two letters' locations idx1, idx2 = sample(range(len(self.letters)), k=2) self.letters[idx1], self.letters[idx2] = self.letters[idx2], self.letters[idx1] @@ -76,7 +82,16 @@ def __str__(self) -> str: if __name__ == "__main__": - initial_population: List[SendMoreMoney2] = [SendMoreMoney2.random_instance() for _ in range(1000)] - 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) + initial_population: List[SendMoreMoney2] = [ + SendMoreMoney2.random_instance() for _ in range(1000) + ] + 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, + ) result: SendMoreMoney2 = ga.run() print(result) diff --git a/Chapter5/simple_equation.py b/Chapter5/simple_equation.py index d18f413..f6931ab 100644 --- a/Chapter5/simple_equation.py +++ b/Chapter5/simple_equation.py @@ -26,7 +26,7 @@ def __init__(self, x: int, y: int) -> None: self.x: int = x self.y: int = y - def fitness(self) -> float: # 6x - x^2 + 4y - y^2 + def fitness(self) -> float: # 6x - x^2 + 4y - y^2 return 6 * self.x - self.x * self.x + 4 * self.y - self.y * self.y @classmethod @@ -41,12 +41,12 @@ def crossover(self, other: SimpleEquation) -> Tuple[SimpleEquation, SimpleEquati return child1, child2 def mutate(self) -> None: - if random() > 0.5: # mutate x + if random() > 0.5: # mutate x if random() > 0.5: self.x += 1 else: self.x -= 1 - else: # otherwise mutate y + else: # otherwise mutate y if random() > 0.5: self.y += 1 else: @@ -57,7 +57,15 @@ def __str__(self) -> str: if __name__ == "__main__": - initial_population: List[SimpleEquation] = [SimpleEquation.random_instance() for _ in range(20)] - ga: GeneticAlgorithm[SimpleEquation] = GeneticAlgorithm(initial_population=initial_population, threshold=13.0, max_generations = 100, mutation_chance = 0.1, crossover_chance = 0.7) + initial_population: List[SimpleEquation] = [ + SimpleEquation.random_instance() for _ in range(20) + ] + ga: GeneticAlgorithm[SimpleEquation] = GeneticAlgorithm( + initial_population=initial_population, + threshold=13.0, + max_generations=100, + mutation_chance=0.1, + crossover_chance=0.7, + ) result: SimpleEquation = ga.run() print(result) diff --git a/Chapter6/governors.py b/Chapter6/governors.py index 72111ad..0f7d26f 100644 --- a/Chapter6/governors.py +++ b/Chapter6/governors.py @@ -31,31 +31,58 @@ def __repr__(self) -> str: if __name__ == "__main__": - governors: List[Governor] = [Governor(-86.79113, 72, "Alabama"), Governor(-152.404419, 66, "Alaska"), - Governor(-111.431221, 53, "Arizona"), Governor(-92.373123, 66, "Arkansas"), - Governor(-119.681564, 79, "California"), Governor(-105.311104, 65, "Colorado"), - Governor(-72.755371, 61, "Connecticut"), Governor(-75.507141, 61, "Delaware"), - Governor(-81.686783, 64, "Florida"), Governor(-83.643074, 74, "Georgia"), - Governor(-157.498337, 60, "Hawaii"), Governor(-114.478828, 75, "Idaho"), - Governor(-88.986137, 60, "Illinois"), Governor(-86.258278, 49, "Indiana"), - Governor(-93.210526, 57, "Iowa"), Governor(-96.726486, 60, "Kansas"), - Governor(-84.670067, 50, "Kentucky"), Governor(-91.867805, 50, "Louisiana"), - Governor(-69.381927, 68, "Maine"), Governor(-76.802101, 61, "Maryland"), - Governor(-71.530106, 60, "Massachusetts"), Governor(-84.536095, 58, "Michigan"), - Governor(-93.900192, 70, "Minnesota"), Governor(-89.678696, 62, "Mississippi"), - Governor(-92.288368, 43, "Missouri"), Governor(-110.454353, 51, "Montana"), - Governor(-98.268082, 52, "Nebraska"), Governor(-117.055374, 53, "Nevada"), - Governor(-71.563896, 42, "New Hampshire"), Governor(-74.521011, 54, "New Jersey"), - Governor(-106.248482, 57, "New Mexico"), Governor(-74.948051, 59, "New York"), - Governor(-79.806419, 60, "North Carolina"), Governor(-99.784012, 60, "North Dakota"), - Governor(-82.764915, 65, "Ohio"), Governor(-96.928917, 62, "Oklahoma"), - Governor(-122.070938, 56, "Oregon"), Governor(-77.209755, 68, "Pennsylvania"), - Governor(-71.51178, 46, "Rhode Island"), Governor(-80.945007, 70, "South Carolina"), - Governor(-99.438828, 64, "South Dakota"), Governor(-86.692345, 58, "Tennessee"), - Governor(-97.563461, 59, "Texas"), Governor(-111.862434, 70, "Utah"), - Governor(-72.710686, 58, "Vermont"), Governor(-78.169968, 60, "Virginia"), - Governor(-121.490494, 66, "Washington"), Governor(-80.954453, 66, "West Virginia"), - Governor(-89.616508, 49, "Wisconsin"), Governor(-107.30249, 55, "Wyoming")] + governors: List[Governor] = [ + Governor(-86.79113, 72, "Alabama"), + Governor(-152.404419, 66, "Alaska"), + Governor(-111.431221, 53, "Arizona"), + Governor(-92.373123, 66, "Arkansas"), + Governor(-119.681564, 79, "California"), + Governor(-105.311104, 65, "Colorado"), + Governor(-72.755371, 61, "Connecticut"), + Governor(-75.507141, 61, "Delaware"), + Governor(-81.686783, 64, "Florida"), + Governor(-83.643074, 74, "Georgia"), + Governor(-157.498337, 60, "Hawaii"), + Governor(-114.478828, 75, "Idaho"), + Governor(-88.986137, 60, "Illinois"), + Governor(-86.258278, 49, "Indiana"), + Governor(-93.210526, 57, "Iowa"), + Governor(-96.726486, 60, "Kansas"), + Governor(-84.670067, 50, "Kentucky"), + Governor(-91.867805, 50, "Louisiana"), + Governor(-69.381927, 68, "Maine"), + Governor(-76.802101, 61, "Maryland"), + Governor(-71.530106, 60, "Massachusetts"), + Governor(-84.536095, 58, "Michigan"), + Governor(-93.900192, 70, "Minnesota"), + Governor(-89.678696, 62, "Mississippi"), + Governor(-92.288368, 43, "Missouri"), + Governor(-110.454353, 51, "Montana"), + Governor(-98.268082, 52, "Nebraska"), + Governor(-117.055374, 53, "Nevada"), + Governor(-71.563896, 42, "New Hampshire"), + Governor(-74.521011, 54, "New Jersey"), + Governor(-106.248482, 57, "New Mexico"), + Governor(-74.948051, 59, "New York"), + Governor(-79.806419, 60, "North Carolina"), + Governor(-99.784012, 60, "North Dakota"), + Governor(-82.764915, 65, "Ohio"), + Governor(-96.928917, 62, "Oklahoma"), + Governor(-122.070938, 56, "Oregon"), + Governor(-77.209755, 68, "Pennsylvania"), + Governor(-71.51178, 46, "Rhode Island"), + Governor(-80.945007, 70, "South Carolina"), + Governor(-99.438828, 64, "South Dakota"), + Governor(-86.692345, 58, "Tennessee"), + Governor(-97.563461, 59, "Texas"), + Governor(-111.862434, 70, "Utah"), + Governor(-72.710686, 58, "Vermont"), + Governor(-78.169968, 60, "Virginia"), + Governor(-121.490494, 66, "Washington"), + Governor(-80.954453, 66, "West Virginia"), + Governor(-89.616508, 49, "Wisconsin"), + Governor(-107.30249, 55, "Wyoming"), + ] kmeans: KMeans[Governor] = KMeans(2, governors) gov_clusters: List[KMeans.Cluster] = kmeans.run() for index, cluster in enumerate(gov_clusters): diff --git a/Chapter6/kmeans.py b/Chapter6/kmeans.py index fdff5cf..5726d5f 100644 --- a/Chapter6/kmeans.py +++ b/Chapter6/kmeans.py @@ -26,12 +26,12 @@ def zscores(original: Sequence[float]) -> List[float]: avg: float = mean(original) std: float = pstdev(original) - if std == 0: # return all zeros if there is no variation + if std == 0: # return all zeros if there is no variation return [0] * len(original) return [(x - avg) / std for x in original] -Point = TypeVar('Point', bound=DataPoint) +Point = TypeVar("Point", bound=DataPoint) class KMeans(Generic[Point]): @@ -41,7 +41,7 @@ class Cluster: centroid: DataPoint def __init__(self, k: int, points: List[Point]) -> None: - if k < 1: # k-means can't do negative or zero clusters + if k < 1: # k-means can't do negative or zero clusters raise ValueError("k must be >= 1") self._points: List[Point] = points self._zscore_normalize() @@ -79,7 +79,9 @@ def _random_point(self) -> DataPoint: # Find the closest cluster centroid to each point and assign the point to that cluster def _assign_clusters(self) -> None: for point in self._points: - closest: DataPoint = min(self._centroids, key=partial(DataPoint.distance, point)) + closest: DataPoint = min( + self._centroids, key=partial(DataPoint.distance, point) + ) idx: int = self._centroids.index(closest) cluster: KMeans.Cluster = self._clusters[idx] cluster.points.append(point) @@ -87,22 +89,24 @@ def _assign_clusters(self) -> None: # Find the center of each cluster and move the centroid to there def _generate_centroids(self) -> None: for cluster in self._clusters: - if len(cluster.points) == 0: # keep the same centroid if no points + if len(cluster.points) == 0: # keep the same centroid if no points continue means: List[float] = [] for dimension in range(cluster.points[0].num_dimensions): - dimension_slice: List[float] = [p.dimensions[dimension] for p in cluster.points] + dimension_slice: List[float] = [ + p.dimensions[dimension] for p in cluster.points + ] means.append(mean(dimension_slice)) cluster.centroid = DataPoint(means) def run(self, max_iterations: int = 100) -> List[KMeans.Cluster]: for iteration in range(max_iterations): - for cluster in self._clusters: # clear all clusters + for cluster in self._clusters: # clear all clusters cluster.points.clear() - self._assign_clusters() # find cluster each point is closest to - old_centroids: List[DataPoint] = deepcopy(self._centroids) # record - self._generate_centroids() # find new centroids - if old_centroids == self._centroids: # have centroids moved? + self._assign_clusters() # find cluster each point is closest to + old_centroids: List[DataPoint] = deepcopy(self._centroids) # record + self._generate_centroids() # find new centroids + if old_centroids == self._centroids: # have centroids moved? print(f"Converged after {iteration} iterations") return self._clusters return self._clusters diff --git a/Chapter6/mj.py b/Chapter6/mj.py index 8716c45..c44df75 100644 --- a/Chapter6/mj.py +++ b/Chapter6/mj.py @@ -32,12 +32,21 @@ def __repr__(self) -> str: if __name__ == "__main__": - albums: List[Album] = [Album("Got to Be There", 1972, 35.45, 10), Album("Ben", 1972, 31.31, 10), - Album("Music & Me", 1973, 32.09, 10), Album("Forever, Michael", 1975, 33.36, 10), - Album("Off the Wall", 1979, 42.28, 10), Album("Thriller", 1982, 42.19, 9), - Album("Bad", 1987, 48.16, 10), Album("Dangerous", 1991, 77.03, 14), - Album("HIStory: Past, Present and Future, Book I", 1995, 148.58, 30), Album("Invincible", 2001, 77.05, 16)] + albums: List[Album] = [ + Album("Got to Be There", 1972, 35.45, 10), + Album("Ben", 1972, 31.31, 10), + Album("Music & Me", 1973, 32.09, 10), + Album("Forever, Michael", 1975, 33.36, 10), + Album("Off the Wall", 1979, 42.28, 10), + Album("Thriller", 1982, 42.19, 9), + Album("Bad", 1987, 48.16, 10), + Album("Dangerous", 1991, 77.03, 14), + Album("HIStory: Past, Present and Future, Book I", 1995, 148.58, 30), + Album("Invincible", 2001, 77.05, 16), + ] kmeans: KMeans[Album] = KMeans(2, albums) clusters: List[KMeans.Cluster] = kmeans.run() for index, cluster in enumerate(clusters): - print(f"Cluster {index} Avg Length {cluster.centroid.dimensions[0]} Avg Tracks {cluster.centroid.dimensions[1]}: {cluster.points}\n") + print( + f"Cluster {index} Avg Length {cluster.centroid.dimensions[0]} Avg Tracks {cluster.centroid.dimensions[1]}: {cluster.points}\n" + ) diff --git a/Chapter7/iris_test.py b/Chapter7/iris_test.py index e83bb94..b7da01a 100644 --- a/Chapter7/iris_test.py +++ b/Chapter7/iris_test.py @@ -23,9 +23,9 @@ iris_parameters: List[List[float]] = [] iris_classifications: List[List[float]] = [] iris_species: List[str] = [] - with open('iris.csv', mode='r') as iris_file: + with open("iris.csv", mode="r") as iris_file: irises: List = list(csv.reader(iris_file)) - shuffle(irises) # get our lines of data in random order + shuffle(irises) # get our lines of data in random order for iris in irises: parameters: List[float] = [float(n) for n in iris[0:4]] iris_parameters.append(parameters) @@ -58,7 +58,7 @@ def iris_interpret_output(output: List[float]) -> str: # test over the last 10 of the irises in the data set iris_testers: List[List[float]] = iris_parameters[140:150] iris_testers_corrects: List[str] = iris_species[140:150] - iris_results = iris_network.validate(iris_testers, iris_testers_corrects, iris_interpret_output) + iris_results = iris_network.validate( + iris_testers, iris_testers_corrects, iris_interpret_output + ) print(f"{iris_results[0]} correct of {iris_results[1]} = {iris_results[2] * 100}%") - - diff --git a/Chapter7/layer.py b/Chapter7/layer.py index b133c4b..98d90a7 100644 --- a/Chapter7/layer.py +++ b/Chapter7/layer.py @@ -21,7 +21,14 @@ class Layer: - 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: + 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: self.previous_layer: Optional[Layer] = previous_layer self.neurons: List[Neuron] = [] # the following could all be one large list comprehension, but gets a bit long that way @@ -30,7 +37,12 @@ def __init__(self, previous_layer: Optional[Layer], num_neurons: int, learning_r random_weights: List[float] = [] else: random_weights = [random() for _ in range(len(previous_layer.neurons))] - neuron: Neuron = Neuron(random_weights, learning_rate, activation_function, derivative_activation_function) + neuron: Neuron = Neuron( + random_weights, + learning_rate, + activation_function, + derivative_activation_function, + ) self.neurons.append(neuron) self.output_cache: List[float] = [0.0 for _ in range(num_neurons)] @@ -44,7 +56,9 @@ def outputs(self, inputs: List[float]) -> List[float]: # should only be called on output layer def calculate_deltas_for_output_layer(self, expected: List[float]) -> None: for n in range(len(self.neurons)): - self.neurons[n].delta = self.neurons[n].derivative_activation_function(self.neurons[n].output_cache) * (expected[n] - self.output_cache[n]) + self.neurons[n].delta = self.neurons[n].derivative_activation_function( + self.neurons[n].output_cache + ) * (expected[n] - self.output_cache[n]) # should not be called on output layer def calculate_deltas_for_hidden_layer(self, next_layer: Layer) -> None: @@ -52,5 +66,7 @@ def calculate_deltas_for_hidden_layer(self, next_layer: Layer) -> None: next_weights: List[float] = [n.weights[index] for n in next_layer.neurons] next_deltas: List[float] = [n.delta for n in next_layer.neurons] sum_weights_and_deltas: float = dot_product(next_weights, next_deltas) - neuron.delta = neuron.derivative_activation_function(neuron.output_cache) * sum_weights_and_deltas - + neuron.delta = ( + neuron.derivative_activation_function(neuron.output_cache) + * sum_weights_and_deltas + ) diff --git a/Chapter7/network.py b/Chapter7/network.py index 59ac223..3e78272 100644 --- a/Chapter7/network.py +++ b/Chapter7/network.py @@ -19,20 +19,40 @@ from layer import Layer from util import sigmoid, derivative_sigmoid -T = TypeVar('T') # output type of interpretation of neural network +T = TypeVar("T") # output type of interpretation of neural network class Network: - 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: + 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: if len(layer_structure) < 3: - raise ValueError("Error: Should be at least 3 layers (1 input, 1 hidden, 1 output)") + raise ValueError( + "Error: Should be at least 3 layers (1 input, 1 hidden, 1 output)" + ) self.layers: List[Layer] = [] # input layer - input_layer: Layer = Layer(None, layer_structure[0], learning_rate, activation_function, derivative_activation_function) + input_layer: Layer = Layer( + None, + layer_structure[0], + learning_rate, + activation_function, + derivative_activation_function, + ) self.layers.append(input_layer) # hidden layers and output layer for previous, num_neurons in enumerate(layer_structure[1::]): - next_layer = Layer(self.layers[previous], num_neurons, learning_rate, activation_function, derivative_activation_function) + next_layer = Layer( + self.layers[previous], + num_neurons, + learning_rate, + activation_function, + derivative_activation_function, + ) self.layers.append(next_layer) # Pushes input data to the first layer, then output from the first @@ -54,10 +74,14 @@ def backpropagate(self, expected: List[float]) -> None: # this function uses the deltas calculated in backpropagate() to # actually make changes to the weights def update_weights(self) -> None: - for layer in self.layers[1:]: # skip input layer + for layer in self.layers[1:]: # skip input layer for neuron in layer.neurons: for w in range(len(neuron.weights)): - neuron.weights[w] = neuron.weights[w] + (neuron.learning_rate * (layer.previous_layer.output_cache[w]) * neuron.delta) + neuron.weights[w] = neuron.weights[w] + ( + neuron.learning_rate + * (layer.previous_layer.output_cache[w]) + * neuron.delta + ) # train() uses the results of outputs() run over many inputs and compared # against expecteds to feed backpropagate() and update_weights() @@ -70,7 +94,12 @@ def train(self, inputs: List[List[float]], expecteds: List[List[float]]) -> None # for generalized results that require classification this function will return # the correct number of trials and the percentage correct out of the total - def validate(self, inputs: List[List[float]], expecteds: List[T], interpret_output: Callable[[List[float]], T]) -> Tuple[int, int, float]: + def validate( + self, + inputs: List[List[float]], + expecteds: List[T], + interpret_output: Callable[[List[float]], T], + ) -> Tuple[int, int, float]: correct: int = 0 for input, expected in zip(inputs, expecteds): result: T = interpret_output(self.outputs(input)) @@ -78,4 +107,3 @@ def validate(self, inputs: List[List[float]], expecteds: List[T], interpret_outp correct += 1 percentage: float = correct / len(inputs) return correct, len(inputs), percentage - diff --git a/Chapter7/neuron.py b/Chapter7/neuron.py index 4b8e319..78246ce 100644 --- a/Chapter7/neuron.py +++ b/Chapter7/neuron.py @@ -18,10 +18,18 @@ class Neuron: - def __init__(self, weights: List[float], learning_rate: float, activation_function: Callable[[float], float], derivative_activation_function: Callable[[float], float]) -> None: + def __init__( + self, + weights: List[float], + learning_rate: float, + activation_function: Callable[[float], float], + derivative_activation_function: Callable[[float], float], + ) -> None: self.weights: List[float] = weights self.activation_function: Callable[[float], float] = activation_function - self.derivative_activation_function: Callable[[float], float] = derivative_activation_function + self.derivative_activation_function: Callable[ + [float], float + ] = derivative_activation_function self.learning_rate: float = learning_rate self.output_cache: float = 0.0 self.delta: float = 0.0 @@ -29,4 +37,3 @@ def __init__(self, weights: List[float], learning_rate: float, activation_functi def output(self, inputs: List[float]) -> float: self.output_cache = dot_product(inputs, self.weights) return self.activation_function(self.output_cache) - diff --git a/Chapter7/util.py b/Chapter7/util.py index b83a66e..00c9731 100644 --- a/Chapter7/util.py +++ b/Chapter7/util.py @@ -40,5 +40,6 @@ def normalize_by_feature_scaling(dataset: List[List[float]]) -> None: maximum = max(column) minimum = min(column) for row_num in range(len(dataset)): - dataset[row_num][col_num] = (dataset[row_num][col_num] - minimum) / (maximum - minimum) - + dataset[row_num][col_num] = (dataset[row_num][col_num] - minimum) / ( + maximum - minimum + ) diff --git a/Chapter7/wine_test.py b/Chapter7/wine_test.py index c0a7eec..6e25ac1 100644 --- a/Chapter7/wine_test.py +++ b/Chapter7/wine_test.py @@ -23,9 +23,9 @@ wine_parameters: List[List[float]] = [] wine_classifications: List[List[float]] = [] wine_species: List[int] = [] - with open('wine.csv', mode='r') as wine_file: + with open("wine.csv", mode="r") as wine_file: wines: List = list(csv.reader(wine_file, quoting=csv.QUOTE_NONNUMERIC)) - shuffle(wines) # get our lines of data in random order + shuffle(wines) # get our lines of data in random order for wine in wines: parameters: List[float] = [float(n) for n in wine[1:14]] wine_parameters.append(parameters) @@ -58,7 +58,7 @@ def wine_interpret_output(output: List[float]) -> int: # test over the last 28 of the wines in the data set wine_testers: List[List[float]] = wine_parameters[150:178] wine_testers_corrects: List[int] = wine_species[150:178] - wine_results = wine_network.validate(wine_testers, wine_testers_corrects, wine_interpret_output) + wine_results = wine_network.validate( + wine_testers, wine_testers_corrects, wine_interpret_output + ) print(f"{wine_results[0]} correct of {wine_results[1]} = {wine_results[2] * 100}%") - - diff --git a/Chapter8/board.py b/Chapter8/board.py index 5008a58..6d34de6 100644 --- a/Chapter8/board.py +++ b/Chapter8/board.py @@ -17,7 +17,7 @@ from typing import NewType, List from abc import ABC, abstractmethod -Move = NewType('Move', int) +Move = NewType("Move", int) class Piece: @@ -53,4 +53,3 @@ def is_draw(self) -> bool: @abstractmethod def evaluate(self, player: Piece) -> float: ... - diff --git a/Chapter8/connectfour.py b/Chapter8/connectfour.py index 98bfa22..bf11c59 100644 --- a/Chapter8/connectfour.py +++ b/Chapter8/connectfour.py @@ -22,7 +22,7 @@ class C4Piece(Piece, Enum): B = "B" R = "R" - E = " " # stand-in for empty + E = " " # stand-in for empty @property def opposite(self) -> C4Piece: @@ -37,7 +37,9 @@ def __str__(self) -> str: return self.value -def generate_segments(num_columns: int, num_rows: int, segment_length: int) -> List[List[Tuple[int, int]]]: +def generate_segments( + num_columns: int, num_rows: int, segment_length: int +) -> List[List[Tuple[int, int]]]: segments: List[List[Tuple[int, int]]] = [] # generate the vertical segments for c in range(num_columns): @@ -77,7 +79,9 @@ class C4Board(Board): NUM_ROWS: int = 6 NUM_COLUMNS: int = 7 SEGMENT_LENGTH: int = 4 - SEGMENTS: List[List[Tuple[int, int]]] = generate_segments(NUM_COLUMNS, NUM_ROWS, SEGMENT_LENGTH) + SEGMENTS: List[List[Tuple[int, int]]] = generate_segments( + NUM_COLUMNS, NUM_ROWS, SEGMENT_LENGTH + ) class Column: def __init__(self) -> None: @@ -105,9 +109,13 @@ def copy(self) -> C4Board.Column: temp._container = self._container.copy() return temp - def __init__(self, position: Optional[List[C4Board.Column]] = None, turn: C4Piece = C4Piece.B) -> None: + def __init__( + self, position: Optional[List[C4Board.Column]] = None, turn: C4Piece = C4Piece.B + ) -> None: if position is None: - self.position: List[C4Board.Column] = [C4Board.Column() for _ in range(C4Board.NUM_COLUMNS)] + self.position: List[C4Board.Column] = [ + C4Board.Column() for _ in range(C4Board.NUM_COLUMNS) + ] else: self.position = position self._turn: C4Piece = turn @@ -125,7 +133,9 @@ def move(self, location: Move) -> Board: @property def legal_moves(self) -> List[Move]: - return [Move(c) for c in range(C4Board.NUM_COLUMNS) if not self.position[c].full] + return [ + Move(c) for c in range(C4Board.NUM_COLUMNS) if not self.position[c].full + ] # Returns the count of black & red pieces in a segment def _count_segment(self, segment: List[Tuple[int, int]]) -> Tuple[int, int]: @@ -149,7 +159,7 @@ def is_win(self) -> bool: def _evaluate_segment(self, segment: List[Tuple[int, int]], player: Piece) -> float: black_count, red_count = self._count_segment(segment) if red_count > 0 and black_count > 0: - return 0 # mixed segments are neutral + return 0 # mixed segments are neutral count: int = max(red_count, black_count) score: float = 0 if count == 2: @@ -179,4 +189,3 @@ def __repr__(self) -> str: display += f"{self.position[c][r]}" + "|" display += "\n" return display - diff --git a/Chapter8/connectfour_ai.py b/Chapter8/connectfour_ai.py index a9df7ab..74a469b 100644 --- a/Chapter8/connectfour_ai.py +++ b/Chapter8/connectfour_ai.py @@ -49,5 +49,3 @@ def get_player_move() -> Move: elif board.is_draw: print("Draw!") break - - diff --git a/Chapter8/minimax.py b/Chapter8/minimax.py index 0d358ae..98d0bed 100644 --- a/Chapter8/minimax.py +++ b/Chapter8/minimax.py @@ -18,27 +18,42 @@ # Find the best possible outcome for original player -def minimax(board: Board, maximizing: bool, original_player: Piece, max_depth: int = 8) -> float: +def minimax( + board: Board, maximizing: bool, original_player: Piece, max_depth: int = 8 +) -> float: # Base case – terminal position or maximum depth reached if board.is_win or board.is_draw or max_depth == 0: return board.evaluate(original_player) # Recursive case - maximize your gains or minimize the opponent's gains if maximizing: - best_eval: float = float("-inf") # arbitrarily low starting point + best_eval: float = float("-inf") # arbitrarily low starting point for move in board.legal_moves: - result: float = minimax(board.move(move), False, original_player, max_depth - 1) - best_eval = max(result, best_eval) # we want the move with the highest evaluation + result: float = minimax( + board.move(move), False, original_player, max_depth - 1 + ) + best_eval = max( + result, best_eval + ) # we want the move with the highest evaluation return best_eval - else: # minimizing + else: # minimizing worst_eval: float = float("inf") for move in board.legal_moves: result = minimax(board.move(move), True, original_player, max_depth - 1) - worst_eval = min(result, worst_eval) # we want the move with the lowest evaluation + worst_eval = min( + result, worst_eval + ) # we want the move with the lowest evaluation return worst_eval -def alphabeta(board: Board, maximizing: bool, original_player: Piece, max_depth: int = 8, alpha: float = float("-inf"), beta: float = float("inf")) -> float: +def alphabeta( + board: Board, + maximizing: bool, + original_player: Piece, + max_depth: int = 8, + alpha: float = float("-inf"), + beta: float = float("inf"), +) -> float: # Base case – terminal position or maximum depth reached if board.is_win or board.is_draw or max_depth == 0: return board.evaluate(original_player) @@ -46,14 +61,18 @@ def alphabeta(board: Board, maximizing: bool, original_player: Piece, max_depth: # Recursive case - maximize your gains or minimize the opponent's gains if maximizing: for move in board.legal_moves: - result: float = alphabeta(board.move(move), False, original_player, max_depth - 1, alpha, beta) + result: float = alphabeta( + board.move(move), False, original_player, max_depth - 1, alpha, beta + ) alpha = max(result, alpha) if beta <= alpha: break return alpha else: # minimizing for move in board.legal_moves: - result = alphabeta(board.move(move), True, original_player, max_depth - 1, alpha, beta) + result = alphabeta( + board.move(move), True, original_player, max_depth - 1, alpha, beta + ) beta = min(result, beta) if beta <= alpha: break @@ -70,4 +89,4 @@ def find_best_move(board: Board, max_depth: int = 8) -> Move: if result > best_eval: best_eval = result best_move = move - return best_move \ No newline at end of file + return best_move diff --git a/Chapter8/tictactoe.py b/Chapter8/tictactoe.py index 74192ff..414216d 100644 --- a/Chapter8/tictactoe.py +++ b/Chapter8/tictactoe.py @@ -22,7 +22,7 @@ class TTTPiece(Piece, Enum): X = "X" O = "O" - E = " " # stand-in for empty + E = " " # stand-in for empty @property def opposite(self) -> TTTPiece: @@ -38,7 +38,9 @@ def __str__(self) -> str: class TTTBoard(Board): - def __init__(self, position: List[TTTPiece] = [TTTPiece.E] * 9, turn: TTTPiece = TTTPiece.X) -> None: + def __init__( + self, position: List[TTTPiece] = [TTTPiece.E] * 9, turn: TTTPiece = TTTPiece.X + ) -> None: self.position: List[TTTPiece] = position self._turn: TTTPiece = turn @@ -53,19 +55,39 @@ def move(self, location: Move) -> Board: @property def legal_moves(self) -> List[Move]: - return [Move(l) for l in range(len(self.position)) if self.position[l] == TTTPiece.E] + return [ + Move(l) for l in range(len(self.position)) if self.position[l] == TTTPiece.E + ] @property def is_win(self) -> bool: # three row, three column, and then two diagonal checks - return self.position[0] == self.position[1] and self.position[0] == self.position[2] and self.position[0] != TTTPiece.E or \ - self.position[3] == self.position[4] and self.position[3] == self.position[5] and self.position[3] != TTTPiece.E or \ - self.position[6] == self.position[7] and self.position[6] == self.position[8] and self.position[6] != TTTPiece.E or \ - self.position[0] == self.position[3] and self.position[0] == self.position[6] and self.position[0] != TTTPiece.E or \ - self.position[1] == self.position[4] and self.position[1] == self.position[7] and self.position[1] != TTTPiece.E or \ - self.position[2] == self.position[5] and self.position[2] == self.position[8] and self.position[2] != TTTPiece.E or \ - self.position[0] == self.position[4] and self.position[0] == self.position[8] and self.position[0] != TTTPiece.E or \ - self.position[2] == self.position[4] and self.position[2] == self.position[6] and self.position[2] != TTTPiece.E + return ( + self.position[0] == self.position[1] + and self.position[0] == self.position[2] + and self.position[0] != TTTPiece.E + or self.position[3] == self.position[4] + and self.position[3] == self.position[5] + and self.position[3] != TTTPiece.E + or self.position[6] == self.position[7] + and self.position[6] == self.position[8] + and self.position[6] != TTTPiece.E + or self.position[0] == self.position[3] + and self.position[0] == self.position[6] + and self.position[0] != TTTPiece.E + or self.position[1] == self.position[4] + and self.position[1] == self.position[7] + and self.position[1] != TTTPiece.E + or self.position[2] == self.position[5] + and self.position[2] == self.position[8] + and self.position[2] != TTTPiece.E + or self.position[0] == self.position[4] + and self.position[0] == self.position[8] + and self.position[0] != TTTPiece.E + or self.position[2] == self.position[4] + and self.position[2] == self.position[6] + and self.position[2] != TTTPiece.E + ) def evaluate(self, player: Piece) -> float: if self.is_win and self.turn == player: diff --git a/Chapter8/tictactoe_ai.py b/Chapter8/tictactoe_ai.py index 5132ace..11a6378 100644 --- a/Chapter8/tictactoe_ai.py +++ b/Chapter8/tictactoe_ai.py @@ -49,5 +49,3 @@ def get_player_move() -> Move: elif board.is_draw: print("Draw!") break - - diff --git a/Chapter8/tictactoe_tests.py b/Chapter8/tictactoe_tests.py index 9864e1a..d914bca 100644 --- a/Chapter8/tictactoe_tests.py +++ b/Chapter8/tictactoe_tests.py @@ -23,33 +23,55 @@ class TTTMinimaxTestCase(unittest.TestCase): def test_easy_position(self): # win in 1 move - to_win_easy_position: List[TTTPiece] = [TTTPiece.X, TTTPiece.O, TTTPiece.X, - TTTPiece.X, TTTPiece.E, TTTPiece.O, - TTTPiece.E, TTTPiece.E, TTTPiece.O] + to_win_easy_position: List[TTTPiece] = [ + TTTPiece.X, + TTTPiece.O, + TTTPiece.X, + TTTPiece.X, + TTTPiece.E, + TTTPiece.O, + TTTPiece.E, + TTTPiece.E, + TTTPiece.O, + ] test_board1: TTTBoard = TTTBoard(to_win_easy_position, TTTPiece.X) answer1: Move = find_best_move(test_board1) self.assertEqual(answer1, 6) def test_block_position(self): # must block O's win - to_block_position: List[TTTPiece] = [TTTPiece.X, TTTPiece.E, TTTPiece.E, - TTTPiece.E, TTTPiece.E, TTTPiece.O, - TTTPiece.E, TTTPiece.X, TTTPiece.O] + to_block_position: List[TTTPiece] = [ + TTTPiece.X, + TTTPiece.E, + TTTPiece.E, + TTTPiece.E, + TTTPiece.E, + TTTPiece.O, + TTTPiece.E, + TTTPiece.X, + TTTPiece.O, + ] test_board2: TTTBoard = TTTBoard(to_block_position, TTTPiece.X) answer2: Move = find_best_move(test_board2) self.assertEqual(answer2, 2) def test_hard_position(self): # find the best move to win 2 moves - to_win_hard_position: List[TTTPiece] = [TTTPiece.X, TTTPiece.E, TTTPiece.E, - TTTPiece.E, TTTPiece.E, TTTPiece.O, - TTTPiece.O, TTTPiece.X, TTTPiece.E] + to_win_hard_position: List[TTTPiece] = [ + TTTPiece.X, + TTTPiece.E, + TTTPiece.E, + TTTPiece.E, + TTTPiece.E, + TTTPiece.O, + TTTPiece.O, + TTTPiece.X, + TTTPiece.E, + ] test_board3: TTTBoard = TTTBoard(to_win_hard_position, TTTPiece.X) answer3: Move = find_best_move(test_board3) self.assertEqual(answer3, 1) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() - - diff --git a/Chapter9/knapsack.py b/Chapter9/knapsack.py index 9a09f27..1adaca0 100644 --- a/Chapter9/knapsack.py +++ b/Chapter9/knapsack.py @@ -24,20 +24,24 @@ class Item(NamedTuple): def knapsack(items: List[Item], max_capacity: int) -> List[Item]: # build up dynamic programming table - table: List[List[float]] = [[0.0 for _ in range(max_capacity + 1)] for _ in range(len(items) + 1)] + table: List[List[float]] = [ + [0.0 for _ in range(max_capacity + 1)] for _ in range(len(items) + 1) + ] for i, item in enumerate(items): for capacity in range(1, max_capacity + 1): previous_items_value: float = table[i][capacity] - if capacity >= item.weight: # item fits in knapsack + if capacity >= item.weight: # item fits in knapsack value_freeing_weight_for_item: float = table[i][capacity - item.weight] # only take if more valuable than previous item - table[i + 1][capacity] = max(value_freeing_weight_for_item + item.value, previous_items_value) - else: # no room for this item + table[i + 1][capacity] = max( + value_freeing_weight_for_item + item.value, previous_items_value + ) + else: # no room for this item table[i + 1][capacity] = previous_items_value # figure out solution from table solution: List[Item] = [] capacity = max_capacity - for i in range(len(items), 0, -1): # work backwards + for i in range(len(items), 0, -1): # work backwards # was this item used? if table[i - 1][capacity] != table[i][capacity]: solution.append(items[i - 1]) @@ -47,17 +51,17 @@ def knapsack(items: List[Item], max_capacity: int) -> List[Item]: if __name__ == "__main__": - items: List[Item] = [Item("television", 50, 500), - Item("candlesticks", 2, 300), - Item("stereo", 35, 400), - Item("laptop", 3, 1000), - Item("food", 15, 50), - Item("clothing", 20, 800), - Item("jewelry", 1, 4000), - Item("books", 100, 300), - Item("printer", 18, 30), - Item("refrigerator", 200, 700), - Item("painting", 10, 1000)] + items: List[Item] = [ + Item("television", 50, 500), + Item("candlesticks", 2, 300), + Item("stereo", 35, 400), + Item("laptop", 3, 1000), + Item("food", 15, 50), + Item("clothing", 20, 800), + Item("jewelry", 1, 4000), + Item("books", 100, 300), + Item("printer", 18, 30), + Item("refrigerator", 200, 700), + Item("painting", 10, 1000), + ] print(knapsack(items, 75)) - - diff --git a/Chapter9/phone_number_mnemonics.py b/Chapter9/phone_number_mnemonics.py index 19f39f4..c3dd3bb 100644 --- a/Chapter9/phone_number_mnemonics.py +++ b/Chapter9/phone_number_mnemonics.py @@ -16,16 +16,18 @@ from typing import Dict, Tuple, Iterable, List from itertools import product -phone_mapping: Dict[str, Tuple[str, ...]] = {"1": ("1",), - "2": ("a", "b", "c"), - "3": ("d", "e", "f"), - "4": ("g", "h", "i"), - "5": ("j", "k", "l"), - "6": ("m", "n", "o"), - "7": ("p", "q", "r", "s"), - "8": ("t", "u", "v"), - "9": ("w", "x", "y", "z"), - "0": ("0",)} +phone_mapping: Dict[str, Tuple[str, ...]] = { + "1": ("1",), + "2": ("a", "b", "c"), + "3": ("d", "e", "f"), + "4": ("g", "h", "i"), + "5": ("j", "k", "l"), + "6": ("m", "n", "o"), + "7": ("p", "q", "r", "s"), + "8": ("t", "u", "v"), + "9": ("w", "x", "y", "z"), + "0": ("0",), +} def possible_mnemonics(phone_number: str) -> Iterable[Tuple[str, ...]]: diff --git a/Chapter9/tsp.py b/Chapter9/tsp.py index e7bab73..1e25035 100644 --- a/Chapter9/tsp.py +++ b/Chapter9/tsp.py @@ -17,31 +17,36 @@ from itertools import permutations vt_distances: Dict[str, Dict[str, int]] = { - "Rutland": - {"Burlington": 67, - "White River Junction": 46, - "Bennington": 55, - "Brattleboro": 75}, - "Burlington": - {"Rutland": 67, - "White River Junction": 91, - "Bennington": 122, - "Brattleboro": 153}, - "White River Junction": - {"Rutland": 46, - "Burlington": 91, - "Bennington": 98, - "Brattleboro": 65}, - "Bennington": - {"Rutland": 55, - "Burlington": 122, - "White River Junction": 98, - "Brattleboro": 40}, - "Brattleboro": - {"Rutland": 75, - "Burlington": 153, - "White River Junction": 65, - "Bennington": 40} + "Rutland": { + "Burlington": 67, + "White River Junction": 46, + "Bennington": 55, + "Brattleboro": 75, + }, + "Burlington": { + "Rutland": 67, + "White River Junction": 91, + "Bennington": 122, + "Brattleboro": 153, + }, + "White River Junction": { + "Rutland": 46, + "Burlington": 91, + "Bennington": 98, + "Brattleboro": 65, + }, + "Bennington": { + "Rutland": 55, + "Burlington": 122, + "White River Junction": 98, + "Brattleboro": 40, + }, + "Brattleboro": { + "Rutland": 75, + "Burlington": 153, + "White River Junction": 65, + "Bennington": 40, + }, } vt_cities: Iterable[str] = vt_distances.keys() @@ -50,7 +55,7 @@ if __name__ == "__main__": best_path: Tuple[str, ...] - min_distance: int = 99999999999 # arbitrarily high number + min_distance: int = 99999999999 # arbitrarily high number for path in tsp_paths: distance: int = 0 last: str = path[0] @@ -61,5 +66,3 @@ min_distance = distance best_path = path print(f"The shortest path is {best_path} in {min_distance} miles.") - - From 4933f0e112226f4c14940725b6ca21ab84966802 Mon Sep 17 00:00:00 2001 From: Johnny Metz Date: Sun, 23 Jun 2019 00:08:18 -0700 Subject: [PATCH 2/4] Complete chapter 1 --- Chapter1/fib2.py | 9 +++++++-- Chapter1/fib3.py | 5 +++++ Chapter1/fib5.py | 11 +++++++---- Chapter1/fib6.py | 8 ++++---- Chapter1/hanoi.py | 5 ++++- Chapter1/trivial_compression.py | 27 +++++++++------------------ Chapter1/unbreakable_encryption.py | 4 +++- 7 files changed, 39 insertions(+), 30 deletions(-) diff --git a/Chapter1/fib2.py b/Chapter1/fib2.py index 048d4eb..b575068 100644 --- a/Chapter1/fib2.py +++ b/Chapter1/fib2.py @@ -22,5 +22,10 @@ def fib2(n: int) -> int: if __name__ == "__main__": - print(fib2(5)) - print(fib2(10)) + print(fib2(0)) # 0 + print(fib2(1)) # 1 + print(fib2(2)) # 1 + print(fib2(3)) # 2 + print(fib2(4)) # 3 + print(fib2(5)) # 5 + print(fib2(10)) # 55 diff --git a/Chapter1/fib3.py b/Chapter1/fib3.py index a32cb81..cb05d66 100644 --- a/Chapter1/fib3.py +++ b/Chapter1/fib3.py @@ -19,6 +19,11 @@ def fib3(n: int) -> int: + """ + Memoization is a technique in which you store the + results of computational tasks when they are + completed so that when you need them again + """ if n not in memo: memo[n] = fib3(n - 1) + fib3(n - 2) # memoization return memo[n] diff --git a/Chapter1/fib5.py b/Chapter1/fib5.py index dbb819e..374e4f3 100644 --- a/Chapter1/fib5.py +++ b/Chapter1/fib5.py @@ -16,11 +16,14 @@ def fib5(n: int) -> int: if n == 0: return n # special case - last: int = 0 # initially set to fib(0) - next: int = 1 # initially set to fib(1) + last_item: int = 0 # initially set to fib(0) + next_item: int = 1 # initially set to fib(1) for _ in range(1, n): - last, next = next, last + next - return next + last_item, next_item = next_item, last_item + next_item + # last_copy = last_item + # last_item = next_item + # next_item = last_copy + next_item + return next_item if __name__ == "__main__": diff --git a/Chapter1/fib6.py b/Chapter1/fib6.py index 61a2ce7..b1274a7 100644 --- a/Chapter1/fib6.py +++ b/Chapter1/fib6.py @@ -20,11 +20,11 @@ def fib6(n: int) -> Generator[int, None, None]: yield 0 # special case if n > 0: yield 1 # special case - last: int = 0 # initially set to fib(0) - next: int = 1 # initially set to fib(1) + last_item: int = 0 # initially set to fib(0) + next_item: int = 1 # initially set to fib(1) for _ in range(1, n): - last, next = next, last + next - yield next # main generation step + last_item, next_item = next_item, last_item + next_item + yield next_item # main generation step if __name__ == "__main__": diff --git a/Chapter1/hanoi.py b/Chapter1/hanoi.py index 30a00ee..8f0f5ad 100644 --- a/Chapter1/hanoi.py +++ b/Chapter1/hanoi.py @@ -50,7 +50,10 @@ def hanoi(begin: Stack[int], end: Stack[int], temp: Stack[int], n: int) -> None: if __name__ == "__main__": - hanoi(tower_a, tower_c, tower_b, num_discs) + # The challenge is to move the three discs, one at a time, from tower A to tower C. + # The topmost disc of any tower is the only one available for moving. + # A larger disc may never be on top of a smaller disc. + hanoi(begin=tower_a, end=tower_c, temp=tower_b, n=num_discs) print(tower_a) print(tower_b) print(tower_c) diff --git a/Chapter1/trivial_compression.py b/Chapter1/trivial_compression.py index d296176..5041041 100644 --- a/Chapter1/trivial_compression.py +++ b/Chapter1/trivial_compression.py @@ -15,6 +15,10 @@ # limitations under the License. +nucleotide_to_bits = {"A": 0b00, "C": 0b01, "G": 0b10, "T": 0b11} +bits_to_nucleotide = {v: k for k, v in nucleotide_to_bits.items()} + + class CompressedGene: def __init__(self, gene: str) -> None: self._compress(gene) @@ -23,16 +27,10 @@ def _compress(self, gene: str) -> None: self.bit_string: int = 1 # start with sentinel for nucleotide in gene.upper(): self.bit_string <<= 2 # shift left two bits - if nucleotide == "A": # change last two bits to 00 - self.bit_string |= 0b00 - elif nucleotide == "C": # change last two bits to 01 - self.bit_string |= 0b01 - elif nucleotide == "G": # change last two bits to 10 - self.bit_string |= 0b10 - elif nucleotide == "T": # change last two bits to 11 - self.bit_string |= 0b11 - else: + if nucleotide not in nucleotide_to_bits: raise ValueError("Invalid Nucleotide:{}".format(nucleotide)) + # change last two bits to xx + self.bit_string |= nucleotide_to_bits[nucleotide] def decompress(self) -> str: gene: str = "" @@ -40,16 +38,9 @@ def decompress(self) -> str: 0, self.bit_string.bit_length() - 1, 2 ): # - 1 to exclude sentinel bits: int = self.bit_string >> i & 0b11 # get just 2 relevant bits - if bits == 0b00: # A - gene += "A" - elif bits == 0b01: # C - gene += "C" - elif bits == 0b10: # G - gene += "G" - elif bits == 0b11: # T - gene += "T" - else: + if bits not in bits_to_nucleotide: raise ValueError("Invalid bits:{}".format(bits)) + gene += bits_to_nucleotide[bits] return gene[::-1] # [::-1] reverses string by slicing backwards def __str__(self) -> str: # string representation for pretty printing diff --git a/Chapter1/unbreakable_encryption.py b/Chapter1/unbreakable_encryption.py index 2a41151..8ad145d 100644 --- a/Chapter1/unbreakable_encryption.py +++ b/Chapter1/unbreakable_encryption.py @@ -27,18 +27,20 @@ def random_key(length: int) -> int: def encrypt(original: str) -> Tuple[int, int]: original_bytes: bytes = original.encode() dummy: int = random_key(len(original_bytes)) + # dummy: int = int.from_bytes(token_bytes(len(original_bytes)), "big") original_key: int = int.from_bytes(original_bytes, "big") encrypted: int = original_key ^ dummy # XOR return dummy, encrypted def decrypt(key1: int, key2: int) -> str: - decrypted: int = key1 ^ key2 # XOR + decrypted: int = key1 ^ key2 # XOR (same every time) temp: bytes = decrypted.to_bytes((decrypted.bit_length() + 7) // 8, "big") return temp.decode() if __name__ == "__main__": + # key1, key2 are different every time key1, key2 = encrypt("One Time Pad!") result: str = decrypt(key1, key2) print(result) From 3a82521097b81bd069def2bea796c492d6c82121 Mon Sep 17 00:00:00 2001 From: Johnny Metz Date: Sun, 23 Jun 2019 11:51:51 -0700 Subject: [PATCH 3/4] Improve variable names --- Chapter1/fib5.py | 14 +++++++------- Chapter1/fib6.py | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Chapter1/fib5.py b/Chapter1/fib5.py index 374e4f3..70aa3cb 100644 --- a/Chapter1/fib5.py +++ b/Chapter1/fib5.py @@ -16,14 +16,14 @@ def fib5(n: int) -> int: if n == 0: return n # special case - last_item: int = 0 # initially set to fib(0) - next_item: int = 1 # initially set to fib(1) + last: int = 0 # initially set to fib(0) + nxt: int = 1 # initially set to fib(1) for _ in range(1, n): - last_item, next_item = next_item, last_item + next_item - # last_copy = last_item - # last_item = next_item - # next_item = last_copy + next_item - return next_item + last, nxt = nxt, last + nxt + # last_copy = last + # last = nxt + # nxt = last_copy + nxt + return nxt if __name__ == "__main__": diff --git a/Chapter1/fib6.py b/Chapter1/fib6.py index b1274a7..c0a4cb8 100644 --- a/Chapter1/fib6.py +++ b/Chapter1/fib6.py @@ -20,11 +20,11 @@ def fib6(n: int) -> Generator[int, None, None]: yield 0 # special case if n > 0: yield 1 # special case - last_item: int = 0 # initially set to fib(0) - next_item: int = 1 # initially set to fib(1) + last: int = 0 # initially set to fib(0) + nxt: int = 1 # initially set to fib(1) for _ in range(1, n): - last_item, next_item = next_item, last_item + next_item - yield next_item # main generation step + last, nxt = nxt, last + nxt + yield nxt # main generation step if __name__ == "__main__": From c38fce1ba9cab7df2fe6f95df2e7bb3429f80283 Mon Sep 17 00:00:00 2001 From: Johnny Metz Date: Sat, 31 Aug 2019 11:11:51 -0700 Subject: [PATCH 4/4] Update dna search --- .gitignore | 3 +++ Chapter2/dna_search.py | 29 +++++++++++++++++++++++------ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 894a44c..a127971 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,6 @@ venv.bak/ # mypy .mypy_cache/ + +# vscode +.vscode/ diff --git a/Chapter2/dna_search.py b/Chapter2/dna_search.py index 3e05af8..06b44d6 100644 --- a/Chapter2/dna_search.py +++ b/Chapter2/dna_search.py @@ -28,39 +28,56 @@ def string_to_gene(s: str) -> Gene: for i in range(0, len(s), 3): if (i + 2) >= len(s): # don't run off end! return gene - # initialize codon out of three nucleotides + # initialize codon out of three nucleotides codon: Codon = (Nucleotide[s[i]], Nucleotide[s[i + 1]], Nucleotide[s[i + 2]]) gene.append(codon) # add codon to gene return gene my_gene: Gene = string_to_gene(gene_str) +# print(my_gene) +acg: Codon = (Nucleotide.A, Nucleotide.C, Nucleotide.G) +gat: Codon = (Nucleotide.G, Nucleotide.A, Nucleotide.T) def linear_contains(gene: Gene, key_codon: Codon) -> bool: - for codon in gene: - if codon == key_codon: - return True + """ + LINEAR SEARCH (aka sequential search) - O(n) + + Check every element in iterable until target element is found. + """ + # for codon in gene: + # if codon == key_codon: + # return True + if key_codon in gene: + return True return False -acg: Codon = (Nucleotide.A, Nucleotide.C, Nucleotide.G) -gat: Codon = (Nucleotide.G, Nucleotide.A, Nucleotide.T) print(linear_contains(my_gene, acg)) # True print(linear_contains(my_gene, gat)) # False def binary_contains(gene: Gene, key_codon: Codon) -> bool: + """ + BINARY SEARCH (half-interval search) - O(lg n) + + Check middle element, reduce range by half based on comparison to target element. + Iterable must be sorted to start. + """ low: int = 0 high: int = len(gene) - 1 while low <= high: # while there is still a search space mid: int = (low + high) // 2 + print(low, high, mid) if gene[mid] < key_codon: low = mid + 1 elif gene[mid] > key_codon: high = mid - 1 else: + assert gene[mid] == key_codon return True + print(low, high) return False