diff --git a/.idea/ClassicComputerScienceProblemsInPython.iml b/.idea/ClassicComputerScienceProblemsInPython.iml
new file mode 100644
index 0000000..6711606
--- /dev/null
+++ b/.idea/ClassicComputerScienceProblemsInPython.iml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..0325094
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+ ApexVCS
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..3901a63
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Chapter1/Chapter1.ipynb b/Chapter1/Chapter1.ipynb
new file mode 100644
index 0000000..c29d2c1
--- /dev/null
+++ b/Chapter1/Chapter1.ipynb
@@ -0,0 +1,570 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Fibonacci"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "RecursionError('maximum recursion depth exceeded',)\n"
+ ]
+ }
+ ],
+ "source": [
+ "def fib1(n:int) -> int:\n",
+ " return fib1(n-1) + fib1(n-2)\n",
+ "try:\n",
+ " fib1(3)\n",
+ "except RecursionError as e:\n",
+ " print(repr(e))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "21"
+ ]
+ },
+ "execution_count": 2,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def fib2(n:int) -> int:\n",
+ " if n < 2:\n",
+ " return n\n",
+ " else:\n",
+ " return fib2(n-2) + fib2(n-1)\n",
+ " \n",
+ "fib2(8)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "21 12586269025\n"
+ ]
+ }
+ ],
+ "source": [
+ "from typing import Dict\n",
+ "memo:Dict[int, int] = {0:0, 1:1} #base cases\n",
+ "def fib3(n:int) -> int:\n",
+ " if n not in memo:\n",
+ " memo[n] = fib3(n-1) + fib3(n-2)\n",
+ " return memo[n]\n",
+ "print(fib3(8), fib3(50))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "21 12586269025\n"
+ ]
+ }
+ ],
+ "source": [
+ "from functools import lru_cache\n",
+ "@lru_cache(maxsize=None)\n",
+ "def fib4(n: int) -> int:\n",
+ " if n < 2:\n",
+ " return n\n",
+ " else:\n",
+ " return fib4(n-2) + fib4(n-1)\n",
+ " \n",
+ "print(fib4(8), fib4(50))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "21 12586269025\n"
+ ]
+ }
+ ],
+ "source": [
+ "def fib5(n:int) -> int:\n",
+ " if n == 0:\n",
+ " return n\n",
+ " else:\n",
+ " last:int = 0\n",
+ " _next:int = 1\n",
+ " for _ in range(1,n):\n",
+ " last, _next = _next, last + _next\n",
+ " return _next\n",
+ " \n",
+ "print(fib5(8), fib5(50))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[0,\n",
+ " 1,\n",
+ " 1,\n",
+ " 2,\n",
+ " 3,\n",
+ " 5,\n",
+ " 8,\n",
+ " 13,\n",
+ " 21,\n",
+ " 34,\n",
+ " 55,\n",
+ " 89,\n",
+ " 144,\n",
+ " 233,\n",
+ " 377,\n",
+ " 610,\n",
+ " 987,\n",
+ " 1597,\n",
+ " 2584,\n",
+ " 4181,\n",
+ " 6765,\n",
+ " 10946,\n",
+ " 17711,\n",
+ " 28657,\n",
+ " 46368,\n",
+ " 75025,\n",
+ " 121393,\n",
+ " 196418,\n",
+ " 317811,\n",
+ " 514229,\n",
+ " 832040,\n",
+ " 1346269,\n",
+ " 2178309,\n",
+ " 3524578,\n",
+ " 5702887,\n",
+ " 9227465,\n",
+ " 14930352,\n",
+ " 24157817,\n",
+ " 39088169,\n",
+ " 63245986,\n",
+ " 102334155,\n",
+ " 165580141,\n",
+ " 267914296,\n",
+ " 433494437,\n",
+ " 701408733,\n",
+ " 1134903170,\n",
+ " 1836311903,\n",
+ " 2971215073,\n",
+ " 4807526976,\n",
+ " 7778742049,\n",
+ " 12586269025]"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "from typing import Generator\n",
+ "def fib6(n:int) -> int:\n",
+ " yield 0\n",
+ " if n> 0: \n",
+ " yield 1\n",
+ " last:int = 0\n",
+ " _next:int = 1\n",
+ " for _ in range(1,n):\n",
+ " last, _next = _next, last + _next\n",
+ " yield _next\n",
+ " \n",
+ "list([i for i in fib6(50)])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Trivial Compression"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 36,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class CompressedGene:\n",
+ " def __init__(self, gene: str) -> None:\n",
+ " self._compress(gene)\n",
+ "\n",
+ " def _compress(self, gene: str) -> None:\n",
+ " self.bit_string: int = 1 # start with sentinel\n",
+ " for nucleotide in gene.upper():\n",
+ " self.bit_string <<= 2 # shift left two bits\n",
+ " if nucleotide == \"A\": # change last two bits to 00\n",
+ " self.bit_string |= 0b00\n",
+ " elif nucleotide == \"C\": # change last two bits to 01\n",
+ " self.bit_string |= 0b01\n",
+ " elif nucleotide == \"G\": # change last two bits to 10\n",
+ " self.bit_string |= 0b10\n",
+ " elif nucleotide == \"T\": # change last two bits to 11\n",
+ " self.bit_string |= 0b11\n",
+ " else:\n",
+ " raise ValueError(\"Invalid Nucleotide:{}\".format(nucleotide))\n",
+ "\n",
+ " def decompress(self) -> str:\n",
+ " gene: str = \"\"\n",
+ " for i in range(0, self.bit_string.bit_length() - 1, 2): # - 1 to exclude sentinel\n",
+ " bits: int = self.bit_string >> i & 0b11 # get just 2 relevant bits\n",
+ " if bits == 0b00: # A\n",
+ " gene += \"A\"\n",
+ " elif bits == 0b01: # C\n",
+ " gene += \"C\"\n",
+ " elif bits == 0b10: # G\n",
+ " gene += \"G\"\n",
+ " elif bits == 0b11: # T\n",
+ " gene += \"T\"\n",
+ " else:\n",
+ " raise ValueError(\"Invalid bits:{}\".format(bits))\n",
+ " return gene[::-1] # [::-1] reverses string by slicing backwards\n",
+ "\n",
+ " def __str__(self) -> str: # string representation for pretty printing\n",
+ " return self.decompress()\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 43,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "original is 4749 bytes\n",
+ "compressed is 1280 bytes\n",
+ "TAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAGTAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAG\n",
+ "original and decompressed are the same: True\n"
+ ]
+ }
+ ],
+ "source": [
+ "from sys import getsizeof\n",
+ "original:str = \"TAGGGATTAACCATTAACCGTTATATATATATAGCCATGGACAGTAG\"*100\n",
+ "print(\"original is {} bytes\".format(getsizeof(original)))\n",
+ "compressed:CompressedGene = CompressedGene(original)\n",
+ "print(\"compressed is {} bytes\".format(getsizeof(compressed.bit_string)))\n",
+ "print(compressed)\n",
+ "print(\"original and decompressed are the same: {}\".format(original == compressed.decompress()))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Unbreakable Encryption"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 44,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from secrets import token_bytes\n",
+ "from typing import Tuple"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 53,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def random_key(length:int) -> int:\n",
+ " # generate length random bytes\n",
+ " tb:bytes = token_bytes(length)\n",
+ " # convert to a bit string in the form of an int\n",
+ " return int.from_bytes(tb, \"big\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 57,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "(2977035838, 3607335770)\n"
+ ]
+ }
+ ],
+ "source": [
+ "def encrypt(original:str) -> Tuple[int, int]:\n",
+ " original_bytes: bytes = original.encode()\n",
+ " dummy:int = random_key(len(original_bytes))\n",
+ " original_key:int = int.from_bytes(original_bytes, \"big\")\n",
+ " encrypted: int = original_key^dummy # XOR\n",
+ " return dummy, encrypted\n",
+ "\n",
+ "print(encrypt(\"fred\"))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 58,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def decrypt(key1:int, key2:int) -> str:\n",
+ " decrypted:int = key1^key2 #XOR\n",
+ " temp:bytes = decrypted.to_bytes((decrypted.bit_length()+7)//8, \"big\")\n",
+ " return temp.decode()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 59,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "One Time Pad!\n"
+ ]
+ }
+ ],
+ "source": [
+ "key1, key2 = encrypt(\"One Time Pad!\")\n",
+ "result:str = decrypt(key1, key2)\n",
+ "print(result)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Calcluating Pi"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 60,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "3.1415916535897743\n"
+ ]
+ }
+ ],
+ "source": [
+ "def calculate_pi(n_terms:int) -> float:\n",
+ " numerator:float = 4.0\n",
+ " denominator:float = 1.0\n",
+ " operation:float=1.0\n",
+ " pi:float = 0.0\n",
+ " for _ in range(n_terms):\n",
+ " pi += operation*(numerator/denominator)\n",
+ " denominator += 2.0\n",
+ " operation *= -1.0\n",
+ " return pi\n",
+ "\n",
+ "print(calculate_pi(1000000))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Towers of Hanoi"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 61,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from typing import TypeVar, Generic, List\n",
+ "T = TypeVar(\"T\")\n",
+ "\n",
+ "class Stack(Generic[T]):\n",
+ " \n",
+ " def __init__(self) -> None:\n",
+ " self._container: List[T] = []\n",
+ " \n",
+ " def push(self, item:T) -> None:\n",
+ " self._container.append(item)\n",
+ " \n",
+ " def pop(self) -> T:\n",
+ " return self._container.pop()\n",
+ " \n",
+ " def __repr__(self) -> str:\n",
+ " return repr(self._container)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 62,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "num_discs:int = 3\n",
+ "tower_a: Stack[int] = Stack()\n",
+ "tower_b: Stack[int] = Stack()\n",
+ "tower_c: Stack[int] = Stack()\n",
+ "for i in range(1, num_discs+1):\n",
+ " tower_a.push(i)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 64,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def hanoi(begin:Stack[int], end:Stack[int], temp:Stack[int], n:int) -> None:\n",
+ " if n == 1:\n",
+ " end.push(begin.pop())\n",
+ " else:\n",
+ " hanoi(begin, temp, end, n-1)\n",
+ " hanoi(begin, end, temp, 1)\n",
+ " hanoi(temp, end, begin, n-1)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 65,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[] [] [1, 2, 3]\n"
+ ]
+ }
+ ],
+ "source": [
+ "hanoi(tower_a, tower_c, tower_b, num_discs)\n",
+ "print(tower_a, tower_b, tower_c)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 68,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def do_hanoi(num_discs:int) -> list:\n",
+ " tower_a: Stack[int] = Stack()\n",
+ " tower_b: Stack[int] = Stack()\n",
+ " tower_c: Stack[int] = Stack()\n",
+ " for i in range(1, num_discs+1):\n",
+ " tower_a.push(i)\n",
+ " hanoi(tower_a, tower_c, tower_b, num_discs)\n",
+ " print(tower_a, tower_b, tower_c)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 69,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[] [] [1, 2, 3, 4]\n"
+ ]
+ }
+ ],
+ "source": [
+ "do_hanoi(4)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 70,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[] [] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n"
+ ]
+ }
+ ],
+ "source": [
+ "do_hanoi(19)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.2"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/Chapter2/Chapter 2.ipynb b/Chapter2/Chapter 2.ipynb
new file mode 100644
index 0000000..70cbfc7
--- /dev/null
+++ b/Chapter2/Chapter 2.ipynb
@@ -0,0 +1,994 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## DNA Search"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from enum import IntEnum\n",
+ "from typing import Tuple, List"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "Nucleotide:IntEnum = IntEnum(\"Nucleotide\", (\"A\", \"C\", \"G\", \"T\"))\n",
+ "Codon = Tuple[Nucleotide, Nucleotide, Nucleotide]\n",
+ "Gene = List[Codon]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "gene_str: str = \"ACGTGGCTCTCTAACGTACGTACGTACGGGGTTTATATATACCCTAGGACTCCCTTT\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def string_to_gene(s:str) -> Gene:\n",
+ " gene:Gene = []\n",
+ " for i in range(0, len(s), 3):\n",
+ " if (i+2) >= len(s): #Checking for the end\n",
+ " return gene\n",
+ " codon:Codon = (Nucleotide[s[i]], Nucleotide[s[i+1]], Nucleotide[s[i+2]])\n",
+ " gene.append(codon)\n",
+ " return gene"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[(, , ), (, , ), (, , ), (, , ), (, , ), (, , ), (, , ), (, , ), (, , ), (, , ), (, , ), (, , ), (, , ), (, , ), (, , ), (, , ), (, , ), (, , ), (, , )]\n"
+ ]
+ }
+ ],
+ "source": [
+ "my_gene: Gene = string_to_gene(gene_str)\n",
+ "print(my_gene)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Linear Search"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "True\n",
+ "False\n"
+ ]
+ }
+ ],
+ "source": [
+ "def linear_contains(gene:Gene, key_codon:Codon) -> bool:\n",
+ " for codon in gene:\n",
+ " if codon == key_codon:\n",
+ " return True\n",
+ " return False\n",
+ "\n",
+ "acg:Codon = (Nucleotide.A, Nucleotide.C, Nucleotide.G)\n",
+ "gat:Codon = (Nucleotide.G, Nucleotide.A, Nucleotide.T)\n",
+ "print(linear_contains(my_gene, acg)) # Should be True\n",
+ "print(linear_contains(my_gene, gat)) # Expect False"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "True\n",
+ "False\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Alternative using __contains__() method. we would never actually write the above.\n",
+ "print(acg in my_gene)\n",
+ "print(gat in my_gene)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Binary Search"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def binary_contains(gene:Gene, key_codon:Codon) -> bool:\n",
+ " low:int = 0\n",
+ " high:int = len(gene) - 1\n",
+ " while low <= high: #i.e. while there still is a search space\n",
+ " mid:int = (low+high)//2\n",
+ " if gene[mid] < key_codon:\n",
+ " low = mid+1\n",
+ " elif gene[mid] > key_codon:\n",
+ " high = mid-1\n",
+ " else:\n",
+ " return True\n",
+ " return False"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "True\n",
+ "False\n"
+ ]
+ }
+ ],
+ "source": [
+ "my_sorted_gene = sorted(my_gene)\n",
+ "print(binary_contains(my_sorted_gene, acg)) # Should be True\n",
+ "print(binary_contains(my_sorted_gene, gat)) # Should be False"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Generic Example"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from __future__ import annotations\n",
+ "from typing import TypeVar, Iterable, Sequence, Generic, List, Callable, Set, Deque, Dict, Any, Optional\n",
+ "from typing_extensions import Protocol\n",
+ "from heapq import heappush, heappop"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "T = TypeVar(\"T\")\n",
+ "\n",
+ "def linear_contains(iterable:Iterable[T], key:T) ->bool:\n",
+ " for item in iterable:\n",
+ " if item == key:\n",
+ " return True\n",
+ " return False\n",
+ "\n",
+ "C = TypeVar(\"C\", bound=\"Comparable\")\n",
+ "\n",
+ "class Comparable(Protocol):\n",
+ " \n",
+ " def __eq__(self, other:Any) -> bool:\n",
+ " ...\n",
+ " \n",
+ " def __lt__(self:C, other:C) -> bool:\n",
+ " ...\n",
+ " \n",
+ " def __gt__(self:C, other:C) -> bool:\n",
+ " return (not self < other) and (self != other)\n",
+ " \n",
+ " def __le__(self:C, other:C) -> bool:\n",
+ " return (self < other) or (self == other)\n",
+ " \n",
+ " def __ge__(self:C, other:C) -> bool:\n",
+ " return (other < self) or (other == self)\n",
+ " \n",
+ "def binary_contains(sequence:Sequence[C], key:C) -> bool:\n",
+ " low:int = 0\n",
+ " high:int = len(sequence) - 1\n",
+ " while low <= high:\n",
+ " mid:int = (low + high) //2\n",
+ " if sequence[mid] < key:\n",
+ " low = mid + 1\n",
+ " elif key < sequence[mid]:\n",
+ " high = mid - 1\n",
+ " else:\n",
+ " return True\n",
+ " return False"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "True\n",
+ "True\n",
+ "False\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(linear_contains([1, 5, 15, 15, 15, 15, 20], 5)) # Expect True\n",
+ "print(binary_contains([\"a\", \"d\", \"e\", \"f\", \"z\"], \"f\")) # Expect True\n",
+ "print(binary_contains([\"john\", \"mark\", \"ronald\", \"sarah\"], \"sheila\")) # Expect False"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Maze Solving"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from enum import Enum\n",
+ "from typing import List, NamedTuple, Callable, Optional\n",
+ "import random\n",
+ "from math import sqrt\n",
+ "from generic_search import dfs, bfs, node_to_path, astar, Node"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 37,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Cell(str, Enum):\n",
+ " EMPTY = \" \"\n",
+ " BLOCKED = \"X\"\n",
+ " START = \"S\"\n",
+ " GOAL = \"G\"\n",
+ " PATH = \"*\"\n",
+ " \n",
+ "class MazeLocation(NamedTuple):\n",
+ " row:int\n",
+ " column:int\n",
+ " \n",
+ "class Maze:\n",
+ " def __init__(self, rows:int=10, columns:int=10, sparseness:float=0.2,\n",
+ " start:MazeLocation=MazeLocation(0,0),\n",
+ " goal:MazeLocation=MazeLocation(9,9)) -> None:\n",
+ " self._rows:int = rows\n",
+ " self._columns:int = columns\n",
+ " self.start:MazeLocation = start\n",
+ " self.goal:MazeLocation = goal\n",
+ " self._grid:List[List[Cell]] = [[Cell.EMPTY for c in range(columns)] for r in range(rows)]\n",
+ " self._randomly_fill(rows, columns, sparseness)\n",
+ " self._grid[start.row][start.column] = Cell.START\n",
+ " self._grid[goal.row][goal.column] = Cell.GOAL\n",
+ " \n",
+ " def _randomly_fill(self, rows:int, columns:int, sparseness:float) -> None:\n",
+ " for row in range(rows):\n",
+ " for column in range(columns):\n",
+ " if random.uniform(0, 1.0) < sparseness:\n",
+ " self._grid[row][column] = Cell.BLOCKED\n",
+ " \n",
+ " def __str__(self) -> str:\n",
+ " output:str = \"\"\n",
+ " for row in self._grid:\n",
+ " output += \"\".join([c.value for c in row]) + \"\\n\"\n",
+ " return output\n",
+ " \n",
+ " def goal_test(self, ml:MazeLocation) -> bool:\n",
+ " return ml == self.goal\n",
+ " \n",
+ " def successors(self, ml: MazeLocation) -> List[MazeLocation]:\n",
+ " locations: List[MazeLocation] = []\n",
+ " if ml.row + 1 < self._rows and self._grid[ml.row + 1][ml.column] != Cell.BLOCKED:\n",
+ " locations.append(MazeLocation(ml.row + 1, ml.column))\n",
+ " if ml.row - 1 >= 0 and self._grid[ml.row - 1][ml.column] != Cell.BLOCKED:\n",
+ " locations.append(MazeLocation(ml.row - 1, ml.column))\n",
+ " if ml.column + 1 < self._columns and self._grid[ml.row][ml.column + 1] != Cell.BLOCKED:\n",
+ " locations.append(MazeLocation(ml.row, ml.column + 1))\n",
+ " if ml.column - 1 >= 0 and self._grid[ml.row][ml.column - 1] != Cell.BLOCKED:\n",
+ " locations.append(MazeLocation(ml.row, ml.column - 1))\n",
+ " return locations\n",
+ "\n",
+ " \n",
+ " def mark(self, path: List[MazeLocation]) -> None:\n",
+ " \n",
+ " for maze_location in path:\n",
+ " self._grid[maze_location.row][maze_location.column] = Cell.PATH\n",
+ " self._grid[self.start.row][self.start.column] = Cell.START\n",
+ " self._grid[self.goal.row][self.goal.column] = Cell.GOAL\n",
+ " \n",
+ " def clear(self, path:List[MazeLocation]) -> None:\n",
+ " for maze_location in path:\n",
+ " self._grid[maze_location.row][maze_location.column] = Cell.EMPTY\n",
+ " self._grid[self.start.row][self.start.column] = Cell.START\n",
+ " self._grid[self.goal.row][self.goal.column] = Cell.GOAL\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "S XX X \n",
+ "X X \n",
+ " X X \n",
+ "X X \n",
+ "X X \n",
+ " X \n",
+ " X \n",
+ " X\n",
+ " X X\n",
+ "XXXX G\n",
+ "\n"
+ ]
+ }
+ ],
+ "source": [
+ "maze:Maze = Maze()\n",
+ "print(maze)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### The DFS Algorithm"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Stack(Generic[T]):\n",
+ " \n",
+ " def __init__(self) -> None:\n",
+ " self._container: List[T] = []\n",
+ " \n",
+ " @property\n",
+ " def empty(self) -> bool:\n",
+ " return not self._container\n",
+ " \n",
+ " def push(self, item:T) -> None:\n",
+ " self._container.append(item)\n",
+ " \n",
+ " def pop(self) -> T:\n",
+ " return self._container.pop()\n",
+ " \n",
+ " def __repr__(self) -> str:\n",
+ " return repr(self._container)\n",
+ " \n",
+ "class Node(Generic[T]):\n",
+ " \n",
+ " def __init__(self, state:T, parent:Optional[Node], cost:float=0.0, heuristic:float=0.0) -> None:\n",
+ " self.state:T = state\n",
+ " self.parent:Optional[Node] = parent\n",
+ " self.cost:float = cost\n",
+ " self.heuristic:float = heuristic\n",
+ " \n",
+ " def __lt__(self, other:Node) -> bool:\n",
+ " return (self.cost + self.heuristic) < (other.cost + other.heuristic)\n",
+ " \n",
+ "def dfs(initial:T, goal_test:Callable[[T], bool], successors:Callable[[T], List[T]]) -> Optional[Node[T]]:\n",
+ " # Frontier is where we haven't gone yet.\n",
+ " frontier:Stack[Node[T]] = Stack()\n",
+ " frontier.push(Node(initial, None))\n",
+ " # Explored is where we've been\n",
+ " explored: Set[T] = {initial}\n",
+ " \n",
+ " while not frontier.empty:\n",
+ " current_node: Node[T] = frontier.pop()\n",
+ " current_state: T = current_node.state\n",
+ " # if we've found the goal, we're done\n",
+ " if goal_test(current_state):\n",
+ " return current_node\n",
+ " # check where we can go next but haven't yet explored\n",
+ " for child in successors(current_state):\n",
+ " if child in explored: # we can skip this\n",
+ " continue\n",
+ " explored.add(child)\n",
+ " frontier.push(Node(child, current_node))\n",
+ " \n",
+ " return None # We tried but never found goal\n",
+ "\n",
+ "def node_to_path(node:Node[T]) -> List[T]:\n",
+ " path: List[T] = [node.state]\n",
+ " # Work backwards from end to front\n",
+ " while node.parent is not None:\n",
+ " node = node.parent\n",
+ " path.append(node.state)\n",
+ " path.reverse()\n",
+ " return path"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Applying DFS to solve the maze"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 52,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "S X X\n",
+ " X X \n",
+ " XX \n",
+ " X X \n",
+ " XX X \n",
+ " X X \n",
+ " \n",
+ "X \n",
+ " \n",
+ " X X XG\n",
+ "\n",
+ "S*X X\n",
+ " *X X \n",
+ "** XX \n",
+ "* X X \n",
+ "***XX X \n",
+ " X*X \n",
+ " ** \n",
+ "X* \n",
+ " *********\n",
+ " X X XG\n",
+ "\n"
+ ]
+ }
+ ],
+ "source": [
+ "m:Maze = Maze()\n",
+ "print(m)\n",
+ "solution1:Optional[Node[MazeLocation]] = dfs(m.start, m.goal_test, m.successors)\n",
+ "if solution1 is None:\n",
+ " print(\"No solution found using DFS\")\n",
+ "else:\n",
+ " path1: List[MazeLocation] = node_to_path(solution1)\n",
+ " m.mark(path1)\n",
+ " print(m)\n",
+ " m.clear(path1)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Breadth-first Search"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 50,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Queue(Generic[T]):\n",
+ " \n",
+ " def __init__(self) -> None:\n",
+ " self._container: Deque[T] = Deque()\n",
+ " \n",
+ " @property\n",
+ " def empty(self) -> bool:\n",
+ " return not self._container\n",
+ " \n",
+ " def push(self, item:T) -> None:\n",
+ " self._container.append(item)\n",
+ " \n",
+ " def pop(self) -> T:\n",
+ " return self._container.popleft() #FIFO\n",
+ " \n",
+ " def __repr__(self) -> str:\n",
+ " return repr(self._container)\n",
+ " \n",
+ "def bfs(initial:T, goal_test:Callable[[T], bool], successors:Callable[[T], List[T]]) -> Optional[Node[T]]:\n",
+ " # Frontier is where we haven't gone yet.\n",
+ " frontier:Queue[Node[T]] = Queue()\n",
+ " frontier.push(Node(initial, None))\n",
+ " # Explored is where we've been\n",
+ " explored: Set[T] = {initial}\n",
+ " \n",
+ " while not frontier.empty:\n",
+ " current_node: Node[T] = frontier.pop()\n",
+ " current_state: T = current_node.state\n",
+ " # if we've found the goal, we're done\n",
+ " if goal_test(current_state):\n",
+ " return current_node\n",
+ " # check where we can go next but haven't yet explored\n",
+ " for child in successors(current_state):\n",
+ " if child in explored: # we can skip this\n",
+ " continue\n",
+ " explored.add(child)\n",
+ " frontier.push(Node(child, current_node))\n",
+ " \n",
+ " return None # We tried but never found goal\n",
+ "\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 53,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "S X X\n",
+ "* X X \n",
+ "* XX \n",
+ "* X X \n",
+ "* XX X \n",
+ "*X X \n",
+ "** \n",
+ "X* \n",
+ " *********\n",
+ " X X XG\n",
+ "\n"
+ ]
+ }
+ ],
+ "source": [
+ "solution2:Optional[Node[MazeLocation]] = bfs(m.start, m.goal_test, m.successors)\n",
+ "if solution2 is None:\n",
+ " print(\"No solution found using BFS\")\n",
+ "else:\n",
+ " path1: List[MazeLocation] = node_to_path(solution2)\n",
+ " m.mark(path1)\n",
+ " print(m)\n",
+ " m.clear(path1)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### A* Search"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 55,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class PriorityQueue(Generic[T]):\n",
+ " \n",
+ " def __init__(self) -> None:\n",
+ " self._container:List [T] = []\n",
+ " \n",
+ " @property\n",
+ " def empty(self) -> bool:\n",
+ " return not self._container\n",
+ " \n",
+ " def push(self, item:T) -> None:\n",
+ " heappush(self._container, item) # In by priority\n",
+ " \n",
+ " def pop(self) -> T:\n",
+ " return heappop(self._container) # out by priority\n",
+ " \n",
+ " def __repr__(self) -> str:\n",
+ " return repr(self._container)\n",
+ " \n",
+ "\n",
+ "def euclidean_distance(goal:MazeLocation) -> Callable[[MazeLocation], float]:\n",
+ " def distance(ml:MazeLocation) -> float:\n",
+ " xdist:int = ml.column - goal.column\n",
+ " ydist:int = ml.row - goal.row\n",
+ " return sqrt((xdist*xdist) + (ydist*ydist))\n",
+ " \n",
+ " return distance\n",
+ "\n",
+ "def manhattan_distance(goal:MazeLocation) -> Callable[[MazeLocation], float]:\n",
+ " def distance(ml:MazeLocation) -> float:\n",
+ " xdist:int = ml.column - goal.column\n",
+ " ydist:int = ml.row - goal.row\n",
+ " return abs(xdist) + abs(ydist)\n",
+ " \n",
+ " return distance\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 63,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "MazeLocation(row=9, column=9)\n",
+ "5.656854249492381 8\n"
+ ]
+ }
+ ],
+ "source": [
+ "test_ml = MazeLocation(row=5, column=5)\n",
+ "print(m.goal)\n",
+ "ed = euclidean_distance(m.goal)\n",
+ "md = manhattan_distance(m.goal)\n",
+ "print(ed(test_ml), md(test_ml))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 64,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def astar(initial:T, goal_test:Callable[[T], bool], \n",
+ " successors:Callable[[T], List[T]],\n",
+ " heuristic: Callable[[T], float]) -> Optional[Node[T]]:\n",
+ " frontier: PriorityQueue[Node[T]] = PriorityQueue()\n",
+ " frontier.push(Node(initial, None, 0.0, heuristic(initial)))\n",
+ " explored: Dict[T, float] = {initial:0.0}\n",
+ " while not frontier.empty:\n",
+ " current_node:Node[T] = frontier.pop()\n",
+ " current_state: T = current_node.state\n",
+ " # If we found goal, we're done\n",
+ " if goal_test(current_state):\n",
+ " return current_node\n",
+ " # Check where we can go next and where we haven't explored\n",
+ " for child in successors(current_state):\n",
+ " new_cost:float = current_node.cost + 1 # Assumes a grid with equal step costs\n",
+ " if child not in explored or explored[child] > new_cost:\n",
+ " explored[child] = new_cost\n",
+ " frontier.push(Node(child, current_node, new_cost, heuristic(child)))\n",
+ " return None"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 66,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "S X X\n",
+ "* X X \n",
+ "* XX \n",
+ "* X X \n",
+ "***XX X \n",
+ " X*X \n",
+ " ********\n",
+ "X *\n",
+ " *\n",
+ " X X XG\n",
+ "\n"
+ ]
+ }
+ ],
+ "source": [
+ "distance:Callable[[MazeLocation], float] = manhattan_distance(m.goal)\n",
+ "solution3:Optional[Node[MazeLocation]] = astar(m.start, m.goal_test, m.successors, distance)\n",
+ "if solution3 is None:\n",
+ " print(\"No solution found using A*\")\n",
+ "else:\n",
+ " path1: List[MazeLocation] = node_to_path(solution3)\n",
+ " m.mark(path1)\n",
+ " print(m)\n",
+ " m.clear(path1)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Missionaries and cannibals"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 93,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "MAX_NUM:int = 3\n",
+ " \n",
+ "class MCState:\n",
+ " \n",
+ " def __init__(self, missionaries:int, cannibals:int, boat:bool) -> None:\n",
+ " self.wm:int = missionaries\n",
+ " self.wc:int = cannibals\n",
+ " self.em:int = MAX_NUM - self.wm\n",
+ " self.ec:int = MAX_NUM - self.wc\n",
+ " self.boat:bool = boat\n",
+ " \n",
+ " def __str__(self) -> str:\n",
+ " return \"On the west bank there are {} missionaries and {} cannibals.\\n\"\\\n",
+ " \"On the east bank there are {} missionaries and {} cannibals.\\n\"\\\n",
+ " \"The boat is on the {} bank.\".format(self.wm, self.wc, self.em, self.ec,\n",
+ " (\"west\" if self.boat else \"east\"))\n",
+ " \n",
+ " def goal_test(self) -> bool:\n",
+ " return self.is_legal and self.em == MAX_NUM and self.ec==MAX_NUM\n",
+ " \n",
+ " @property\n",
+ " def is_legal(self) -> bool:\n",
+ " if self.wm < self.wc and self.wm > 0:\n",
+ " return False\n",
+ " if self.em < self.ec and self.em > 0:\n",
+ " return False\n",
+ " return True\n",
+ " \n",
+ " def successors(self) -> List[MCState]:\n",
+ " sucs: List[MCState] = []\n",
+ " if self.boat: # boat on west bank\n",
+ " if self.wm > 1:\n",
+ " sucs.append(MCState(self.wm - 2, self.wc, not self.boat))\n",
+ " if self.wm > 0:\n",
+ " sucs.append(MCState(self.wm - 1, self.wc, not self.boat))\n",
+ " if self.wc > 1:\n",
+ " sucs.append(MCState(self.wm, self.wc - 2, not self.boat))\n",
+ " if self.wc > 0:\n",
+ " sucs.append(MCState(self.wm, self.wc - 1, not self.boat))\n",
+ " if (self.wc > 0) and (self.wm > 0):\n",
+ " sucs.append(MCState(self.wm - 1, self.wc - 1, not self.boat))\n",
+ " else: # boat on east bank\n",
+ " if self.em > 1:\n",
+ " sucs.append(MCState(self.wm + 2, self.wc, not self.boat))\n",
+ " if self.em > 0:\n",
+ " sucs.append(MCState(self.wm + 1, self.wc, not self.boat))\n",
+ " if self.ec > 1:\n",
+ " sucs.append(MCState(self.wm, self.wc + 2, not self.boat))\n",
+ " if self.ec > 0:\n",
+ " sucs.append(MCState(self.wm, self.wc + 1, not self.boat))\n",
+ " if (self.ec > 0) and (self.em > 0):\n",
+ " sucs.append(MCState(self.wm + 1, self.wc + 1, not self.boat))\n",
+ " return [x for x in sucs if x.is_legal]\n",
+ "\n",
+ " \n",
+ " return [x for x in sucs if x.is_legal]\n",
+ "\n",
+ "def display_solution(path: List[MCState]):\n",
+ " if len(path) == 0: # sanity check\n",
+ " return\n",
+ " old_state: MCState = path[0]\n",
+ " print(old_state)\n",
+ " for current_state in path[1:]:\n",
+ " if current_state.boat:\n",
+ " print(\"{} missionaries and {} cannibals moved from the east bank to the west bank.\\n\"\n",
+ " .format(old_state.em - current_state.em, old_state.ec - current_state.ec))\n",
+ " else:\n",
+ " print(\"{} missionaries and {} cannibals moved from the west bank to the east bank.\\n\"\n",
+ " .format(old_state.wm - current_state.wm, old_state.wc - current_state.wc))\n",
+ " print(current_state)\n",
+ " old_state = current_state\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 94,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "On the west bank there are 3 missionaries and 3 cannibals.\n",
+ "On the east bank there are 0 missionaries and 0 cannibals.\n",
+ "The boat is on the west bank. ['On the west bank there are 3 missionaries and 1 cannibals.\\nOn the east bank there are 0 missionaries and 2 cannibals.\\nThe boat is on the east bank.', 'On the west bank there are 3 missionaries and 2 cannibals.\\nOn the east bank there are 0 missionaries and 1 cannibals.\\nThe boat is on the east bank.', 'On the west bank there are 2 missionaries and 2 cannibals.\\nOn the east bank there are 1 missionaries and 1 cannibals.\\nThe boat is on the east bank.']\n"
+ ]
+ }
+ ],
+ "source": [
+ "mc = MCState(3, 3, True)\n",
+ "print(mc, [suc.__str__() for suc in mc.successors()])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 95,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "On the west bank there are 3 missionaries and 3 cannibals.\n",
+ "On the east bank there are 0 missionaries and 0 cannibals.\n",
+ "The boat is on the west bank.\n",
+ "0 missionaries and 2 cannibals moved from the west bank to the east bank.\n",
+ "\n",
+ "On the west bank there are 3 missionaries and 1 cannibals.\n",
+ "On the east bank there are 0 missionaries and 2 cannibals.\n",
+ "The boat is on the east bank.\n",
+ "0 missionaries and 1 cannibals moved from the east bank to the west bank.\n",
+ "\n",
+ "On the west bank there are 3 missionaries and 2 cannibals.\n",
+ "On the east bank there are 0 missionaries and 1 cannibals.\n",
+ "The boat is on the west bank.\n",
+ "0 missionaries and 2 cannibals moved from the west bank to the east bank.\n",
+ "\n",
+ "On the west bank there are 3 missionaries and 0 cannibals.\n",
+ "On the east bank there are 0 missionaries and 3 cannibals.\n",
+ "The boat is on the east bank.\n",
+ "0 missionaries and 1 cannibals moved from the east bank to the west bank.\n",
+ "\n",
+ "On the west bank there are 3 missionaries and 1 cannibals.\n",
+ "On the east bank there are 0 missionaries and 2 cannibals.\n",
+ "The boat is on the west bank.\n",
+ "2 missionaries and 0 cannibals moved from the west bank to the east bank.\n",
+ "\n",
+ "On the west bank there are 1 missionaries and 1 cannibals.\n",
+ "On the east bank there are 2 missionaries and 2 cannibals.\n",
+ "The boat is on the east bank.\n",
+ "1 missionaries and 1 cannibals moved from the east bank to the west bank.\n",
+ "\n",
+ "On the west bank there are 2 missionaries and 2 cannibals.\n",
+ "On the east bank there are 1 missionaries and 1 cannibals.\n",
+ "The boat is on the west bank.\n",
+ "2 missionaries and 0 cannibals moved from the west bank to the east bank.\n",
+ "\n",
+ "On the west bank there are 0 missionaries and 2 cannibals.\n",
+ "On the east bank there are 3 missionaries and 1 cannibals.\n",
+ "The boat is on the east bank.\n",
+ "0 missionaries and 1 cannibals moved from the east bank to the west bank.\n",
+ "\n",
+ "On the west bank there are 0 missionaries and 3 cannibals.\n",
+ "On the east bank there are 3 missionaries and 0 cannibals.\n",
+ "The boat is on the west bank.\n",
+ "0 missionaries and 2 cannibals moved from the west bank to the east bank.\n",
+ "\n",
+ "On the west bank there are 0 missionaries and 1 cannibals.\n",
+ "On the east bank there are 3 missionaries and 2 cannibals.\n",
+ "The boat is on the east bank.\n",
+ "1 missionaries and 0 cannibals moved from the east bank to the west bank.\n",
+ "\n",
+ "On the west bank there are 1 missionaries and 1 cannibals.\n",
+ "On the east bank there are 2 missionaries and 2 cannibals.\n",
+ "The boat is on the west bank.\n",
+ "1 missionaries and 1 cannibals moved from the west bank to the east bank.\n",
+ "\n",
+ "On the west bank there are 0 missionaries and 0 cannibals.\n",
+ "On the east bank there are 3 missionaries and 3 cannibals.\n",
+ "The boat is on the east bank.\n"
+ ]
+ }
+ ],
+ "source": [
+ "start:MCState = MCState(MAX_NUM, MAX_NUM, True)\n",
+ "solution:Optional[Node[MCState]] = bfs(start, MCState.goal_test, MCState.successors)\n",
+ "if solution is None:\n",
+ " print(\"No solution found\")\n",
+ "else:\n",
+ " path:List[MCState] = node_to_path(solution)\n",
+ " display_solution(path)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 96,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "On the west bank there are 3 missionaries and 2 cannibals.\n",
+ "On the east bank there are 0 missionaries and 1 cannibals.\n",
+ "The boat is on the west bank. ['On the west bank there are 2 missionaries and 2 cannibals.\\nOn the east bank there are 1 missionaries and 1 cannibals.\\nThe boat is on the east bank.', 'On the west bank there are 3 missionaries and 0 cannibals.\\nOn the east bank there are 0 missionaries and 3 cannibals.\\nThe boat is on the east bank.', 'On the west bank there are 3 missionaries and 1 cannibals.\\nOn the east bank there are 0 missionaries and 2 cannibals.\\nThe boat is on the east bank.']\n"
+ ]
+ }
+ ],
+ "source": [
+ "mc = MCState(3, 2, True)\n",
+ "print(mc, [suc.__str__() for suc in mc.successors()])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 99,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "No solution found\n"
+ ]
+ }
+ ],
+ "source": [
+ "start:MCState = MCState(3, 0, True)\n",
+ "solution:Optional[Node[MCState]] = bfs(start, MCState.goal_test, MCState.successors)\n",
+ "if solution is None:\n",
+ " print(\"No solution found\")\n",
+ "else:\n",
+ " path:List[MCState] = node_to_path(solution)\n",
+ " display_solution(path)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.7.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}