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/maze.py b/Chapter2/maze.py index 65c8429..6283ba1 100644 --- a/Chapter2/maze.py +++ b/Chapter2/maze.py @@ -68,11 +68,11 @@ 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: locations.append(MazeLocation(ml.row + 1, ml.column)) - if ml.row - 1 > 0 and self._grid[ml.row - 1][ml.column] != Cell.BLOCKED: + 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: locations.append(MazeLocation(ml.row, ml.column + 1)) - if ml.column - 1 > 0 and self._grid[ml.row][ml.column - 1] != Cell.BLOCKED: + if ml.column - 1 >= 0 and self._grid[ml.row][ml.column - 1] != Cell.BLOCKED: locations.append(MazeLocation(ml.row, ml.column - 1)) return locations 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/Chapter7/iris.csv b/Chapter7/iris.csv new file mode 100644 index 0000000..2bf7d09 --- /dev/null +++ b/Chapter7/iris.csv @@ -0,0 +1,150 @@ +5.1,3.5,1.4,0.2,Iris-setosa +4.9,3.0,1.4,0.2,Iris-setosa +4.7,3.2,1.3,0.2,Iris-setosa +4.6,3.1,1.5,0.2,Iris-setosa +5.0,3.6,1.4,0.2,Iris-setosa +5.4,3.9,1.7,0.4,Iris-setosa +4.6,3.4,1.4,0.3,Iris-setosa +5.0,3.4,1.5,0.2,Iris-setosa +4.4,2.9,1.4,0.2,Iris-setosa +4.9,3.1,1.5,0.1,Iris-setosa +5.4,3.7,1.5,0.2,Iris-setosa +4.8,3.4,1.6,0.2,Iris-setosa +4.8,3.0,1.4,0.1,Iris-setosa +4.3,3.0,1.1,0.1,Iris-setosa +5.8,4.0,1.2,0.2,Iris-setosa +5.7,4.4,1.5,0.4,Iris-setosa +5.4,3.9,1.3,0.4,Iris-setosa +5.1,3.5,1.4,0.3,Iris-setosa +5.7,3.8,1.7,0.3,Iris-setosa +5.1,3.8,1.5,0.3,Iris-setosa +5.4,3.4,1.7,0.2,Iris-setosa +5.1,3.7,1.5,0.4,Iris-setosa +4.6,3.6,1.0,0.2,Iris-setosa +5.1,3.3,1.7,0.5,Iris-setosa +4.8,3.4,1.9,0.2,Iris-setosa +5.0,3.0,1.6,0.2,Iris-setosa +5.0,3.4,1.6,0.4,Iris-setosa +5.2,3.5,1.5,0.2,Iris-setosa +5.2,3.4,1.4,0.2,Iris-setosa +4.7,3.2,1.6,0.2,Iris-setosa +4.8,3.1,1.6,0.2,Iris-setosa +5.4,3.4,1.5,0.4,Iris-setosa +5.2,4.1,1.5,0.1,Iris-setosa +5.5,4.2,1.4,0.2,Iris-setosa +4.9,3.1,1.5,0.2,Iris-setosa +5.0,3.2,1.2,0.2,Iris-setosa +5.5,3.5,1.3,0.2,Iris-setosa +4.9,3.6,1.4,0.1,Iris-setosa +4.4,3.0,1.3,0.2,Iris-setosa +5.1,3.4,1.5,0.2,Iris-setosa +5.0,3.5,1.3,0.3,Iris-setosa +4.5,2.3,1.3,0.3,Iris-setosa +4.4,3.2,1.3,0.2,Iris-setosa +5.0,3.5,1.6,0.6,Iris-setosa +5.1,3.8,1.9,0.4,Iris-setosa +4.8,3.0,1.4,0.3,Iris-setosa +5.1,3.8,1.6,0.2,Iris-setosa +4.6,3.2,1.4,0.2,Iris-setosa +5.3,3.7,1.5,0.2,Iris-setosa +5.0,3.3,1.4,0.2,Iris-setosa +7.0,3.2,4.7,1.4,Iris-versicolor +6.4,3.2,4.5,1.5,Iris-versicolor +6.9,3.1,4.9,1.5,Iris-versicolor +5.5,2.3,4.0,1.3,Iris-versicolor +6.5,2.8,4.6,1.5,Iris-versicolor +5.7,2.8,4.5,1.3,Iris-versicolor +6.3,3.3,4.7,1.6,Iris-versicolor +4.9,2.4,3.3,1.0,Iris-versicolor +6.6,2.9,4.6,1.3,Iris-versicolor +5.2,2.7,3.9,1.4,Iris-versicolor +5.0,2.0,3.5,1.0,Iris-versicolor +5.9,3.0,4.2,1.5,Iris-versicolor +6.0,2.2,4.0,1.0,Iris-versicolor +6.1,2.9,4.7,1.4,Iris-versicolor +5.6,2.9,3.6,1.3,Iris-versicolor +6.7,3.1,4.4,1.4,Iris-versicolor +5.6,3.0,4.5,1.5,Iris-versicolor +5.8,2.7,4.1,1.0,Iris-versicolor +6.2,2.2,4.5,1.5,Iris-versicolor +5.6,2.5,3.9,1.1,Iris-versicolor +5.9,3.2,4.8,1.8,Iris-versicolor +6.1,2.8,4.0,1.3,Iris-versicolor +6.3,2.5,4.9,1.5,Iris-versicolor +6.1,2.8,4.7,1.2,Iris-versicolor +6.4,2.9,4.3,1.3,Iris-versicolor +6.6,3.0,4.4,1.4,Iris-versicolor +6.8,2.8,4.8,1.4,Iris-versicolor +6.7,3.0,5.0,1.7,Iris-versicolor +6.0,2.9,4.5,1.5,Iris-versicolor +5.7,2.6,3.5,1.0,Iris-versicolor +5.5,2.4,3.8,1.1,Iris-versicolor +5.5,2.4,3.7,1.0,Iris-versicolor +5.8,2.7,3.9,1.2,Iris-versicolor +6.0,2.7,5.1,1.6,Iris-versicolor +5.4,3.0,4.5,1.5,Iris-versicolor +6.0,3.4,4.5,1.6,Iris-versicolor +6.7,3.1,4.7,1.5,Iris-versicolor +6.3,2.3,4.4,1.3,Iris-versicolor +5.6,3.0,4.1,1.3,Iris-versicolor +5.5,2.5,4.0,1.3,Iris-versicolor +5.5,2.6,4.4,1.2,Iris-versicolor +6.1,3.0,4.6,1.4,Iris-versicolor +5.8,2.6,4.0,1.2,Iris-versicolor +5.0,2.3,3.3,1.0,Iris-versicolor +5.6,2.7,4.2,1.3,Iris-versicolor +5.7,3.0,4.2,1.2,Iris-versicolor +5.7,2.9,4.2,1.3,Iris-versicolor +6.2,2.9,4.3,1.3,Iris-versicolor +5.1,2.5,3.0,1.1,Iris-versicolor +5.7,2.8,4.1,1.3,Iris-versicolor +6.3,3.3,6.0,2.5,Iris-virginica +5.8,2.7,5.1,1.9,Iris-virginica +7.1,3.0,5.9,2.1,Iris-virginica +6.3,2.9,5.6,1.8,Iris-virginica +6.5,3.0,5.8,2.2,Iris-virginica +7.6,3.0,6.6,2.1,Iris-virginica +4.9,2.5,4.5,1.7,Iris-virginica +7.3,2.9,6.3,1.8,Iris-virginica +6.7,2.5,5.8,1.8,Iris-virginica +7.2,3.6,6.1,2.5,Iris-virginica +6.5,3.2,5.1,2.0,Iris-virginica +6.4,2.7,5.3,1.9,Iris-virginica +6.8,3.0,5.5,2.1,Iris-virginica +5.7,2.5,5.0,2.0,Iris-virginica +5.8,2.8,5.1,2.4,Iris-virginica +6.4,3.2,5.3,2.3,Iris-virginica +6.5,3.0,5.5,1.8,Iris-virginica +7.7,3.8,6.7,2.2,Iris-virginica +7.7,2.6,6.9,2.3,Iris-virginica +6.0,2.2,5.0,1.5,Iris-virginica +6.9,3.2,5.7,2.3,Iris-virginica +5.6,2.8,4.9,2.0,Iris-virginica +7.7,2.8,6.7,2.0,Iris-virginica +6.3,2.7,4.9,1.8,Iris-virginica +6.7,3.3,5.7,2.1,Iris-virginica +7.2,3.2,6.0,1.8,Iris-virginica +6.2,2.8,4.8,1.8,Iris-virginica +6.1,3.0,4.9,1.8,Iris-virginica +6.4,2.8,5.6,2.1,Iris-virginica +7.2,3.0,5.8,1.6,Iris-virginica +7.4,2.8,6.1,1.9,Iris-virginica +7.9,3.8,6.4,2.0,Iris-virginica +6.4,2.8,5.6,2.2,Iris-virginica +6.3,2.8,5.1,1.5,Iris-virginica +6.1,2.6,5.6,1.4,Iris-virginica +7.7,3.0,6.1,2.3,Iris-virginica +6.3,3.4,5.6,2.4,Iris-virginica +6.4,3.1,5.5,1.8,Iris-virginica +6.0,3.0,4.8,1.8,Iris-virginica +6.9,3.1,5.4,2.1,Iris-virginica +6.7,3.1,5.6,2.4,Iris-virginica +6.9,3.1,5.1,2.3,Iris-virginica +5.8,2.7,5.1,1.9,Iris-virginica +6.8,3.2,5.9,2.3,Iris-virginica +6.7,3.3,5.7,2.5,Iris-virginica +6.7,3.0,5.2,2.3,Iris-virginica +6.3,2.5,5.0,1.9,Iris-virginica +6.5,3.0,5.2,2.0,Iris-virginica +6.2,3.4,5.4,2.3,Iris-virginica +5.9,3.0,5.1,1.8,Iris-virginica diff --git a/Chapter7/iris_test.py b/Chapter7/iris_test.py new file mode 100644 index 0000000..e83bb94 --- /dev/null +++ b/Chapter7/iris_test.py @@ -0,0 +1,64 @@ +# iris_test.py +# From Classic Computer Science Problems in Python Chapter 7 +# Copyright 2018 David Kopec +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +import csv +from typing import List +from util import normalize_by_feature_scaling +from network import Network +from random import shuffle + +if __name__ == "__main__": + iris_parameters: List[List[float]] = [] + iris_classifications: List[List[float]] = [] + iris_species: List[str] = [] + 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 + for iris in irises: + parameters: List[float] = [float(n) for n in iris[0:4]] + iris_parameters.append(parameters) + species: str = iris[4] + if species == "Iris-setosa": + iris_classifications.append([1.0, 0.0, 0.0]) + elif species == "Iris-versicolor": + iris_classifications.append([0.0, 1.0, 0.0]) + else: + iris_classifications.append([0.0, 0.0, 1.0]) + iris_species.append(species) + normalize_by_feature_scaling(iris_parameters) + + iris_network: Network = Network([4, 6, 3], 0.3) + + def iris_interpret_output(output: List[float]) -> str: + if max(output) == output[0]: + return "Iris-setosa" + elif max(output) == output[1]: + return "Iris-versicolor" + else: + return "Iris-virginica" + + # train over the first 140 irises in the data set 50 times + iris_trainers: List[List[float]] = iris_parameters[0:140] + iris_trainers_corrects: List[List[float]] = iris_classifications[0:140] + for _ in range(50): + iris_network.train(iris_trainers, iris_trainers_corrects) + + # 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) + print(f"{iris_results[0]} correct of {iris_results[1]} = {iris_results[2] * 100}%") + + diff --git a/Chapter7/util.py b/Chapter7/util.py index b0ac637..b83a66e 100644 --- a/Chapter7/util.py +++ b/Chapter7/util.py @@ -24,9 +24,21 @@ def dot_product(xs: List[float], ys: List[float]) -> float: # the classic sigmoid activation function def sigmoid(x: float) -> float: - return 1.0 / (1.0 + exp(x)) + return 1.0 / (1.0 + exp(-x)) def derivative_sigmoid(x: float) -> float: - sig = sigmoid(x) + sig: float = sigmoid(x) return sig * (1 - sig) + + +# assume all rows are of equal length +# and feature scale each column to be in the range 0 - 1 +def normalize_by_feature_scaling(dataset: List[List[float]]) -> None: + for col_num in range(len(dataset[0])): + column: List[float] = [row[col_num] for row in dataset] + 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) + diff --git a/Chapter7/wine.csv b/Chapter7/wine.csv new file mode 100644 index 0000000..a0b3962 --- /dev/null +++ b/Chapter7/wine.csv @@ -0,0 +1,178 @@ +1,14.23,1.71,2.43,15.6,127,2.8,3.06,.28,2.29,5.64,1.04,3.92,1065 +1,13.2,1.78,2.14,11.2,100,2.65,2.76,.26,1.28,4.38,1.05,3.4,1050 +1,13.16,2.36,2.67,18.6,101,2.8,3.24,.3,2.81,5.68,1.03,3.17,1185 +1,14.37,1.95,2.5,16.8,113,3.85,3.49,.24,2.18,7.8,.86,3.45,1480 +1,13.24,2.59,2.87,21,118,2.8,2.69,.39,1.82,4.32,1.04,2.93,735 +1,14.2,1.76,2.45,15.2,112,3.27,3.39,.34,1.97,6.75,1.05,2.85,1450 +1,14.39,1.87,2.45,14.6,96,2.5,2.52,.3,1.98,5.25,1.02,3.58,1290 +1,14.06,2.15,2.61,17.6,121,2.6,2.51,.31,1.25,5.05,1.06,3.58,1295 +1,14.83,1.64,2.17,14,97,2.8,2.98,.29,1.98,5.2,1.08,2.85,1045 +1,13.86,1.35,2.27,16,98,2.98,3.15,.22,1.85,7.22,1.01,3.55,1045 +1,14.1,2.16,2.3,18,105,2.95,3.32,.22,2.38,5.75,1.25,3.17,1510 +1,14.12,1.48,2.32,16.8,95,2.2,2.43,.26,1.57,5,1.17,2.82,1280 +1,13.75,1.73,2.41,16,89,2.6,2.76,.29,1.81,5.6,1.15,2.9,1320 +1,14.75,1.73,2.39,11.4,91,3.1,3.69,.43,2.81,5.4,1.25,2.73,1150 +1,14.38,1.87,2.38,12,102,3.3,3.64,.29,2.96,7.5,1.2,3,1547 +1,13.63,1.81,2.7,17.2,112,2.85,2.91,.3,1.46,7.3,1.28,2.88,1310 +1,14.3,1.92,2.72,20,120,2.8,3.14,.33,1.97,6.2,1.07,2.65,1280 +1,13.83,1.57,2.62,20,115,2.95,3.4,.4,1.72,6.6,1.13,2.57,1130 +1,14.19,1.59,2.48,16.5,108,3.3,3.93,.32,1.86,8.7,1.23,2.82,1680 +1,13.64,3.1,2.56,15.2,116,2.7,3.03,.17,1.66,5.1,.96,3.36,845 +1,14.06,1.63,2.28,16,126,3,3.17,.24,2.1,5.65,1.09,3.71,780 +1,12.93,3.8,2.65,18.6,102,2.41,2.41,.25,1.98,4.5,1.03,3.52,770 +1,13.71,1.86,2.36,16.6,101,2.61,2.88,.27,1.69,3.8,1.11,4,1035 +1,12.85,1.6,2.52,17.8,95,2.48,2.37,.26,1.46,3.93,1.09,3.63,1015 +1,13.5,1.81,2.61,20,96,2.53,2.61,.28,1.66,3.52,1.12,3.82,845 +1,13.05,2.05,3.22,25,124,2.63,2.68,.47,1.92,3.58,1.13,3.2,830 +1,13.39,1.77,2.62,16.1,93,2.85,2.94,.34,1.45,4.8,.92,3.22,1195 +1,13.3,1.72,2.14,17,94,2.4,2.19,.27,1.35,3.95,1.02,2.77,1285 +1,13.87,1.9,2.8,19.4,107,2.95,2.97,.37,1.76,4.5,1.25,3.4,915 +1,14.02,1.68,2.21,16,96,2.65,2.33,.26,1.98,4.7,1.04,3.59,1035 +1,13.73,1.5,2.7,22.5,101,3,3.25,.29,2.38,5.7,1.19,2.71,1285 +1,13.58,1.66,2.36,19.1,106,2.86,3.19,.22,1.95,6.9,1.09,2.88,1515 +1,13.68,1.83,2.36,17.2,104,2.42,2.69,.42,1.97,3.84,1.23,2.87,990 +1,13.76,1.53,2.7,19.5,132,2.95,2.74,.5,1.35,5.4,1.25,3,1235 +1,13.51,1.8,2.65,19,110,2.35,2.53,.29,1.54,4.2,1.1,2.87,1095 +1,13.48,1.81,2.41,20.5,100,2.7,2.98,.26,1.86,5.1,1.04,3.47,920 +1,13.28,1.64,2.84,15.5,110,2.6,2.68,.34,1.36,4.6,1.09,2.78,880 +1,13.05,1.65,2.55,18,98,2.45,2.43,.29,1.44,4.25,1.12,2.51,1105 +1,13.07,1.5,2.1,15.5,98,2.4,2.64,.28,1.37,3.7,1.18,2.69,1020 +1,14.22,3.99,2.51,13.2,128,3,3.04,.2,2.08,5.1,.89,3.53,760 +1,13.56,1.71,2.31,16.2,117,3.15,3.29,.34,2.34,6.13,.95,3.38,795 +1,13.41,3.84,2.12,18.8,90,2.45,2.68,.27,1.48,4.28,.91,3,1035 +1,13.88,1.89,2.59,15,101,3.25,3.56,.17,1.7,5.43,.88,3.56,1095 +1,13.24,3.98,2.29,17.5,103,2.64,2.63,.32,1.66,4.36,.82,3,680 +1,13.05,1.77,2.1,17,107,3,3,.28,2.03,5.04,.88,3.35,885 +1,14.21,4.04,2.44,18.9,111,2.85,2.65,.3,1.25,5.24,.87,3.33,1080 +1,14.38,3.59,2.28,16,102,3.25,3.17,.27,2.19,4.9,1.04,3.44,1065 +1,13.9,1.68,2.12,16,101,3.1,3.39,.21,2.14,6.1,.91,3.33,985 +1,14.1,2.02,2.4,18.8,103,2.75,2.92,.32,2.38,6.2,1.07,2.75,1060 +1,13.94,1.73,2.27,17.4,108,2.88,3.54,.32,2.08,8.90,1.12,3.1,1260 +1,13.05,1.73,2.04,12.4,92,2.72,3.27,.17,2.91,7.2,1.12,2.91,1150 +1,13.83,1.65,2.6,17.2,94,2.45,2.99,.22,2.29,5.6,1.24,3.37,1265 +1,13.82,1.75,2.42,14,111,3.88,3.74,.32,1.87,7.05,1.01,3.26,1190 +1,13.77,1.9,2.68,17.1,115,3,2.79,.39,1.68,6.3,1.13,2.93,1375 +1,13.74,1.67,2.25,16.4,118,2.6,2.9,.21,1.62,5.85,.92,3.2,1060 +1,13.56,1.73,2.46,20.5,116,2.96,2.78,.2,2.45,6.25,.98,3.03,1120 +1,14.22,1.7,2.3,16.3,118,3.2,3,.26,2.03,6.38,.94,3.31,970 +1,13.29,1.97,2.68,16.8,102,3,3.23,.31,1.66,6,1.07,2.84,1270 +1,13.72,1.43,2.5,16.7,108,3.4,3.67,.19,2.04,6.8,.89,2.87,1285 +2,12.37,.94,1.36,10.6,88,1.98,.57,.28,.42,1.95,1.05,1.82,520 +2,12.33,1.1,2.28,16,101,2.05,1.09,.63,.41,3.27,1.25,1.67,680 +2,12.64,1.36,2.02,16.8,100,2.02,1.41,.53,.62,5.75,.98,1.59,450 +2,13.67,1.25,1.92,18,94,2.1,1.79,.32,.73,3.8,1.23,2.46,630 +2,12.37,1.13,2.16,19,87,3.5,3.1,.19,1.87,4.45,1.22,2.87,420 +2,12.17,1.45,2.53,19,104,1.89,1.75,.45,1.03,2.95,1.45,2.23,355 +2,12.37,1.21,2.56,18.1,98,2.42,2.65,.37,2.08,4.6,1.19,2.3,678 +2,13.11,1.01,1.7,15,78,2.98,3.18,.26,2.28,5.3,1.12,3.18,502 +2,12.37,1.17,1.92,19.6,78,2.11,2,.27,1.04,4.68,1.12,3.48,510 +2,13.34,.94,2.36,17,110,2.53,1.3,.55,.42,3.17,1.02,1.93,750 +2,12.21,1.19,1.75,16.8,151,1.85,1.28,.14,2.5,2.85,1.28,3.07,718 +2,12.29,1.61,2.21,20.4,103,1.1,1.02,.37,1.46,3.05,.906,1.82,870 +2,13.86,1.51,2.67,25,86,2.95,2.86,.21,1.87,3.38,1.36,3.16,410 +2,13.49,1.66,2.24,24,87,1.88,1.84,.27,1.03,3.74,.98,2.78,472 +2,12.99,1.67,2.6,30,139,3.3,2.89,.21,1.96,3.35,1.31,3.5,985 +2,11.96,1.09,2.3,21,101,3.38,2.14,.13,1.65,3.21,.99,3.13,886 +2,11.66,1.88,1.92,16,97,1.61,1.57,.34,1.15,3.8,1.23,2.14,428 +2,13.03,.9,1.71,16,86,1.95,2.03,.24,1.46,4.6,1.19,2.48,392 +2,11.84,2.89,2.23,18,112,1.72,1.32,.43,.95,2.65,.96,2.52,500 +2,12.33,.99,1.95,14.8,136,1.9,1.85,.35,2.76,3.4,1.06,2.31,750 +2,12.7,3.87,2.4,23,101,2.83,2.55,.43,1.95,2.57,1.19,3.13,463 +2,12,.92,2,19,86,2.42,2.26,.3,1.43,2.5,1.38,3.12,278 +2,12.72,1.81,2.2,18.8,86,2.2,2.53,.26,1.77,3.9,1.16,3.14,714 +2,12.08,1.13,2.51,24,78,2,1.58,.4,1.4,2.2,1.31,2.72,630 +2,13.05,3.86,2.32,22.5,85,1.65,1.59,.61,1.62,4.8,.84,2.01,515 +2,11.84,.89,2.58,18,94,2.2,2.21,.22,2.35,3.05,.79,3.08,520 +2,12.67,.98,2.24,18,99,2.2,1.94,.3,1.46,2.62,1.23,3.16,450 +2,12.16,1.61,2.31,22.8,90,1.78,1.69,.43,1.56,2.45,1.33,2.26,495 +2,11.65,1.67,2.62,26,88,1.92,1.61,.4,1.34,2.6,1.36,3.21,562 +2,11.64,2.06,2.46,21.6,84,1.95,1.69,.48,1.35,2.8,1,2.75,680 +2,12.08,1.33,2.3,23.6,70,2.2,1.59,.42,1.38,1.74,1.07,3.21,625 +2,12.08,1.83,2.32,18.5,81,1.6,1.5,.52,1.64,2.4,1.08,2.27,480 +2,12,1.51,2.42,22,86,1.45,1.25,.5,1.63,3.6,1.05,2.65,450 +2,12.69,1.53,2.26,20.7,80,1.38,1.46,.58,1.62,3.05,.96,2.06,495 +2,12.29,2.83,2.22,18,88,2.45,2.25,.25,1.99,2.15,1.15,3.3,290 +2,11.62,1.99,2.28,18,98,3.02,2.26,.17,1.35,3.25,1.16,2.96,345 +2,12.47,1.52,2.2,19,162,2.5,2.27,.32,3.28,2.6,1.16,2.63,937 +2,11.81,2.12,2.74,21.5,134,1.6,.99,.14,1.56,2.5,.95,2.26,625 +2,12.29,1.41,1.98,16,85,2.55,2.5,.29,1.77,2.9,1.23,2.74,428 +2,12.37,1.07,2.1,18.5,88,3.52,3.75,.24,1.95,4.5,1.04,2.77,660 +2,12.29,3.17,2.21,18,88,2.85,2.99,.45,2.81,2.3,1.42,2.83,406 +2,12.08,2.08,1.7,17.5,97,2.23,2.17,.26,1.4,3.3,1.27,2.96,710 +2,12.6,1.34,1.9,18.5,88,1.45,1.36,.29,1.35,2.45,1.04,2.77,562 +2,12.34,2.45,2.46,21,98,2.56,2.11,.34,1.31,2.8,.8,3.38,438 +2,11.82,1.72,1.88,19.5,86,2.5,1.64,.37,1.42,2.06,.94,2.44,415 +2,12.51,1.73,1.98,20.5,85,2.2,1.92,.32,1.48,2.94,1.04,3.57,672 +2,12.42,2.55,2.27,22,90,1.68,1.84,.66,1.42,2.7,.86,3.3,315 +2,12.25,1.73,2.12,19,80,1.65,2.03,.37,1.63,3.4,1,3.17,510 +2,12.72,1.75,2.28,22.5,84,1.38,1.76,.48,1.63,3.3,.88,2.42,488 +2,12.22,1.29,1.94,19,92,2.36,2.04,.39,2.08,2.7,.86,3.02,312 +2,11.61,1.35,2.7,20,94,2.74,2.92,.29,2.49,2.65,.96,3.26,680 +2,11.46,3.74,1.82,19.5,107,3.18,2.58,.24,3.58,2.9,.75,2.81,562 +2,12.52,2.43,2.17,21,88,2.55,2.27,.26,1.22,2,.9,2.78,325 +2,11.76,2.68,2.92,20,103,1.75,2.03,.6,1.05,3.8,1.23,2.5,607 +2,11.41,.74,2.5,21,88,2.48,2.01,.42,1.44,3.08,1.1,2.31,434 +2,12.08,1.39,2.5,22.5,84,2.56,2.29,.43,1.04,2.9,.93,3.19,385 +2,11.03,1.51,2.2,21.5,85,2.46,2.17,.52,2.01,1.9,1.71,2.87,407 +2,11.82,1.47,1.99,20.8,86,1.98,1.6,.3,1.53,1.95,.95,3.33,495 +2,12.42,1.61,2.19,22.5,108,2,2.09,.34,1.61,2.06,1.06,2.96,345 +2,12.77,3.43,1.98,16,80,1.63,1.25,.43,.83,3.4,.7,2.12,372 +2,12,3.43,2,19,87,2,1.64,.37,1.87,1.28,.93,3.05,564 +2,11.45,2.4,2.42,20,96,2.9,2.79,.32,1.83,3.25,.8,3.39,625 +2,11.56,2.05,3.23,28.5,119,3.18,5.08,.47,1.87,6,.93,3.69,465 +2,12.42,4.43,2.73,26.5,102,2.2,2.13,.43,1.71,2.08,.92,3.12,365 +2,13.05,5.8,2.13,21.5,86,2.62,2.65,.3,2.01,2.6,.73,3.1,380 +2,11.87,4.31,2.39,21,82,2.86,3.03,.21,2.91,2.8,.75,3.64,380 +2,12.07,2.16,2.17,21,85,2.6,2.65,.37,1.35,2.76,.86,3.28,378 +2,12.43,1.53,2.29,21.5,86,2.74,3.15,.39,1.77,3.94,.69,2.84,352 +2,11.79,2.13,2.78,28.5,92,2.13,2.24,.58,1.76,3,.97,2.44,466 +2,12.37,1.63,2.3,24.5,88,2.22,2.45,.4,1.9,2.12,.89,2.78,342 +2,12.04,4.3,2.38,22,80,2.1,1.75,.42,1.35,2.6,.79,2.57,580 +3,12.86,1.35,2.32,18,122,1.51,1.25,.21,.94,4.1,.76,1.29,630 +3,12.88,2.99,2.4,20,104,1.3,1.22,.24,.83,5.4,.74,1.42,530 +3,12.81,2.31,2.4,24,98,1.15,1.09,.27,.83,5.7,.66,1.36,560 +3,12.7,3.55,2.36,21.5,106,1.7,1.2,.17,.84,5,.78,1.29,600 +3,12.51,1.24,2.25,17.5,85,2,.58,.6,1.25,5.45,.75,1.51,650 +3,12.6,2.46,2.2,18.5,94,1.62,.66,.63,.94,7.1,.73,1.58,695 +3,12.25,4.72,2.54,21,89,1.38,.47,.53,.8,3.85,.75,1.27,720 +3,12.53,5.51,2.64,25,96,1.79,.6,.63,1.1,5,.82,1.69,515 +3,13.49,3.59,2.19,19.5,88,1.62,.48,.58,.88,5.7,.81,1.82,580 +3,12.84,2.96,2.61,24,101,2.32,.6,.53,.81,4.92,.89,2.15,590 +3,12.93,2.81,2.7,21,96,1.54,.5,.53,.75,4.6,.77,2.31,600 +3,13.36,2.56,2.35,20,89,1.4,.5,.37,.64,5.6,.7,2.47,780 +3,13.52,3.17,2.72,23.5,97,1.55,.52,.5,.55,4.35,.89,2.06,520 +3,13.62,4.95,2.35,20,92,2,.8,.47,1.02,4.4,.91,2.05,550 +3,12.25,3.88,2.2,18.5,112,1.38,.78,.29,1.14,8.21,.65,2,855 +3,13.16,3.57,2.15,21,102,1.5,.55,.43,1.3,4,.6,1.68,830 +3,13.88,5.04,2.23,20,80,.98,.34,.4,.68,4.9,.58,1.33,415 +3,12.87,4.61,2.48,21.5,86,1.7,.65,.47,.86,7.65,.54,1.86,625 +3,13.32,3.24,2.38,21.5,92,1.93,.76,.45,1.25,8.42,.55,1.62,650 +3,13.08,3.9,2.36,21.5,113,1.41,1.39,.34,1.14,9.40,.57,1.33,550 +3,13.5,3.12,2.62,24,123,1.4,1.57,.22,1.25,8.60,.59,1.3,500 +3,12.79,2.67,2.48,22,112,1.48,1.36,.24,1.26,10.8,.48,1.47,480 +3,13.11,1.9,2.75,25.5,116,2.2,1.28,.26,1.56,7.1,.61,1.33,425 +3,13.23,3.3,2.28,18.5,98,1.8,.83,.61,1.87,10.52,.56,1.51,675 +3,12.58,1.29,2.1,20,103,1.48,.58,.53,1.4,7.6,.58,1.55,640 +3,13.17,5.19,2.32,22,93,1.74,.63,.61,1.55,7.9,.6,1.48,725 +3,13.84,4.12,2.38,19.5,89,1.8,.83,.48,1.56,9.01,.57,1.64,480 +3,12.45,3.03,2.64,27,97,1.9,.58,.63,1.14,7.5,.67,1.73,880 +3,14.34,1.68,2.7,25,98,2.8,1.31,.53,2.7,13,.57,1.96,660 +3,13.48,1.67,2.64,22.5,89,2.6,1.1,.52,2.29,11.75,.57,1.78,620 +3,12.36,3.83,2.38,21,88,2.3,.92,.5,1.04,7.65,.56,1.58,520 +3,13.69,3.26,2.54,20,107,1.83,.56,.5,.8,5.88,.96,1.82,680 +3,12.85,3.27,2.58,22,106,1.65,.6,.6,.96,5.58,.87,2.11,570 +3,12.96,3.45,2.35,18.5,106,1.39,.7,.4,.94,5.28,.68,1.75,675 +3,13.78,2.76,2.3,22,90,1.35,.68,.41,1.03,9.58,.7,1.68,615 +3,13.73,4.36,2.26,22.5,88,1.28,.47,.52,1.15,6.62,.78,1.75,520 +3,13.45,3.7,2.6,23,111,1.7,.92,.43,1.46,10.68,.85,1.56,695 +3,12.82,3.37,2.3,19.5,88,1.48,.66,.4,.97,10.26,.72,1.75,685 +3,13.58,2.58,2.69,24.5,105,1.55,.84,.39,1.54,8.66,.74,1.8,750 +3,13.4,4.6,2.86,25,112,1.98,.96,.27,1.11,8.5,.67,1.92,630 +3,12.2,3.03,2.32,19,96,1.25,.49,.4,.73,5.5,.66,1.83,510 +3,12.77,2.39,2.28,19.5,86,1.39,.51,.48,.64,9.899999,.57,1.63,470 +3,14.16,2.51,2.48,20,91,1.68,.7,.44,1.24,9.7,.62,1.71,660 +3,13.71,5.65,2.45,20.5,95,1.68,.61,.52,1.06,7.7,.64,1.74,740 +3,13.4,3.91,2.48,23,102,1.8,.75,.43,1.41,7.3,.7,1.56,750 +3,13.27,4.28,2.26,20,120,1.59,.69,.43,1.35,10.2,.59,1.56,835 +3,13.17,2.59,2.37,20,120,1.65,.68,.53,1.46,9.3,.6,1.62,840 +3,14.13,4.1,2.74,24.5,96,2.05,.76,.56,1.35,9.2,.61,1.6,560 diff --git a/Chapter7/wine_test.py b/Chapter7/wine_test.py new file mode 100644 index 0000000..c0a7eec --- /dev/null +++ b/Chapter7/wine_test.py @@ -0,0 +1,64 @@ +# wine_test.py +# From Classic Computer Science Problems in Python Chapter 7 +# Copyright 2018 David Kopec +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +import csv +from typing import List +from util import normalize_by_feature_scaling +from network import Network +from random import shuffle + +if __name__ == "__main__": + wine_parameters: List[List[float]] = [] + wine_classifications: List[List[float]] = [] + wine_species: List[int] = [] + 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 + for wine in wines: + parameters: List[float] = [float(n) for n in wine[1:14]] + wine_parameters.append(parameters) + species: int = int(wine[0]) + if species == 1: + wine_classifications.append([1.0, 0.0, 0.0]) + elif species == 2: + wine_classifications.append([0.0, 1.0, 0.0]) + else: + wine_classifications.append([0.0, 0.0, 1.0]) + wine_species.append(species) + normalize_by_feature_scaling(wine_parameters) + + wine_network: Network = Network([13, 7, 3], 0.9) + + def wine_interpret_output(output: List[float]) -> int: + if max(output) == output[0]: + return 1 + elif max(output) == output[1]: + return 2 + else: + return 3 + + # train over the first 150 wines 10 times + wine_trainers: List[List[float]] = wine_parameters[0:150] + wine_trainers_corrects: List[List[float]] = wine_classifications[0:150] + for _ in range(10): + wine_network.train(wine_trainers, wine_trainers_corrects) + + # 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) + print(f"{wine_results[0]} correct of {wine_results[1]} = {wine_results[2] * 100}%") + + diff --git a/Chapter8/board.py b/Chapter8/board.py new file mode 100644 index 0000000..5008a58 --- /dev/null +++ b/Chapter8/board.py @@ -0,0 +1,56 @@ +# board.py +# From Classic Computer Science Problems in Python Chapter 8 +# Copyright 2018 David Kopec +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +from __future__ import annotations +from typing import NewType, List +from abc import ABC, abstractmethod + +Move = NewType('Move', int) + + +class Piece: + @property + def opposite(self) -> Piece: + raise NotImplementedError("Should be implemented by subclasses.") + + +class Board(ABC): + @property + @abstractmethod + def turn(self) -> Piece: + ... + + @abstractmethod + def move(self, location: Move) -> Board: + ... + + @property + @abstractmethod + def legal_moves(self) -> List[Move]: + ... + + @property + @abstractmethod + def is_win(self) -> bool: + ... + + @property + def is_draw(self) -> bool: + return (not self.is_win) and (len(self.legal_moves) == 0) + + @abstractmethod + def evaluate(self, player: Piece) -> float: + ... + diff --git a/Chapter8/connectfour.py b/Chapter8/connectfour.py new file mode 100644 index 0000000..98bfa22 --- /dev/null +++ b/Chapter8/connectfour.py @@ -0,0 +1,182 @@ +# connectfour.py +# From Classic Computer Science Problems in Python Chapter 8 +# Copyright 2018 David Kopec +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +from __future__ import annotations +from typing import List, Optional, Tuple +from enum import Enum +from board import Piece, Board, Move + + +class C4Piece(Piece, Enum): + B = "B" + R = "R" + E = " " # stand-in for empty + + @property + def opposite(self) -> C4Piece: + if self == C4Piece.B: + return C4Piece.R + elif self == C4Piece.R: + return C4Piece.B + 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]]] = [] + # generate the vertical segments + for c in range(num_columns): + for r in range(num_rows - segment_length + 1): + segment: List[Tuple[int, int]] = [] + for t in range(segment_length): + segment.append((c, r + t)) + segments.append(segment) + + # generate the horizontal segments + for c in range(num_columns - segment_length + 1): + for r in range(num_rows): + segment = [] + for t in range(segment_length): + segment.append((c + t, r)) + segments.append(segment) + + # generate the bottom left to top right diagonal segments + for c in range(num_columns - segment_length + 1): + for r in range(num_rows - segment_length + 1): + segment = [] + for t in range(segment_length): + segment.append((c + t, r + t)) + segments.append(segment) + + # generate the top left to bottom right diagonal segments + for c in range(num_columns - segment_length + 1): + for r in range(segment_length - 1, num_rows): + segment = [] + for t in range(segment_length): + segment.append((c + t, r - t)) + segments.append(segment) + return segments + + +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) + + class Column: + def __init__(self) -> None: + self._container: List[C4Piece] = [] + + @property + def full(self) -> bool: + return len(self._container) == C4Board.NUM_ROWS + + def push(self, item: C4Piece) -> None: + if self.full: + raise OverflowError("Trying to push piece to full column") + self._container.append(item) + + def __getitem__(self, index: int) -> C4Piece: + if index > len(self._container) - 1: + return C4Piece.E + return self._container[index] + + def __repr__(self) -> str: + return repr(self._container) + + def copy(self) -> C4Board.Column: + temp: C4Board.Column = C4Board.Column() + temp._container = self._container.copy() + return temp + + 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)] + else: + self.position = position + self._turn: C4Piece = turn + + @property + def turn(self) -> Piece: + return self._turn + + def move(self, location: Move) -> Board: + temp_position: List[C4Board.Column] = self.position.copy() + for c in range(C4Board.NUM_COLUMNS): + temp_position[c] = self.position[c].copy() + temp_position[location].push(self._turn) + return C4Board(temp_position, self._turn.opposite) + + @property + def legal_moves(self) -> List[Move]: + 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]: + black_count: int = 0 + red_count: int = 0 + for column, row in segment: + if self.position[column][row] == C4Piece.B: + black_count += 1 + elif self.position[column][row] == C4Piece.R: + red_count += 1 + return black_count, red_count + + @property + def is_win(self) -> bool: + for segment in C4Board.SEGMENTS: + black_count, red_count = self._count_segment(segment) + if black_count == 4 or red_count == 4: + return True + return False + + 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 + count: int = max(red_count, black_count) + score: float = 0 + if count == 2: + score = 1 + elif count == 3: + score = 100 + elif count == 4: + score = 1000000 + color: C4Piece = C4Piece.B + if red_count > black_count: + color = C4Piece.R + if color != player: + return -score + return score + + def evaluate(self, player: Piece) -> float: + total: float = 0 + for segment in C4Board.SEGMENTS: + total += self._evaluate_segment(segment, player) + return total + + def __repr__(self) -> str: + display: str = "" + for r in reversed(range(C4Board.NUM_ROWS)): + display += "|" + for c in range(C4Board.NUM_COLUMNS): + display += f"{self.position[c][r]}" + "|" + display += "\n" + return display + diff --git a/Chapter8/connectfour_ai.py b/Chapter8/connectfour_ai.py new file mode 100644 index 0000000..a9df7ab --- /dev/null +++ b/Chapter8/connectfour_ai.py @@ -0,0 +1,53 @@ +# tictactoe_ai.py +# From Classic Computer Science Problems in Python Chapter 8 +# Copyright 2018 David Kopec +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +from minimax import find_best_move +from connectfour import C4Board +from board import Move, Board + +board: Board = C4Board() + + +def get_player_move() -> Move: + player_move: Move = Move(-1) + while player_move not in board.legal_moves: + play: int = int(input("Enter a legal column (0-6):")) + player_move = Move(play) + return player_move + + +if __name__ == "__main__": + # main game loop + while True: + human_move: Move = get_player_move() + board = board.move(human_move) + if board.is_win: + print("Human wins!") + break + elif board.is_draw: + print("Draw!") + break + computer_move: Move = find_best_move(board, 5) + print(f"Computer move is {computer_move}") + board = board.move(computer_move) + print(board) + if board.is_win: + print("Computer wins!") + break + elif board.is_draw: + print("Draw!") + break + + diff --git a/Chapter8/minimax.py b/Chapter8/minimax.py new file mode 100644 index 0000000..0d358ae --- /dev/null +++ b/Chapter8/minimax.py @@ -0,0 +1,73 @@ +# minimax.py +# From Classic Computer Science Problems in Python Chapter 8 +# Copyright 2018 David Kopec +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +from __future__ import annotations +from board import Piece, Board, Move + + +# Find the best possible outcome for original player +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 + 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 + return best_eval + 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 + 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: + # 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: + for move in board.legal_moves: + 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) + beta = min(result, beta) + if beta <= alpha: + break + return beta + + +# 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) + for move in board.legal_moves: + result: float = alphabeta(board.move(move), False, board.turn, max_depth) + if result > best_eval: + best_eval = result + best_move = move + return best_move \ No newline at end of file diff --git a/Chapter8/tictactoe.py b/Chapter8/tictactoe.py new file mode 100644 index 0000000..74192ff --- /dev/null +++ b/Chapter8/tictactoe.py @@ -0,0 +1,83 @@ +# tictactoe.py +# From Classic Computer Science Problems in Python Chapter 8 +# Copyright 2018 David Kopec +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +from __future__ import annotations +from typing import List +from enum import Enum +from board import Piece, Board, Move + + +class TTTPiece(Piece, Enum): + X = "X" + O = "O" + E = " " # stand-in for empty + + @property + def opposite(self) -> TTTPiece: + if self == TTTPiece.X: + return TTTPiece.O + elif self == TTTPiece.O: + return TTTPiece.X + 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: + self.position: List[TTTPiece] = position + self._turn: TTTPiece = turn + + @property + def turn(self) -> Piece: + return self._turn + + def move(self, location: Move) -> Board: + temp_position: List[TTTPiece] = self.position.copy() + temp_position[location] = self._turn + return TTTBoard(temp_position, self._turn.opposite) + + @property + def legal_moves(self) -> List[Move]: + 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 + + def evaluate(self, player: Piece) -> float: + if self.is_win and self.turn == player: + return -1 + elif self.is_win and self.turn != player: + return 1 + else: + return 0 + + def __repr__(self) -> str: + return f"""{self.position[0]}|{self.position[1]}|{self.position[2]} +----- +{self.position[3]}|{self.position[4]}|{self.position[5]} +----- +{self.position[6]}|{self.position[7]}|{self.position[8]}""" diff --git a/Chapter8/tictactoe_ai.py b/Chapter8/tictactoe_ai.py new file mode 100644 index 0000000..5132ace --- /dev/null +++ b/Chapter8/tictactoe_ai.py @@ -0,0 +1,53 @@ +# tictactoe_ai.py +# From Classic Computer Science Problems in Python Chapter 8 +# Copyright 2018 David Kopec +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +from minimax import find_best_move +from tictactoe import TTTBoard +from board import Move, Board + +board: Board = TTTBoard() + + +def get_player_move() -> Move: + player_move: Move = Move(-1) + while player_move not in board.legal_moves: + play: int = int(input("Enter a legal square (0-8):")) + player_move = Move(play) + return player_move + + +if __name__ == "__main__": + # main game loop + while True: + human_move: Move = get_player_move() + board = board.move(human_move) + if board.is_win: + print("Human wins!") + break + elif board.is_draw: + print("Draw!") + break + computer_move: Move = find_best_move(board) + print(f"Computer move is {computer_move}") + board = board.move(computer_move) + print(board) + if board.is_win: + print("Computer wins!") + break + elif board.is_draw: + print("Draw!") + break + + diff --git a/Chapter8/tictactoe_tests.py b/Chapter8/tictactoe_tests.py new file mode 100644 index 0000000..9864e1a --- /dev/null +++ b/Chapter8/tictactoe_tests.py @@ -0,0 +1,55 @@ +# tictactoe_tests.py +# From Classic Computer Science Problems in Python Chapter 8 +# Copyright 2018 David Kopec +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +import unittest +from typing import List +from minimax import find_best_move +from tictactoe import TTTPiece, TTTBoard +from board import Move + + +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] + 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] + 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] + test_board3: TTTBoard = TTTBoard(to_win_hard_position, TTTPiece.X) + answer3: Move = find_best_move(test_board3) + self.assertEqual(answer3, 1) + + +if __name__ == '__main__': + unittest.main() + + diff --git a/Chapter9/knapsack.py b/Chapter9/knapsack.py new file mode 100644 index 0000000..9a09f27 --- /dev/null +++ b/Chapter9/knapsack.py @@ -0,0 +1,63 @@ +# knapsack.py +# From Classic Computer Science Problems in Python Chapter 9 +# Copyright 2018 David Kopec +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +from typing import NamedTuple, List + + +class Item(NamedTuple): + name: str + weight: int + value: float + + +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)] + 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 + 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] = previous_items_value + # figure out solution from table + solution: List[Item] = [] + capacity = max_capacity + 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]) + # if the item was used, remove its weight + capacity -= items[i - 1].weight + return solution + + +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)] + print(knapsack(items, 75)) + + diff --git a/Chapter9/phone_number_mnemonics.py b/Chapter9/phone_number_mnemonics.py new file mode 100644 index 0000000..19f39f4 --- /dev/null +++ b/Chapter9/phone_number_mnemonics.py @@ -0,0 +1,42 @@ +# phone_number_mnemonics.py +# From Classic Computer Science Problems in Python Chapter 9 +# Copyright 2018 David Kopec +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +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",)} + + +def possible_mnemonics(phone_number: str) -> Iterable[Tuple[str, ...]]: + letter_tuples: List[Tuple[str, ...]] = [] + for digit in phone_number: + letter_tuples.append(phone_mapping.get(digit, (digit,))) + return product(*letter_tuples) + + +if __name__ == "__main__": + phone_number: str = input("Enter a phone number:") + print("Here are the potential mnemonics:") + for mnemonic in possible_mnemonics(phone_number): + print("".join(mnemonic)) diff --git a/Chapter9/tsp.py b/Chapter9/tsp.py new file mode 100644 index 0000000..e7bab73 --- /dev/null +++ b/Chapter9/tsp.py @@ -0,0 +1,65 @@ +# tsp.py +# From Classic Computer Science Problems in Python Chapter 9 +# Copyright 2018 David Kopec +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +from typing import Dict, List, Iterable, Tuple +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} +} + +vt_cities: Iterable[str] = vt_distances.keys() +city_permutations: Iterable[Tuple[str, ...]] = permutations(vt_cities) +tsp_paths: List[Tuple[str, ...]] = [c + (c[0],) for c in city_permutations] + +if __name__ == "__main__": + best_path: Tuple[str, ...] + min_distance: int = 99999999999 # arbitrarily high number + for path in tsp_paths: + distance: int = 0 + last: str = path[0] + for next in path[1:]: + distance += vt_distances[last][next] + last = next + if distance < min_distance: + min_distance = distance + best_path = path + print(f"The shortest path is {best_path} in {min_distance} miles.") + + 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