diff --git a/Chapter1/calculating_pi.py b/Chapter1/calculating_pi.py index 845426c..61abfc6 100644 --- a/Chapter1/calculating_pi.py +++ b/Chapter1/calculating_pi.py @@ -13,6 +13,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + + def calculate_pi(n_terms: int) -> float: numerator: float = 4.0 denominator: float = 1.0 @@ -26,4 +28,4 @@ def calculate_pi(n_terms: int) -> float: if __name__ == "__main__": - print(calculate_pi(1000000)) \ No newline at end of file + print(calculate_pi(1000000)) diff --git a/Chapter1/fib4.py b/Chapter1/fib4.py index 648b4f0..86446c2 100644 --- a/Chapter1/fib4.py +++ b/Chapter1/fib4.py @@ -25,4 +25,4 @@ def fib4(n: int) -> int: # same definition as fib2() if __name__ == "__main__": print(fib4(5)) - print(fib4(50)) \ No newline at end of file + print(fib4(50)) diff --git a/Chapter2/generic_search.py b/Chapter2/generic_search.py index 51b6ea3..6e0ceb7 100644 --- a/Chapter2/generic_search.py +++ b/Chapter2/generic_search.py @@ -16,7 +16,6 @@ from __future__ import annotations from typing import TypeVar, Iterable, Sequence, Generic, List, Callable, Set, Deque, Dict, Any, Optional from typing_extensions import Protocol -from functools import total_ordering from heapq import heappush, heappop T = TypeVar('T') @@ -175,7 +174,7 @@ def empty(self) -> bool: return not self._container # not is true for empty container def push(self, item: T) -> None: - heappush(self._container, item) # priority determined by order of item + heappush(self._container, item) # in by priority def pop(self) -> T: return heappop(self._container) # out by priority diff --git a/Chapter2/missionaries.py b/Chapter2/missionaries.py index 09be34c..92a5692 100644 --- a/Chapter2/missionaries.py +++ b/Chapter2/missionaries.py @@ -31,7 +31,7 @@ def __init__(self, missionaries: int, cannibals: int, boat: bool) -> None: 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 boast is on the {} bank.")\ + "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: diff --git a/Chapter3/queens.py b/Chapter3/queens.py index 8ef820f..31a82a5 100644 --- a/Chapter3/queens.py +++ b/Chapter3/queens.py @@ -23,8 +23,10 @@ def __init__(self, columns: List[int]) -> None: self.columns: List[int] = columns def satisfied(self, assignment: Dict[int, int]) -> bool: - for q1c, q1r in assignment.items(): # q1c = queen 1 column, q1r = queen 1 row - for q2c in range(q1c + 1, len(self.columns) + 1): # q2c = queen 2 column + # q1c = queen 1 column, q1r = queen 1 row + for q1c, q1r in assignment.items(): + # 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? diff --git a/Chapter3/word_search.py b/Chapter3/word_search.py index 890a0d5..9606c0f 100644 --- a/Chapter3/word_search.py +++ b/Chapter3/word_search.py @@ -43,8 +43,8 @@ def generate_domain(word: str, grid: Grid) -> List[List[GridLocation]]: length: int = len(word) for row in range(height): for col in range(width): - columns: range = range(col, col + length + 1) - rows: range = range(row, row + length + 1) + columns: range = range(col, col + length) + rows: range = range(row, row + length) if col + length <= width: # left to right domain.append([GridLocation(row, c) for c in columns]) @@ -55,7 +55,7 @@ def generate_domain(word: str, grid: Grid) -> List[List[GridLocation]]: # top to bottom domain.append([GridLocation(r, col) for r in rows]) # diagonal towards bottom left - if col - length >= 0: + if col + 1 - length >= 0: domain.append([GridLocation(r, col - (r - row)) for r in rows]) return domain diff --git a/Chapter4/dijkstra.py b/Chapter4/dijkstra.py index b172ce6..9bdee27 100644 --- a/Chapter4/dijkstra.py +++ b/Chapter4/dijkstra.py @@ -38,7 +38,8 @@ def __eq__(self, other: DijkstraNode) -> bool: def dijkstra(wg: WeightedGraph[V], root: V) -> Tuple[List[Optional[float]], Dict[int, WeightedEdge]]: first: int = wg.index_of(root) # find starting index - distances: List[Optional[float]] = [None] * wg.vertex_count # distances are unknown at first + # 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 pq: PriorityQueue[DijkstraNode] = PriorityQueue() @@ -47,12 +48,18 @@ def dijkstra(wg: WeightedGraph[V], root: V) -> Tuple[List[Optional[float]], Dict while not pq.empty: u: int = pq.pop().vertex # explore the next closest vertex dist_u: float = distances[u] # should already have seen it - for we in wg.edges_for_index(u): # look at every edge/vertex from the vertex in question - dist_v: float = distances[we.v] # the old distance to this vertex - if dist_v is None or dist_v > we.weight + dist_u: # no old distance or found shorter path - distances[we.v] = we.weight + dist_u # update distance to this vertex - path_dict[we.v] = we # update the edge on the shortest path to this vertex - pq.push(DijkstraNode(we.v, we.weight + dist_u)) # explore it soon + # look at every edge/vertex from the vertex in question + for we in wg.edges_for_index(u): + # the old distance to this vertex + dist_v: float = distances[we.v] + # no old distance or found shorter path + if dist_v is None or dist_v > we.weight + dist_u: + # update distance to this vertex + distances[we.v] = we.weight + dist_u + # update the edge on the shortest path to this vertex + path_dict[we.v] = we + # explore it soon + pq.push(DijkstraNode(we.v, we.weight + dist_u)) return distances, path_dict diff --git a/Chapter4/graph.py b/Chapter4/graph.py index f08601e..f92a5aa 100644 --- a/Chapter4/graph.py +++ b/Chapter4/graph.py @@ -21,7 +21,9 @@ class Graph(Generic[V]): - def __init__(self, vertices: List[V] = []) -> None: + def __init__(self, vertices: Optional[List[V]] = None) -> None: + if vertices is None: + vertices = [] self._vertices: List[V] = vertices self._edges: List[List[Edge]] = [[] for _ in vertices] diff --git a/Chapter4/mst.py b/Chapter4/mst.py index 77865a7..d9f7ba1 100644 --- a/Chapter4/mst.py +++ b/Chapter4/mst.py @@ -31,11 +31,12 @@ def mst(wg: WeightedGraph[V], start: int = 0) -> Optional[WeightedPath]: return None result: WeightedPath = [] # holds the final MST pq: PriorityQueue[WeightedEdge] = PriorityQueue() - visited: [bool] = [False] * wg.vertex_count # where we've been + visited: List[bool] = [False] * wg.vertex_count # where we've been def visit(index: int): visited[index] = True # mark as visited - for edge in wg.edges_for_index(index): # add all edges coming from here to pq + for edge in wg.edges_for_index(index): + # add all edges coming from here to pq if not visited[edge.v]: pq.push(edge) @@ -45,7 +46,8 @@ def visit(index: int): edge = pq.pop() if visited[edge.v]: continue # don't ever revisit - result.append(edge) # this is the current smallest, so add it to solution + # this is the current smallest, so add it to solution + result.append(edge) visit(edge.v) # visit where this connects return result diff --git a/Chapter4/priority_queue.py b/Chapter4/priority_queue.py index c4e36ef..a8dfc81 100644 --- a/Chapter4/priority_queue.py +++ b/Chapter4/priority_queue.py @@ -29,7 +29,7 @@ def empty(self) -> bool: return not self._container # not is true for empty container def push(self, item: T) -> None: - heappush(self._container, item) # priority determined by order of item + heappush(self._container, item) # in by priority def pop(self) -> T: return heappop(self._container) # out by priority diff --git a/Chapter4/weighted_edge.py b/Chapter4/weighted_edge.py index 4e0d095..666f2d7 100644 --- a/Chapter4/weighted_edge.py +++ b/Chapter4/weighted_edge.py @@ -26,7 +26,7 @@ def reversed(self) -> WeightedEdge: return WeightedEdge(self.v, self.u, self.weight) # so that we can order edges by weight to find the minimum weight edge - def __lt__(self, other: WeightedEdge): + def __lt__(self, other: WeightedEdge) -> bool: return self.weight < other.weight def __str__(self) -> str: diff --git a/Chapter4/weighted_graph.py b/Chapter4/weighted_graph.py index 6ea62b2..e96d5fa 100644 --- a/Chapter4/weighted_graph.py +++ b/Chapter4/weighted_graph.py @@ -21,7 +21,9 @@ class WeightedGraph(Generic[V], Graph[V]): - def __init__(self, vertices: List[V] = []) -> None: + def __init__(self, vertices: Optional[List[V]] = None) -> None: + if vertices is None: + vertices = [] self._vertices: List[V] = vertices self._edges: List[List[WeightedEdge]] = [[] for _ in vertices] diff --git a/Chapter5/chromosome.py b/Chapter5/chromosome.py index b75dbb1..3b26a40 100644 --- a/Chapter5/chromosome.py +++ b/Chapter5/chromosome.py @@ -20,7 +20,7 @@ T = TypeVar('T', bound='Chromosome') # for returning self -# Base class for all chromosomes; all methods must be o overridden +# Base class for all chromosomes; all methods must be overridden class Chromosome(ABC): @abstractmethod def fitness(self) -> float: diff --git a/Chapter5/genetic_algorithm.py b/Chapter5/genetic_algorithm.py index a56fbdf..7304882 100644 --- a/Chapter5/genetic_algorithm.py +++ b/Chapter5/genetic_algorithm.py @@ -77,7 +77,8 @@ def _mutate(self) -> None: def run(self) -> C: best: C = max(self._population, key=self._fitness_key) for generation in range(self._max_generations): - if best.fitness() >= self._threshold: # early exit if we beat threshold + # 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))}") self._reproduce_and_replace() @@ -85,5 +86,5 @@ def run(self) -> C: 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 + return best # best we found in _max_generations diff --git a/Chapter5/list_compression.py b/Chapter5/list_compression.py index 8ff37be..c85e32d 100644 --- a/Chapter5/list_compression.py +++ b/Chapter5/list_compression.py @@ -23,7 +23,8 @@ from sys import getsizeof from pickle import dumps -PEOPLE: List[str] = ["Michael", "Sarah", "Joshua", "Narine", "David", "Sajid", "Melanie", "Daniel", "Wei", "Dean", "Brian", "Murat", "Lisa"] # 165 bytes compressed +# 165 bytes compressed +PEOPLE: List[str] = ["Michael", "Sarah", "Joshua", "Narine", "David", "Sajid", "Melanie", "Daniel", "Wei", "Dean", "Brian", "Murat", "Lisa"] class ListCompression(Chromosome): diff --git a/Chapter6/governors.py b/Chapter6/governors.py index cfb526e..72111ad 100644 --- a/Chapter6/governors.py +++ b/Chapter6/governors.py @@ -59,4 +59,4 @@ def __repr__(self) -> str: kmeans: KMeans[Governor] = KMeans(2, governors) gov_clusters: List[KMeans.Cluster] = kmeans.run() for index, cluster in enumerate(gov_clusters): - print(f"Cluster {index}: {cluster.points}") + print(f"Cluster {index}: {cluster.points}\n") diff --git a/Chapter6/kmeans.py b/Chapter6/kmeans.py index 19e2f23..fdff5cf 100644 --- a/Chapter6/kmeans.py +++ b/Chapter6/kmeans.py @@ -100,7 +100,7 @@ def run(self, max_iterations: int = 100) -> List[KMeans.Cluster]: 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 centroids + 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") diff --git a/Chapter6/mj.py b/Chapter6/mj.py index a2023a9..8716c45 100644 --- a/Chapter6/mj.py +++ b/Chapter6/mj.py @@ -40,4 +40,4 @@ def __repr__(self) -> str: 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}") + print(f"Cluster {index} Avg Length {cluster.centroid.dimensions[0]} Avg Tracks {cluster.centroid.dimensions[1]}: {cluster.points}\n") diff --git a/Chapter8/connectfour.py b/Chapter8/connectfour.py index 6f6f900..98bfa22 100644 --- a/Chapter8/connectfour.py +++ b/Chapter8/connectfour.py @@ -33,6 +33,9 @@ def opposite(self) -> C4Piece: else: return C4Piece.E + def __str__(self) -> str: + return self.value + def generate_segments(num_columns: int, num_rows: int, segment_length: int) -> List[List[Tuple[int, int]]]: segments: List[List[Tuple[int, int]]] = [] diff --git a/Chapter8/minimax.py b/Chapter8/minimax.py index efdccee..0d358ae 100644 --- a/Chapter8/minimax.py +++ b/Chapter8/minimax.py @@ -60,7 +60,8 @@ def alphabeta(board: Board, maximizing: bool, original_player: Piece, max_depth: return beta -# Find the best possible move in the current position looking up to max_depth ahead +# Find the best possible move in the current position +# looking up to max_depth ahead def find_best_move(board: Board, max_depth: int = 8) -> Move: best_eval: float = float("-inf") best_move: Move = Move(-1) diff --git a/Chapter8/tictactoe.py b/Chapter8/tictactoe.py index 4547962..74192ff 100644 --- a/Chapter8/tictactoe.py +++ b/Chapter8/tictactoe.py @@ -33,6 +33,9 @@ def opposite(self) -> TTTPiece: else: return TTTPiece.E + def __str__(self) -> str: + return self.value + class TTTBoard(Board): def __init__(self, position: List[TTTPiece] = [TTTPiece.E] * 9, turn: TTTPiece = TTTPiece.X) -> None: diff --git a/README.md b/README.md index ac64832..b02c6ad 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,36 @@ # Classic Computer Science Problems in Python -This repository contains source code to accompany the forthcoming 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. +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.** + +![Classic Computer Science Problems in Python Cover](cover.jpg) ## Get the Book -The book is now available in early access form [through Manning's MEAP program](https://www.manning.com/books/classic-computer-science-problems-in-python). By purchasing the MEAP you will get access to each chapter's draft and join me on the manuscript's development journey. You will also receive the final version of the book upon publication in late 2018/early 2019. +- [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 +- [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 ## Versioning and Packages 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. +## Questions about the Book +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. + +## Free Content Based on the Book +- [Article: Constraint-Satisfaction Problems in Python](https://freecontent.manning.com/constraint-satisfaction-problems-in-python/) + ## License All of the source code in this repository is released under the Apache License version 2.0. See `LICENSE`. -## Other Books in the Series -This is the second book in the Classic Computer Science Problems series by David Kopec and published by Manning. It aims to teach classic computer science problems in a Pythonic way. You may also want to checkout the first book in the series, *Classic Computer Science Problems in Swift*, which covers most of the same problems in a more Swifty way. You can check out the repository for that book on [GitHub as well](https://github.com/davecom/ClassicComputerScienceProblemsInSwift). A reader has also reimplemented the first five chapters of the book [in C++](https://github.com/araya-andres/classic_computer_sci). +## Other Books and Languages +Official Books from the Series by @davecom +- [Classic Computer Science Problems in Java](https://github.com/davecom/ClassicComputerScienceProblemsInJava) +- [Classic Computer Science Problems in Swift](https://github.com/davecom/ClassicComputerScienceProblemsInSwift) + +My Latest Book +- [Computer Science from Scratch: Building Interpreters, Art, Emulators and ML in Python](https://github.com/davecom/ComputerScienceFromScratch) + +Ports +- [C++ implementation by @aray-andres](https://github.com/araya-andres/classic_computer_sci) +- [Go implementation by @arlima](https://github.com/arlima/problemas_classicos_CC) +- [PHP implementation by @SaschaKersken (German translator of CCSPiP)](https://github.com/SaschaKersken/ClassicComputerScienceProblemsInPhp) +- [JavaScript implementation by @SaschaKersken (German translator of CCSPiP)](https://github.com/SaschaKersken/ClassicComputerScienceProblemsInJavaScript) +- [Ruby implementation by @tj84](https://github.com/tj84/cs_problems) +- [Rust implementation by @marpetercontribs](https://github.com/marpetercontribs/classic-computer-science-problems-in-rust) diff --git a/cover.jpg b/cover.jpg new file mode 100644 index 0000000..a9739ea Binary files /dev/null and b/cover.jpg differ