├── search ├── map_example │ └── paris_map_high2.png ├── tictactoe │ ├── OpenSans-Regular.ttf │ ├── tictactoe.py │ └── runner.py ├── search_algo_demo │ ├── main.py │ ├── test_maze.py │ ├── cell.py │ ├── graphics.py │ ├── control_panel.py │ └── maze.py └── search_notes.ipynb ├── optimization ├── hospitals │ ├── assets │ │ ├── images │ │ │ ├── House.png │ │ │ └── Hospital.png │ │ └── fonts │ │ │ └── OpenSans-Regular.ttf │ └── hospitals.py └── ipl_example │ └── ipl_team_selection.py ├── requirements.txt ├── knowledge └── knights │ ├── puzzle.py │ └── logic.py ├── uncertainty └── bayesian_network.ipynb ├── README.md └── oops └── oop_notes.ipynb /search/map_example/paris_map_high2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yash161101/Applied-AI-Batch-A/HEAD/search/map_example/paris_map_high2.png -------------------------------------------------------------------------------- /search/tictactoe/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yash161101/Applied-AI-Batch-A/HEAD/search/tictactoe/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /optimization/hospitals/assets/images/House.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yash161101/Applied-AI-Batch-A/HEAD/optimization/hospitals/assets/images/House.png -------------------------------------------------------------------------------- /optimization/hospitals/assets/images/Hospital.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yash161101/Applied-AI-Batch-A/HEAD/optimization/hospitals/assets/images/Hospital.png -------------------------------------------------------------------------------- /optimization/hospitals/assets/fonts/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yash161101/Applied-AI-Batch-A/HEAD/optimization/hospitals/assets/fonts/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | appnope==0.1.2 2 | asttokens==2.0.5 3 | comm==0.2.1 4 | contourpy==1.2.1 5 | cycler==0.12.1 6 | debugpy==1.6.7 7 | decorator==5.1.1 8 | executing==0.8.3 9 | fonttools==4.53.1 10 | ipykernel==6.28.0 11 | ipython==8.25.0 12 | jedi==0.19.1 13 | jupyter_client==8.6.0 14 | jupyter_core==5.7.2 15 | kiwisolver==1.4.5 16 | matplotlib==3.9.1 17 | matplotlib-inline==0.1.6 18 | nest-asyncio==1.6.0 19 | networkx==3.3 20 | numpy==2.0.1 21 | packaging==24.1 22 | parso==0.8.3 23 | pexpect==4.8.0 24 | pillow==10.4.0 25 | pip==24.0 26 | platformdirs==3.10.0 27 | prompt-toolkit==3.0.43 28 | psutil==5.9.0 29 | ptyprocess==0.7.0 30 | pure-eval==0.2.2 31 | pygame==2.6.0 32 | Pygments==2.15.1 33 | pyparsing==3.1.2 34 | python-dateutil==2.9.0.post0 35 | pyzmq==25.1.2 36 | setuptools==69.5.1 37 | six==1.16.0 38 | stack-data==0.2.0 39 | tornado==6.4.1 40 | traitlets==5.14.3 41 | typing_extensions==4.11.0 42 | wcwidth==0.2.5 43 | wheel==0.43.0 44 | -------------------------------------------------------------------------------- /search/search_algo_demo/main.py: -------------------------------------------------------------------------------- 1 | from control_panel import ControlPanel 2 | from graphics import Window 3 | from maze import Maze 4 | 5 | 6 | def main(): 7 | window_width = 800 8 | window_height = 600 9 | win = Window( 10 | width=window_width, 11 | height=window_height, 12 | bg="black", color="white" 13 | ) 14 | 15 | win.add_title("How to be Late to Class 101") 16 | 17 | maze = init_maze(win) 18 | 19 | ControlPanel(win, maze) 20 | 21 | win.wait_for_close() 22 | 23 | 24 | def init_maze(win: Window): 25 | margin = 25 26 | num_rows = 12 27 | num_cols = 12 28 | cell_size_x = (win.width - margin*2)//num_cols 29 | cell_size_y = (win.height - margin*2)//num_rows 30 | 31 | return Maze( 32 | margin, margin, 33 | num_rows, num_cols, 34 | cell_size_x, cell_size_y, 35 | win, 36 | ) 37 | 38 | if __name__ == "__main__": 39 | main() -------------------------------------------------------------------------------- /search/search_algo_demo/test_maze.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from maze import Maze 3 | 4 | 5 | class TestMaze(unittest.TestCase): 6 | def test_maze_create_cells(self): 7 | num_cols = 12 8 | num_rows = 10 9 | m1 = Maze(0, 0, num_rows, num_cols, 10, 10) 10 | self.assertEqual( 11 | len(m1._cells), 12 | num_rows, 13 | ) 14 | self.assertEqual( 15 | len(m1._cells[0]), 16 | num_cols, 17 | ) 18 | 19 | def test_break_entrance_and_exit(self): 20 | num_cols = 12 21 | num_rows = 10 22 | m1 = Maze(0, 0, num_rows, num_cols, 10, 10) 23 | self.assertEqual( 24 | m1._cells[0][0].has_top_wall, 25 | False 26 | ) 27 | self.assertEqual( 28 | m1._cells[num_rows - 1][num_cols - 1].has_bottom_wall, 29 | False 30 | ) 31 | 32 | def test_reset_cells_visited(self): 33 | num_cols = 12 34 | num_rows = 10 35 | m1 = Maze(0, 0, num_rows, num_cols, 10, 10, seed=0) 36 | for i in range(0, num_rows): 37 | for j in range(0, num_cols): 38 | self.assertEqual( 39 | m1._cells[i][j].visited, 40 | False 41 | ) 42 | 43 | 44 | if __name__ == "__main__": 45 | unittest.main() 46 | -------------------------------------------------------------------------------- /search/search_algo_demo/cell.py: -------------------------------------------------------------------------------- 1 | from typing import Self 2 | from graphics import Line, Point, Window 3 | 4 | class Cell: 5 | def __init__( 6 | self, 7 | win: Window, 8 | ): 9 | self._win = win 10 | self.has_bottom_wall = True 11 | self.has_top_wall = True 12 | self.has_right_wall = True 13 | self.has_left_wall = True 14 | self._x1 = None 15 | self._y1 = None 16 | self._x2 = None 17 | self._y2 = None 18 | self.visited = False 19 | 20 | def draw( 21 | self, 22 | x1: int, y1: int, 23 | x2: int, y2: int, 24 | ): 25 | self._x1 = x1 26 | self._y1 = y1 27 | self._x2 = x2 28 | self._y2 = y2 29 | if self.has_left_wall: 30 | lw = Line(Point(x1, y1), Point(x1, y2)) 31 | self._win.draw_line(lw) 32 | else: 33 | lw = Line(Point(x1, y1), Point(x1, y2)) 34 | self._win.draw_line(lw, self._win.bg) 35 | if self.has_right_wall: 36 | rw = Line(Point(x2, y1), Point(x2, y2)) 37 | self._win.draw_line(rw) 38 | else: 39 | rw = Line(Point(x2, y1), Point(x2, y2)) 40 | self._win.draw_line(rw, self._win.bg) 41 | if self.has_top_wall: 42 | tw = Line(Point(x1, y1), Point(x2, y1)) 43 | self._win.draw_line(tw) 44 | else: 45 | tw = Line(Point(x1, y1), Point(x2, y1)) 46 | self._win.draw_line(tw, self._win.bg) 47 | if self.has_bottom_wall: 48 | bw = Line(Point(x1, y2), Point(x2, y2)) 49 | self._win.draw_line(bw) 50 | else: 51 | bw = Line(Point(x1, y2), Point(x2, y2)) 52 | self._win.draw_line(bw, self._win.bg) 53 | 54 | def draw_move(self, to_cell: Self, undo=False): 55 | fill_color = "gray" if undo else "red" 56 | if ( 57 | self._x1 is None or self._x2 is None 58 | or self._y1 is None or self._y2 is None 59 | or to_cell._x1 is None or to_cell._x2 is None 60 | or to_cell._y1 is None or to_cell._y2 is None 61 | ): 62 | raise ValueError("Cells should be drawn first") 63 | cell1_center = Point( 64 | self._x1 + abs(self._x2 - self._x1)//2, 65 | self._y1 + abs(self._y2 - self._y1)//2 66 | ) 67 | cell2_center = Point( 68 | to_cell._x1 + abs(to_cell._x2 - to_cell._x1)//2, 69 | to_cell._y1 + abs(to_cell._y2 - to_cell._y1)//2 70 | ) 71 | line = Line(cell1_center, cell2_center) 72 | self._win.draw_line(line, fill_color) 73 | -------------------------------------------------------------------------------- /optimization/ipl_example/ipl_team_selection.py: -------------------------------------------------------------------------------- 1 | import pulp 2 | 3 | def define_problem(): 4 | # Initialize the problem 5 | prob = pulp.LpProblem("IPL_Team_Selection", pulp.LpMaximize) 6 | 7 | # Define decision variables 8 | x = pulp.LpVariable.dicts("Player", range(1, 21), cat='Binary') 9 | 10 | # Objective function 11 | prob += (95*x[1] + 90*x[2] + 85*x[3] + 92*x[4] + 88*x[5] + 86*x[6] + 12 | 94*x[7] + 87*x[8] + 89*x[9] + 88*x[10] + 90*x[11] + 91*x[12] + 13 | 88*x[13] + 87*x[14] + 89*x[15] + 90*x[16] + 85*x[17] + 86*x[18] + 14 | 84*x[19] + 87*x[20]), "Total Performance Score" 15 | 16 | # Constraints 17 | prob += (16*x[1] + 15*x[2] + 12*x[3] + 14*x[4] + 13*x[5] + 11*x[6] + 18 | 15*x[7] + 10*x[8] + 12*x[9] + 11*x[10] + 14*x[11] + 13*x[12] + 19 | 12*x[13] + 11*x[14] + 12*x[15] + 14*x[16] + 10*x[17] + 11*x[18] + 20 | 9*x[19] + 11*x[20]) <= 140, "Budget Constraint" 21 | 22 | prob += (x[1] + x[2] + x[3] + x[4] + x[14] + x[15]) >= 4, "Batsmen Constraint" 23 | prob += (x[7] + x[8] + x[9] + x[10] + x[17] + x[18] + x[19]) >= 3, "Bowlers Constraint" 24 | prob += (x[5] + x[6] + x[16]) >= 1, "Wicketkeeper Constraint" 25 | prob += (x[11] + x[12] + x[13] + x[20]) >= 2, "Allrounders Constraint" 26 | prob += (x[3] + x[4] + x[6] + x[8] + x[9] + x[12] + x[18] + x[20]) <= 4, "Overseas Constraint" 27 | prob += (x[1] + x[2] + x[5] + x[7] + x[10] + x[11] + x[13] + x[14] + x[15] + x[16] + x[17] + x[19]) >= 7, "Indian Players Constraint" 28 | prob += sum(x[i] for i in range(1, 21)) == 11, "Total Players Constraint" 29 | 30 | return prob, x 31 | 32 | def solve_problem(prob, x): 33 | # Solve the problem 34 | prob.solve() 35 | 36 | # Extract selected players 37 | selected_team = [] 38 | for i in range(1, 21): 39 | if x[i].varValue == 1: 40 | selected_team.append(i) 41 | 42 | return selected_team 43 | 44 | def get_selected_team_names(selected_team): 45 | selected_team_names = ["Virat Kohli", "Rohit Sharma", "Kane Williamson", "AB de Villiers", 46 | "KL Rahul", "Jos Buttler", "Jasprit Bumrah", "Pat Cummins", "Kagiso Rabada", 47 | "Bhuvneshwar Kumar", "Hardik Pandya", "Ben Stokes", "Ravindra Jadeja", 48 | "Shikhar Dhawan", "Suryakumar Yadav", "Rishabh Pant", "Mohammed Shami", 49 | "Trent Boult", "Yuzvendra Chahal", "Marcus Stoinis"] 50 | 51 | selected_team_final = [selected_team_names[i-1] for i in selected_team] 52 | return selected_team_final 53 | 54 | def main(): 55 | # Define the problem 56 | prob, x = define_problem() 57 | 58 | # Solve the problem 59 | selected_team = solve_problem(prob, x) 60 | 61 | # Get selected team names 62 | selected_team_final = get_selected_team_names(selected_team) 63 | 64 | # Display the selected team 65 | print("Selected IPL Team:") 66 | for player in selected_team_final: 67 | print(player) 68 | 69 | if __name__ == "__main__": 70 | main() 71 | -------------------------------------------------------------------------------- /knowledge/knights/puzzle.py: -------------------------------------------------------------------------------- 1 | from sys import implementation 2 | from logic import * 3 | 4 | AKnight = Symbol("A is a Knight") 5 | AKnave = Symbol("A is a Knave") 6 | 7 | BKnight = Symbol("B is a Knight") 8 | BKnave = Symbol("B is a Knave") 9 | 10 | CKnight = Symbol("C is a Knight") 11 | CKnave = Symbol("C is a Knave") 12 | 13 | knowledgeBase = And( 14 | Or(AKnight,AKnave), 15 | Or(BKnight,BKnave), 16 | Or(CKnight,CKnave), 17 | Not(And(AKnight,AKnave)), 18 | Not(And(BKnight,BKnave)), 19 | Not(And(CKnight,CKnave)), 20 | ) 21 | 22 | # Puzzle 0 23 | # A says "I am both a knight and a knave." 24 | knowledge0 = And( 25 | knowledgeBase, 26 | Implication(AKnight, And(AKnight, AKnave)), 27 | Implication(AKnave, Not(And(AKnight, AKnave))) 28 | ) 29 | 30 | # Puzzle 1 31 | # A says "We are both knaves." 32 | # B says nothing. 33 | knowledge1 = And( 34 | knowledgeBase, 35 | Implication(AKnight, And(AKnave,BKnave)), 36 | Implication(AKnave, Not(And(AKnave, BKnave))) 37 | ) 38 | 39 | # Puzzle 2 40 | # A says "We are the same kind." 41 | # B says "We are of different kinds." 42 | knowledge2 = And( 43 | knowledgeBase, 44 | # A says "We are the same kind." 45 | Implication(AKnight, Or(And(AKnight,BKnight),And(AKnave,BKnave))), 46 | Implication(AKnave, Not(Or(And(AKnight,BKnight),And(AKnave,BKnave)))), 47 | # B says "We are of different kinds." 48 | Implication(BKnight, Or(And(BKnight, AKnave), And(BKnave, AKnight))), 49 | Implication(BKnave, Not(Or(And(BKnight, AKnave), And(BKnave, AKnight)))) 50 | ) 51 | 52 | # Puzzle 3 53 | # A says either "I am a knight." or "I am a knave.", but you don't know which. 54 | # B says "A said 'I am a knave'." 55 | # B says "C is a knave." 56 | # C says "A is a knight." 57 | knowledge3 = And( 58 | knowledgeBase, 59 | # A says either "I am a knight." or "I am a knave.", but you don't know which. 60 | Implication(AKnight,Or(AKnight, AKnave)), 61 | Implication(AKnave,Not(Or(AKnight, AKnave))), 62 | # B says "A said 'I am a knave'." 63 | Or(Implication(BKnight,Or(Implication(AKnight,AKnave), Implication(AKnave, Not(AKnave)))),Implication(BKnave, Not(Or(Implication(AKnight,AKnave), Implication(AKnave, Not(AKnave)))))), 64 | # B says "C is a knave." 65 | Implication(BKnight, CKnave), 66 | Implication(BKnave, Not(CKnave)), 67 | # C says "A is a knight." 68 | Implication(CKnight, AKnight), 69 | Implication(CKnave, Not(AKnight)) 70 | ) 71 | 72 | 73 | def main(): 74 | symbols = [AKnight, AKnave, BKnight, BKnave, CKnight, CKnave] 75 | puzzles = [ 76 | ("Puzzle 0", knowledge0), 77 | ("Puzzle 1", knowledge1), 78 | ("Puzzle 2", knowledge2), 79 | ("Puzzle 3", knowledge3) 80 | ] 81 | for puzzle, knowledge in puzzles: 82 | print(puzzle) 83 | if len(knowledge.conjuncts) == 0: 84 | print(" Not yet implemented.") 85 | else: 86 | for symbol in symbols: 87 | if model_check(knowledge, symbol): 88 | print(f" {symbol}") 89 | 90 | 91 | if __name__ == "__main__": 92 | main() -------------------------------------------------------------------------------- /search/search_algo_demo/graphics.py: -------------------------------------------------------------------------------- 1 | from tkinter import CENTER, Frame, Label, Tk, Canvas 2 | 3 | 4 | class Point: 5 | def __init__(self, x: int, y: int): 6 | self.x = x 7 | self.y = y 8 | 9 | 10 | class Line: 11 | def __init__(self, p1: Point, p2: Point): 12 | self.p1 = p1 13 | self.p2 = p2 14 | 15 | def draw(self, canvas: Canvas, fill_color: str = "black"): 16 | canvas.create_line( 17 | self.p1.x, self.p1.y, 18 | self.p2.x, self.p2.y, 19 | fill=fill_color, 20 | width=2 21 | ) 22 | 23 | 24 | class Window: 25 | def __init__(self, width: int, height: int, bg="#333332", color="black"): 26 | self.bg = bg 27 | self.color = color 28 | self.width = width 29 | self.height = height 30 | self.__root = Tk() 31 | self.__root.title("HowToBeLateToClass") 32 | self.__root.configure(background=bg) 33 | self.__canvas = Canvas( 34 | self.__root, 35 | width=width, 36 | height=height, 37 | bg=bg, 38 | highlightbackground=bg 39 | ) 40 | self.__canvas.pack( 41 | fill="x", 42 | expand=1 43 | ) 44 | self.__root.protocol('WM_DELETE_WINDOW', self.close) 45 | 46 | def clear_canvas(self): 47 | self.__canvas.delete("all") 48 | 49 | def redraw(self): 50 | self.__root.update_idletasks() 51 | self.__root.update() 52 | 53 | def wait_for_close(self): 54 | self.__root.mainloop() 55 | 56 | def close(self): 57 | self.__root.destroy() 58 | 59 | def draw_line(self, line: Line, fill_color: str | None = None): 60 | color = self.color if fill_color is None else fill_color 61 | line.draw(self.__canvas, color) 62 | 63 | def write_text(self, x: int, y: int, text: str, **kwargs): 64 | self.__canvas.create_text( 65 | x, 66 | y, 67 | text=text, 68 | justify=CENTER, 69 | **kwargs, 70 | ) 71 | 72 | def add_title(self, text): 73 | frm = self.add_frame( 74 | height=50, 75 | pady=10, 76 | pack_options={ 77 | "before": self.__canvas 78 | } 79 | ) 80 | label = Label( 81 | frm, 82 | text=text, 83 | font=("Sans-serif", 30, "bold"), 84 | justify=CENTER, 85 | bg=self.bg, 86 | highlightbackground=self.bg 87 | ) 88 | label.pack( 89 | fill="x", 90 | expand=1 91 | ) 92 | return label 93 | 94 | def add_frame(self, height: int, **kwargs): 95 | pack_options = kwargs.pop("pack_options", {}) 96 | bg = kwargs.pop("bg") if "bg" in kwargs else self.bg 97 | frm = Frame( 98 | self.__root, 99 | height=height, 100 | bg=bg, 101 | **kwargs 102 | ) 103 | frm.pack( 104 | fill="x", 105 | expand=1, 106 | **pack_options 107 | ) 108 | return frm 109 | -------------------------------------------------------------------------------- /search/tictactoe/tictactoe.py: -------------------------------------------------------------------------------- 1 | import math 2 | from copy import deepcopy 3 | 4 | X = "X" 5 | O = "O" 6 | EMPTY = None 7 | 8 | 9 | def initial_state(): 10 | """ 11 | Returns starting state of the board. 12 | """ 13 | return [[EMPTY, EMPTY, EMPTY], 14 | [EMPTY, EMPTY, EMPTY], 15 | [EMPTY, EMPTY, EMPTY]] 16 | 17 | 18 | def player(board): 19 | """ 20 | Returns player who has the next turn on a board. 21 | """ 22 | Xcount = 0 23 | Ocount = 0 24 | 25 | for row in board: 26 | Xcount += row.count(X) 27 | Ocount += row.count(O) 28 | 29 | if Xcount <= Ocount: 30 | return X 31 | else: 32 | return O 33 | 34 | 35 | def actions(board): 36 | """ 37 | Returns set of all possible actions (i, j) available on the board. 38 | """ 39 | 40 | possible_moves = set() 41 | 42 | for row_index, row in enumerate(board): 43 | for column_index, item in enumerate(row): 44 | if item == EMPTY: 45 | possible_moves.add((row_index, column_index)) 46 | 47 | return possible_moves 48 | 49 | 50 | def result(board, action): 51 | """ 52 | Returns the board that results from making move (i, j) on the board. 53 | """ 54 | player_move = player(board) 55 | 56 | new_board = deepcopy(board) 57 | i, j = action 58 | 59 | if board[i][j] != None: 60 | raise Exception 61 | else: 62 | new_board[i][j] = player_move 63 | 64 | return new_board 65 | 66 | 67 | def winner(board): 68 | """ 69 | Returns the winner of the game, if there is one. 70 | """ 71 | for player in (X, O): 72 | # check vertical 73 | for row in board: 74 | if row == [player] * 3: 75 | return player 76 | 77 | # check horizontal 78 | for i in range(3): 79 | column = [board[x][i] for x in range(3)] 80 | if column == [player] * 3: 81 | return player 82 | 83 | # check diagonal 84 | if [board[i][i] for i in range(0, 3)] == [player] * 3: 85 | return player 86 | 87 | elif [board[i][~i] for i in range(0, 3)] == [player] * 3: 88 | return player 89 | return None 90 | 91 | 92 | def terminal(board): 93 | """ 94 | Returns True if game is over, False otherwise. 95 | """ 96 | # game is won by one of the players 97 | if winner(board) != None: 98 | return True 99 | 100 | # moves still possible 101 | for row in board: 102 | if EMPTY in row: 103 | return False 104 | 105 | # no possible moves 106 | return True 107 | 108 | 109 | def utility(board): 110 | """ 111 | Returns 1 if X has won the game, -1 if O has won, 0 otherwise. 112 | """ 113 | 114 | win_player = winner(board) 115 | 116 | if win_player == X: 117 | return 1 118 | elif win_player == O: 119 | return -1 120 | else: 121 | return 0 122 | 123 | 124 | def minimax(board): 125 | """ 126 | Returns the optimal action for the current player on the board. 127 | """ 128 | 129 | def max_value(board): 130 | optimal_move = () 131 | if terminal(board): 132 | return utility(board), optimal_move 133 | else: 134 | v = -float('inf') 135 | for action in actions(board): 136 | minval = min_value(result(board, action))[0] 137 | if minval > v: 138 | v = minval 139 | optimal_move = action 140 | return v, optimal_move 141 | 142 | def min_value(board): 143 | optimal_move = () 144 | if terminal(board): 145 | return utility(board), optimal_move 146 | else: 147 | v = float('inf') 148 | for action in actions(board): 149 | maxval = max_value(result(board, action))[0] 150 | if maxval < v: 151 | v = maxval 152 | optimal_move = action 153 | return v, optimal_move 154 | 155 | curr_player = player(board) 156 | 157 | if terminal(board): 158 | return None 159 | 160 | if curr_player == X: 161 | return max_value(board)[1] 162 | 163 | else: 164 | return min_value(board)[1] 165 | -------------------------------------------------------------------------------- /search/search_algo_demo/control_panel.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Callable 2 | from tkinter import LEFT, Button, Entry, Frame, Label 3 | from graphics import Window 4 | from maze import Maze 5 | 6 | 7 | class ControlPanel: 8 | def __init__( 9 | self, 10 | win: Window, 11 | maze: Maze 12 | ): 13 | self._win = win 14 | self._maze = maze 15 | self._algo_frm = self._create_frame() 16 | self._maze_control_frm = self._create_frame() 17 | self._solve_dfs_btn = self._add_button( 18 | self._algo_frm, 19 | "Solve with DFS algorithm", 20 | maze.solve, 21 | pack_options={ 22 | "ipadx": 20, 23 | "ipady": 5, 24 | } 25 | ) 26 | self._solve_bfs_btn = self._add_button( 27 | self._algo_frm, 28 | "Solve with BFS algorithm", 29 | maze.solve_bfs, 30 | pack_options={ 31 | "ipadx": 20, 32 | "ipady": 5, 33 | } 34 | ) 35 | self._solve_a_star_btn = self._add_button( 36 | self._algo_frm, 37 | "Solve with A* search algorithm", 38 | maze.solve_a_star, 39 | pack_options={ 40 | "ipadx": 3, 41 | "ipady": 5, 42 | } 43 | ) 44 | self._rows_input, self._cols_input = self._add_size_input( 45 | self._maze_control_frm, 46 | pack_options={ 47 | "padx": 5, 48 | "pady": 5, 49 | } 50 | ) 51 | self._add_button( 52 | self._maze_control_frm, 53 | "New Maze", 54 | self._create_new_maze, 55 | fg="#333333", 56 | pack_options={ 57 | "side": LEFT, 58 | "ipadx": 30, 59 | "ipady": 5, 60 | } 61 | ) 62 | 63 | def _create_frame(self): 64 | return self._win.add_frame( 65 | height=100, 66 | width=self._win.width/2, 67 | padx=10, 68 | pady=10, 69 | pack_options={ 70 | "side": LEFT 71 | } 72 | ) 73 | 74 | def _add_button(self, parent, text: str, command: Callable, **kwargs): 75 | pack_options = kwargs.pop("pack_options", {}) 76 | bg = kwargs.pop("bg") if "bg" in kwargs else self._win.bg 77 | btn = Button( 78 | parent, 79 | text=text, 80 | command=command, 81 | bg=bg, 82 | **kwargs 83 | ) 84 | btn.pack( 85 | **pack_options 86 | ) 87 | return btn 88 | 89 | def _add_size_input(self, parent, **kwargs): 90 | pack_options = kwargs.pop("pack_options", {}) 91 | bg = kwargs.pop("bg") if "bg" in kwargs else self._win.bg 92 | 93 | frm = Frame(parent, bg=bg) 94 | frm.pack(fill='x', expand=1) 95 | 96 | lbl = Label( 97 | frm, 98 | text="size", 99 | font=("Sans-serif", 18, "italic"), 100 | bg=bg, 101 | ) 102 | lbl.pack(side=LEFT, ipadx=5, ipady=10) 103 | row = Entry(frm, bg=bg, width=3, **kwargs) 104 | row.pack(side=LEFT, **pack_options) 105 | 106 | lbl2 = Label( 107 | frm, 108 | text="x", 109 | font=("Sans-serif", 18), 110 | bg=bg, 111 | ) 112 | lbl2.pack(side=LEFT, ipadx=5, ipady=10) 113 | column = Entry(frm, bg=bg, width=3, **kwargs) 114 | column.pack(side=LEFT, **pack_options) 115 | 116 | row.insert(0, '12') 117 | column.insert(0, '12') 118 | return row, column 119 | 120 | def _create_new_maze(self): 121 | self._win.clear_canvas() 122 | margin = 25 123 | num_rows = int(self._rows_input.get()) 124 | num_cols = int(self._cols_input.get()) 125 | cell_size_x = (self._win.width - margin*2)//num_cols 126 | cell_size_y = (self._win.height - margin*2)//num_rows 127 | maze = Maze( 128 | margin, margin, 129 | num_rows, num_cols, 130 | cell_size_x, cell_size_y, 131 | self._win, 132 | ) 133 | self._solve_dfs_btn.configure(command=maze.solve) 134 | self._solve_bfs_btn.configure(command=maze.solve_bfs) 135 | self._solve_a_star_btn.configure(command=maze.solve_a_star) 136 | -------------------------------------------------------------------------------- /uncertainty/bayesian_network.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 2, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from pomegranate import *\n", 10 | "\n", 11 | "import numpy as np\n", 12 | "from pomegranate.distributions import *\n", 13 | "from pomegranate.bayesian_network import BayesianNetwork\n", 14 | "import torch" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 3, 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "# Weather in Gotham: clear, cloudy, stormy\n", 24 | "weather = Categorical(\n", 25 | " [\n", 26 | " [0.7, 0.2, 0.1], # Probabilities for clear, cloudy, stormy\n", 27 | " ]\n", 28 | ")\n", 29 | "\n", 30 | "# Bat-Signal activation: yes, no\n", 31 | "bat_signal = ConditionalCategorical(\n", 32 | " [\n", 33 | " [\n", 34 | " [0.4, 0.6], # Probabilities for Bat-Signal given clear weather\n", 35 | " [0.2, 0.8], # Probabilities for Bat-Signal given cloudy weather\n", 36 | " [0.1, 0.9], # Probabilities for Bat-Signal given stormy weather\n", 37 | " ],\n", 38 | " ]\n", 39 | ")\n", 40 | "\n", 41 | "# Batmobile arrival: on time, delayed\n", 42 | "batmobile = ConditionalCategorical(\n", 43 | " [\n", 44 | " [\n", 45 | " [\n", 46 | " [0.8, 0.2], # Probabilities for Batmobile on time given clear weather and Bat-Signal yes\n", 47 | " [0.9, 0.1], # Probabilities for Batmobile on time given clear weather and Bat-Signal no\n", 48 | " ],\n", 49 | " [\n", 50 | " [0.6, 0.4], # Probabilities for Batmobile on time given cloudy weather and Bat-Signal yes\n", 51 | " [0.7, 0.3], # Probabilities for Batmobile on time given cloudy weather and Bat-Signal no\n", 52 | " ],\n", 53 | " [\n", 54 | " [0.4, 0.6], # Probabilities for Batmobile on time given stormy weather and Bat-Signal yes\n", 55 | " [0.5, 0.5], # Probabilities for Batmobile on time given stormy weather and Bat-Signal no\n", 56 | " ],\n", 57 | " ]\n", 58 | " ]\n", 59 | ")\n", 60 | "\n", 61 | "# Crime Fighting success: success, fail\n", 62 | "crime_fighting = ConditionalCategorical(\n", 63 | " [\n", 64 | " [\n", 65 | " [0.9, 0.1], # Probabilities for crime fighting success given Batmobile on time\n", 66 | " [0.6, 0.4], # Probabilities for crime fighting success given Batmobile delayed\n", 67 | " ],\n", 68 | " ]\n", 69 | ")\n", 70 | "\n", 71 | "# Create a Bayesian Network and add states\n", 72 | "model = BayesianNetwork()\n", 73 | "model.add_distributions([weather, bat_signal, batmobile, crime_fighting])\n", 74 | "\n", 75 | "# Add edges connecting nodes\n", 76 | "model.add_edge(weather, bat_signal)\n", 77 | "model.add_edge(weather, batmobile)\n", 78 | "model.add_edge(bat_signal, batmobile)\n", 79 | "model.add_edge(batmobile, crime_fighting)" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": 4, 85 | "metadata": {}, 86 | "outputs": [ 87 | { 88 | "name": "stdout", 89 | "output_type": "stream", 90 | "text": [ 91 | "tensor([0.3402])\n" 92 | ] 93 | } 94 | ], 95 | "source": [ 96 | "weather_values = [\"clear\", \"cloudy\", \"stormy\"]\n", 97 | "bat_signal_values = [\"yes\", \"no\"]\n", 98 | "batmobile_values = [\"on time\", \"delayed\"]\n", 99 | "crime_fighting_values = [\"success\", \"fail\"]\n", 100 | "\n", 101 | "probability = model.probability(\n", 102 | " torch.as_tensor(\n", 103 | " [\n", 104 | " [\n", 105 | " weather_values.index(\"clear\"),\n", 106 | " bat_signal_values.index(\"no\"),\n", 107 | " batmobile_values.index(\"on time\"),\n", 108 | " crime_fighting_values.index(\"success\"),\n", 109 | " ]\n", 110 | " ]\n", 111 | " )\n", 112 | ")\n", 113 | "\n", 114 | "print(probability)" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": null, 120 | "metadata": {}, 121 | "outputs": [], 122 | "source": [] 123 | } 124 | ], 125 | "metadata": { 126 | "kernelspec": { 127 | "display_name": "aai", 128 | "language": "python", 129 | "name": "python3" 130 | }, 131 | "language_info": { 132 | "codemirror_mode": { 133 | "name": "ipython", 134 | "version": 3 135 | }, 136 | "file_extension": ".py", 137 | "mimetype": "text/x-python", 138 | "name": "python", 139 | "nbconvert_exporter": "python", 140 | "pygments_lexer": "ipython3", 141 | "version": "3.10.14" 142 | }, 143 | "orig_nbformat": 4, 144 | "vscode": { 145 | "interpreter": { 146 | "hash": "f13c07b14fad073544cf30f313c5e372bd0c64a011009a03bcc2bc0bcba6863d" 147 | } 148 | } 149 | }, 150 | "nbformat": 4, 151 | "nbformat_minor": 2 152 | } 153 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Applied AI - Batch A 🦾 2 | 3 | This repository contains lecture notes, code examples, and interactive demos for various topics in AI and programming demonstrated in class. 4 | Below, you'll find a list of topics covered along with links to relevant resources. 5 | 6 | ## Setting up Environment 7 | - Create a conda virtual environment: `conda create --name aai_code python=3.12` 8 | - Activate the enviroment: `conda activate aai_code` 9 | - Install requirements in environment: `pip install -r requirements.txt` 10 | 11 | ## Topics and Resources 12 | 13 | ### Topic 1: Object Oriented Programming 14 | 15 | - **Lecture Notes**: [Object Oriented Programming - Lecture Notes](https://github.com/yash161101/Applied-AI-Batch-A/blob/main/oops/oop_notes.ipynb) 16 | - Comprehensive notes covering the fundamentals of Object Oriented Programming (OOP). This resource will help you understand key OOP concepts such as classes, objects, class methods, and inheritance. 17 | 18 | - **Kaggle Example - Titanic Problem**: [Titanic Problem Solved with OOP](https://www.kaggle.com/code/yash161101/topic-1-object-oriented-programming) 19 | - An example demonstrating how to apply Object Oriented Programming principles to solve the Titanic problem on Kaggle. This example showcases the practical application of OOP in data science and machine learning projects. 20 | 21 | ### Topic 2: Search 22 | 23 | - **Lecture Notes**: [Search - Lecture Notes](https://github.com/yash161101/Applied-AI-Batch-A/blob/main/search/search_notes.ipynb) 24 | - A collection of generic code snippets for implementing various search algorithms. This resource provides a foundational understanding of how to code search algorithms from scratch. 25 | 26 | - **Search Algo Demo**: [Search Algorithm Demo](https://github.com/yash161101/Applied-AI-Batch-A/tree/main/search/search_algo_demo) 27 | - An interactive interface that creates a maze and visualizes how Depth-First Search (DFS), Breadth-First Search (BFS), and A* search algorithms work in action. This demo is designed to help you understand how these search algorithms operate in a controlled environment. 28 | - To run this use `python > 3.12` and use the command `python main.py` 29 | 30 | - **Map Example**: [Search Algorithms with a Map of Paris](https://github.com/yash161101/Applied-AI-Batch-A/tree/main/search/map_example) 31 | - A practical example illustrating search algorithms applied to a real-world scenario using a map of the streets of Paris. This example demonstrates how search algorithms can be used to navigate and find paths in geographical data. 32 | 33 | - **tictactoe**: [Adversarial Search with Tic-Tac-Toe](https://github.com/yash161101/Applied-AI-Batch-A/tree/main/search/tictactoe) 34 | - An interactive example explaining adversarial search through a Tic-Tac-Toe game. This resource includes an interface that shows how algorithms can be used to play optimally in adversarial environments. 35 | - To run this use the command `python runner.py` 36 | 37 | ### Topic 3: Knowledge 38 | - **knights**: [Knights and Knaves Problem](https://github.com/yash161101/Applied-AI-Batch-A/tree/main/knowledge/knights) 39 | - The Knights problem is based on the classic "Knights and Knaves" logic puzzles. In these puzzles, characters are either knights, who always tell the truth, or knaves, who always lie. The objective is to use propositional logic to determine the identity (knight or knave) of each character based on the statements they make. The AI uses a model-checking algorithm to evaluate the truthfulness of these statements and deduce the correct identities of the characters 40 | - To run this use the command `python puzzle.py` 41 | 42 | ### Topic 4: Uncertainty 43 | - **Batman Network**: [Bayesian Networks Explained - Batman Example](https://github.com/yash161101/Applied-AI-Batch-A/blob/main/uncertainty/bayesian_network.ipynb) 44 | - An example using pomegranate library demonstrating how Bayesian Networks can be constructed and be used to create inferences and calculate probabilities of states. 45 | 46 | - **Markov Chain Demo**: [Markov Chain Demo - Stocks Example](https://github.com/yash161101/Applied-AI-Batch-A/blob/main/uncertainty/markov_chain.ipynb) 47 | - The code simulates stock price movements using a basic random walk model, with daily returns drawn from a normal distribution. It visualizes multiple simulations of stock price trajectories over time, demonstrating the variability and potential paths prices can take. 48 | 49 | ### Topic 5: Optimization 50 | - **Hospitals**: [Hill Climbing Explained - Hospitals Example](https://github.com/yash161101/Applied-AI-Batch-A/tree/main/optimization/hospitals) 51 | - An example demonstrating how Hill Climbing and Random Hill Climbing works by adding random houses to a grid and finding the optimized way to place n number of hospitals such that they are the closest to all the houses. 52 | - To run this use the command `python hospitals.py` 53 | 54 | - **IPL Team Selection**: [Linear Programming - IPL Team Selection Example](https://github.com/yash161101/Applied-AI-Batch-A/tree/main/optimization/ipl_example) 55 | - This code takes 20 random players and tries to maximize the team performance by keeping in mind the constraints such as budget, team balance and overseas limit to find the best posible team. 56 | - To run this use the command `python ipl_team_selection.py` 57 | 58 | Happy Coding! 59 | -------------------------------------------------------------------------------- /search/tictactoe/runner.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import sys 3 | import time 4 | 5 | import tictactoe as ttt 6 | 7 | pygame.init() 8 | size = width, height = 600, 400 9 | 10 | # Colors 11 | black = (0, 0, 0) 12 | white = (255, 255, 255) 13 | 14 | screen = pygame.display.set_mode(size) 15 | 16 | mediumFont = pygame.font.Font("OpenSans-Regular.ttf", 28) 17 | largeFont = pygame.font.Font("OpenSans-Regular.ttf", 40) 18 | moveFont = pygame.font.Font("OpenSans-Regular.ttf", 60) 19 | 20 | user = None 21 | board = ttt.initial_state() 22 | ai_turn = False 23 | 24 | while True: 25 | 26 | for event in pygame.event.get(): 27 | if event.type == pygame.QUIT: 28 | sys.exit() 29 | 30 | screen.fill(black) 31 | 32 | # Let user choose a player. 33 | if user is None: 34 | 35 | # Draw title 36 | title = largeFont.render("Play Tic-Tac-Toe", True, white) 37 | titleRect = title.get_rect() 38 | titleRect.center = ((width / 2), 50) 39 | screen.blit(title, titleRect) 40 | 41 | # Draw buttons 42 | playXButton = pygame.Rect((width / 8), (height / 2), width / 4, 50) 43 | playX = mediumFont.render("Play as X", True, black) 44 | playXRect = playX.get_rect() 45 | playXRect.center = playXButton.center 46 | pygame.draw.rect(screen, white, playXButton) 47 | screen.blit(playX, playXRect) 48 | 49 | playOButton = pygame.Rect(5 * (width / 8), (height / 2), width / 4, 50) 50 | playO = mediumFont.render("Play as O", True, black) 51 | playORect = playO.get_rect() 52 | playORect.center = playOButton.center 53 | pygame.draw.rect(screen, white, playOButton) 54 | screen.blit(playO, playORect) 55 | 56 | # Check if button is clicked 57 | click, _, _ = pygame.mouse.get_pressed() 58 | if click == 1: 59 | mouse = pygame.mouse.get_pos() 60 | if playXButton.collidepoint(mouse): 61 | time.sleep(0.2) 62 | user = ttt.X 63 | elif playOButton.collidepoint(mouse): 64 | time.sleep(0.2) 65 | user = ttt.O 66 | 67 | else: 68 | 69 | # Draw game board 70 | tile_size = 80 71 | tile_origin = (width / 2 - (1.5 * tile_size), 72 | height / 2 - (1.5 * tile_size)) 73 | tiles = [] 74 | for i in range(3): 75 | row = [] 76 | for j in range(3): 77 | rect = pygame.Rect( 78 | tile_origin[0] + j * tile_size, 79 | tile_origin[1] + i * tile_size, 80 | tile_size, tile_size 81 | ) 82 | pygame.draw.rect(screen, white, rect, 3) 83 | 84 | if board[i][j] != ttt.EMPTY: 85 | move = moveFont.render(board[i][j], True, white) 86 | moveRect = move.get_rect() 87 | moveRect.center = rect.center 88 | screen.blit(move, moveRect) 89 | row.append(rect) 90 | tiles.append(row) 91 | 92 | game_over = ttt.terminal(board) 93 | player = ttt.player(board) 94 | 95 | # Show title 96 | if game_over: 97 | winner = ttt.winner(board) 98 | if winner is None: 99 | title = f"Game Over: Tie." 100 | else: 101 | title = f"Game Over: {winner} wins." 102 | elif user == player: 103 | title = f"Play as {user}" 104 | else: 105 | title = f"Computer thinking..." 106 | title = largeFont.render(title, True, white) 107 | titleRect = title.get_rect() 108 | titleRect.center = ((width / 2), 30) 109 | screen.blit(title, titleRect) 110 | 111 | # Check for AI move 112 | if user != player and not game_over: 113 | if ai_turn: 114 | time.sleep(0.5) 115 | move = ttt.minimax(board) 116 | board = ttt.result(board, move) 117 | ai_turn = False 118 | else: 119 | ai_turn = True 120 | 121 | # Check for a user move 122 | click, _, _ = pygame.mouse.get_pressed() 123 | if click == 1 and user == player and not game_over: 124 | mouse = pygame.mouse.get_pos() 125 | for i in range(3): 126 | for j in range(3): 127 | if (board[i][j] == ttt.EMPTY and tiles[i][j].collidepoint(mouse)): 128 | board = ttt.result(board, (i, j)) 129 | 130 | if game_over: 131 | againButton = pygame.Rect(width / 3, height - 65, width / 3, 50) 132 | again = mediumFont.render("Play Again", True, black) 133 | againRect = again.get_rect() 134 | againRect.center = againButton.center 135 | pygame.draw.rect(screen, white, againButton) 136 | screen.blit(again, againRect) 137 | click, _, _ = pygame.mouse.get_pressed() 138 | if click == 1: 139 | mouse = pygame.mouse.get_pos() 140 | if againButton.collidepoint(mouse): 141 | time.sleep(0.2) 142 | user = None 143 | board = ttt.initial_state() 144 | ai_turn = False 145 | 146 | pygame.display.flip() 147 | -------------------------------------------------------------------------------- /optimization/hospitals/hospitals.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | 4 | class Space(): 5 | 6 | def __init__(self, height, width, num_hospitals): 7 | """Create a new state space with given dimensions.""" 8 | self.height = height 9 | self.width = width 10 | self.num_hospitals = num_hospitals 11 | self.houses = set() 12 | self.hospitals = set() 13 | 14 | def add_house(self, row, col): 15 | """Add a house at a particular location in state space.""" 16 | self.houses.add((row, col)) 17 | 18 | def available_spaces(self): 19 | """Returns all cells not currently used by a house or hospital.""" 20 | 21 | # Consider all possible cells 22 | candidates = set( 23 | (row, col) 24 | for row in range(self.height) 25 | for col in range(self.width) 26 | ) 27 | 28 | # Remove all houses and hospitals 29 | for house in self.houses: 30 | candidates.remove(house) 31 | for hospital in self.hospitals: 32 | candidates.remove(hospital) 33 | return candidates 34 | 35 | def hill_climb(self, maximum=None, image_prefix=None, log=False): 36 | """Performs hill-climbing to find a solution.""" 37 | count = 0 38 | 39 | # Start by initializing hospitals randomly 40 | self.hospitals = set() 41 | for i in range(self.num_hospitals): 42 | self.hospitals.add(random.choice(list(self.available_spaces()))) 43 | if log: 44 | print("Initial state: cost", self.get_cost(self.hospitals)) 45 | if image_prefix: 46 | self.output_image(f"{image_prefix}{str(count).zfill(3)}.png") 47 | 48 | # Continue until we reach maximum number of iterations 49 | while maximum is None or count < maximum: 50 | count += 1 51 | best_neighbors = [] 52 | best_neighbor_cost = None 53 | 54 | # Consider all hospitals to move 55 | for hospital in self.hospitals: 56 | 57 | # Consider all neighbors for that hospital 58 | for replacement in self.get_neighbors(*hospital): 59 | 60 | # Generate a neighboring set of hospitals 61 | neighbor = self.hospitals.copy() 62 | neighbor.remove(hospital) 63 | neighbor.add(replacement) 64 | 65 | # Check if neighbor is best so far 66 | cost = self.get_cost(neighbor) 67 | if best_neighbor_cost is None or cost < best_neighbor_cost: 68 | best_neighbor_cost = cost 69 | best_neighbors = [neighbor] 70 | elif best_neighbor_cost == cost: 71 | best_neighbors.append(neighbor) 72 | 73 | # None of the neighbors are better than the current state 74 | if best_neighbor_cost >= self.get_cost(self.hospitals): 75 | return self.hospitals 76 | 77 | # Move to a highest-valued neighbor 78 | else: 79 | if log: 80 | print(f"Found better neighbor: cost {best_neighbor_cost}") 81 | self.hospitals = random.choice(best_neighbors) 82 | 83 | # Generate image 84 | if image_prefix: 85 | self.output_image(f"{image_prefix}{str(count).zfill(3)}.png") 86 | 87 | def random_restart(self, maximum, image_prefix=None, log=False): 88 | """Repeats hill-climbing multiple times.""" 89 | best_hospitals = None 90 | best_cost = None 91 | 92 | # Repeat hill-climbing a fixed number of times 93 | for i in range(maximum): 94 | hospitals = self.hill_climb() 95 | cost = self.get_cost(hospitals) 96 | if best_cost is None or cost < best_cost: 97 | best_cost = cost 98 | best_hospitals = hospitals 99 | if log: 100 | print(f"{i}: Found new best state: cost {cost}") 101 | else: 102 | if log: 103 | print(f"{i}: Found state: cost {cost}") 104 | 105 | if image_prefix: 106 | self.output_image(f"{image_prefix}{str(i).zfill(3)}.png") 107 | 108 | return best_hospitals 109 | 110 | def get_cost(self, hospitals): 111 | """Calculates sum of distances from houses to nearest hospital.""" 112 | cost = 0 113 | for house in self.houses: 114 | cost += min( 115 | abs(house[0] - hospital[0]) + abs(house[1] - hospital[1]) 116 | for hospital in hospitals 117 | ) 118 | return cost 119 | 120 | def get_neighbors(self, row, col): 121 | """Returns neighbors not already containing a house or hospital.""" 122 | candidates = [ 123 | (row - 1, col), 124 | (row + 1, col), 125 | (row, col - 1), 126 | (row, col + 1) 127 | ] 128 | neighbors = [] 129 | for r, c in candidates: 130 | if (r, c) in self.houses or (r, c) in self.hospitals: 131 | continue 132 | if 0 <= r < self.height and 0 <= c < self.width: 133 | neighbors.append((r, c)) 134 | return neighbors 135 | 136 | def output_image(self, filename): 137 | """Generates image with all houses and hospitals.""" 138 | from PIL import Image, ImageDraw, ImageFont 139 | cell_size = 100 140 | cell_border = 2 141 | cost_size = 40 142 | padding = 10 143 | 144 | # Create a blank canvas 145 | img = Image.new( 146 | "RGBA", 147 | (self.width * cell_size, 148 | self.height * cell_size + cost_size + padding * 2), 149 | "white" 150 | ) 151 | house = Image.open("assets/images/House.png").resize( 152 | (cell_size, cell_size) 153 | ) 154 | hospital = Image.open("assets/images/Hospital.png").resize( 155 | (cell_size, cell_size) 156 | ) 157 | font = ImageFont.truetype("assets/fonts/OpenSans-Regular.ttf", 30) 158 | draw = ImageDraw.Draw(img) 159 | 160 | for i in range(self.height): 161 | for j in range(self.width): 162 | 163 | # Draw cell 164 | rect = [ 165 | (j * cell_size + cell_border, 166 | i * cell_size + cell_border), 167 | ((j + 1) * cell_size - cell_border, 168 | (i + 1) * cell_size - cell_border) 169 | ] 170 | draw.rectangle(rect, fill="black") 171 | 172 | if (i, j) in self.houses: 173 | img.paste(house, rect[0], house) 174 | if (i, j) in self.hospitals: 175 | img.paste(hospital, rect[0], hospital) 176 | 177 | # Add cost 178 | draw.rectangle( 179 | (0, self.height * cell_size, self.width * cell_size, 180 | self.height * cell_size + cost_size + padding * 2), 181 | "black" 182 | ) 183 | draw.text( 184 | (padding, self.height * cell_size + padding), 185 | f"Cost: {self.get_cost(self.hospitals)}", 186 | fill="white", 187 | font=font 188 | ) 189 | 190 | img.save(filename) 191 | 192 | 193 | # Create a new space and add houses randomly 194 | s = Space(height=10, width=20, num_hospitals=3) 195 | for i in range(15): 196 | s.add_house(random.randrange(s.height), random.randrange(s.width)) 197 | 198 | # Use local search to determine hospital placement 199 | hospitals = s.hill_climb(image_prefix="hospitals", log=True) 200 | 201 | # hospitals = s.random_restart(20, image_prefix="hospitals", log=True) -------------------------------------------------------------------------------- /knowledge/knights/logic.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | 3 | 4 | class Sentence(): 5 | 6 | def evaluate(self, model): 7 | """Evaluates the logical sentence.""" 8 | raise Exception("nothing to evaluate") 9 | 10 | def formula(self): 11 | """Returns string formula representing logical sentence.""" 12 | return "" 13 | 14 | def symbols(self): 15 | """Returns a set of all symbols in the logical sentence.""" 16 | return set() 17 | 18 | @classmethod 19 | def validate(cls, sentence): 20 | if not isinstance(sentence, Sentence): 21 | raise TypeError("must be a logical sentence") 22 | 23 | @classmethod 24 | def parenthesize(cls, s): 25 | """Parenthesizes an expression if not already parenthesized.""" 26 | def balanced(s): 27 | """Checks if a string has balanced parentheses.""" 28 | count = 0 29 | for c in s: 30 | if c == "(": 31 | count += 1 32 | elif c == ")": 33 | if count <= 0: 34 | return False 35 | count -= 1 36 | return count == 0 37 | if not len(s) or s.isalpha() or ( 38 | s[0] == "(" and s[-1] == ")" and balanced(s[1:-1]) 39 | ): 40 | return s 41 | else: 42 | return f"({s})" 43 | 44 | 45 | class Symbol(Sentence): 46 | 47 | def __init__(self, name): 48 | self.name = name 49 | 50 | def __eq__(self, other): 51 | return isinstance(other, Symbol) and self.name == other.name 52 | 53 | def __hash__(self): 54 | return hash(("symbol", self.name)) 55 | 56 | def __repr__(self): 57 | return self.name 58 | 59 | def evaluate(self, model): 60 | try: 61 | return bool(model[self.name]) 62 | except KeyError: 63 | raise Exception(f"variable {self.name} not in model") 64 | 65 | def formula(self): 66 | return self.name 67 | 68 | def symbols(self): 69 | return {self.name} 70 | 71 | 72 | class Not(Sentence): 73 | def __init__(self, operand): 74 | Sentence.validate(operand) 75 | self.operand = operand 76 | 77 | def __eq__(self, other): 78 | return isinstance(other, Not) and self.operand == other.operand 79 | 80 | def __hash__(self): 81 | return hash(("not", hash(self.operand))) 82 | 83 | def __repr__(self): 84 | return f"Not({self.operand})" 85 | 86 | def evaluate(self, model): 87 | return not self.operand.evaluate(model) 88 | 89 | def formula(self): 90 | return "¬" + Sentence.parenthesize(self.operand.formula()) 91 | 92 | def symbols(self): 93 | return self.operand.symbols() 94 | 95 | 96 | class And(Sentence): 97 | def __init__(self, *conjuncts): 98 | for conjunct in conjuncts: 99 | Sentence.validate(conjunct) 100 | self.conjuncts = list(conjuncts) 101 | 102 | def __eq__(self, other): 103 | return isinstance(other, And) and self.conjuncts == other.conjuncts 104 | 105 | def __hash__(self): 106 | return hash( 107 | ("and", tuple(hash(conjunct) for conjunct in self.conjuncts)) 108 | ) 109 | 110 | def __repr__(self): 111 | conjunctions = ", ".join( 112 | [str(conjunct) for conjunct in self.conjuncts] 113 | ) 114 | return f"And({conjunctions})" 115 | 116 | def add(self, conjunct): 117 | Sentence.validate(conjunct) 118 | self.conjuncts.append(conjunct) 119 | 120 | def evaluate(self, model): 121 | return all(conjunct.evaluate(model) for conjunct in self.conjuncts) 122 | 123 | def formula(self): 124 | if len(self.conjuncts) == 1: 125 | return self.conjuncts[0].formula() 126 | return " ∧ ".join([Sentence.parenthesize(conjunct.formula()) 127 | for conjunct in self.conjuncts]) 128 | 129 | def symbols(self): 130 | return set.union(*[conjunct.symbols() for conjunct in self.conjuncts]) 131 | 132 | 133 | class Or(Sentence): 134 | def __init__(self, *disjuncts): 135 | for disjunct in disjuncts: 136 | Sentence.validate(disjunct) 137 | self.disjuncts = list(disjuncts) 138 | 139 | def __eq__(self, other): 140 | return isinstance(other, Or) and self.disjuncts == other.disjuncts 141 | 142 | def __hash__(self): 143 | return hash( 144 | ("or", tuple(hash(disjunct) for disjunct in self.disjuncts)) 145 | ) 146 | 147 | def __repr__(self): 148 | disjuncts = ", ".join([str(disjunct) for disjunct in self.disjuncts]) 149 | return f"Or({disjuncts})" 150 | 151 | def evaluate(self, model): 152 | return any(disjunct.evaluate(model) for disjunct in self.disjuncts) 153 | 154 | def formula(self): 155 | if len(self.disjuncts) == 1: 156 | return self.disjuncts[0].formula() 157 | return " ∨ ".join([Sentence.parenthesize(disjunct.formula()) 158 | for disjunct in self.disjuncts]) 159 | 160 | def symbols(self): 161 | return set.union(*[disjunct.symbols() for disjunct in self.disjuncts]) 162 | 163 | 164 | class Implication(Sentence): 165 | def __init__(self, antecedent, consequent): 166 | Sentence.validate(antecedent) 167 | Sentence.validate(consequent) 168 | self.antecedent = antecedent 169 | self.consequent = consequent 170 | 171 | def __eq__(self, other): 172 | return (isinstance(other, Implication) 173 | and self.antecedent == other.antecedent 174 | and self.consequent == other.consequent) 175 | 176 | def __hash__(self): 177 | return hash(("implies", hash(self.antecedent), hash(self.consequent))) 178 | 179 | def __repr__(self): 180 | return f"Implication({self.antecedent}, {self.consequent})" 181 | 182 | def evaluate(self, model): 183 | return ((not self.antecedent.evaluate(model)) 184 | or self.consequent.evaluate(model)) 185 | 186 | def formula(self): 187 | antecedent = Sentence.parenthesize(self.antecedent.formula()) 188 | consequent = Sentence.parenthesize(self.consequent.formula()) 189 | return f"{antecedent} => {consequent}" 190 | 191 | def symbols(self): 192 | return set.union(self.antecedent.symbols(), self.consequent.symbols()) 193 | 194 | 195 | class Biconditional(Sentence): 196 | def __init__(self, left, right): 197 | Sentence.validate(left) 198 | Sentence.validate(right) 199 | self.left = left 200 | self.right = right 201 | 202 | def __eq__(self, other): 203 | return (isinstance(other, Biconditional) 204 | and self.left == other.left 205 | and self.right == other.right) 206 | 207 | def __hash__(self): 208 | return hash(("biconditional", hash(self.left), hash(self.right))) 209 | 210 | def __repr__(self): 211 | return f"Biconditional({self.left}, {self.right})" 212 | 213 | def evaluate(self, model): 214 | return ((self.left.evaluate(model) 215 | and self.right.evaluate(model)) 216 | or (not self.left.evaluate(model) 217 | and not self.right.evaluate(model))) 218 | 219 | def formula(self): 220 | left = Sentence.parenthesize(str(self.left)) 221 | right = Sentence.parenthesize(str(self.right)) 222 | return f"{left} <=> {right}" 223 | 224 | def symbols(self): 225 | return set.union(self.left.symbols(), self.right.symbols()) 226 | 227 | 228 | def model_check(knowledge, query): 229 | """Checks if knowledge base entails query.""" 230 | 231 | def check_all(knowledge, query, symbols, model): 232 | """Checks if knowledge base entails query, given a particular model.""" 233 | 234 | # If model has an assignment for each symbol 235 | if not symbols: 236 | 237 | # If knowledge base is true in model, then query must also be true 238 | if knowledge.evaluate(model): 239 | return query.evaluate(model) 240 | return True 241 | else: 242 | 243 | # Choose one of the remaining unused symbols 244 | remaining = symbols.copy() 245 | p = remaining.pop() 246 | 247 | # Create a model where the symbol is true 248 | model_true = model.copy() 249 | model_true[p] = True 250 | 251 | # Create a model where the symbol is false 252 | model_false = model.copy() 253 | model_false[p] = False 254 | 255 | # Ensure entailment holds in both models 256 | return (check_all(knowledge, query, remaining, model_true) and 257 | check_all(knowledge, query, remaining, model_false)) 258 | 259 | # Get all symbols in both knowledge and query 260 | symbols = set.union(knowledge.symbols(), query.symbols()) 261 | 262 | # Check that knowledge entails query 263 | return check_all(knowledge, query, symbols, dict()) 264 | -------------------------------------------------------------------------------- /search/search_notes.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "metadata": {}, 7 | "source": [ 8 | "# Depth First Search" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": 10, 14 | "metadata": {}, 15 | "outputs": [], 16 | "source": [ 17 | "class Stack:\n", 18 | " def __init__(self):\n", 19 | " self.stack = []\n", 20 | "\n", 21 | " def push(self, value):\n", 22 | " self.stack.append(value)\n", 23 | "\n", 24 | " def pop(self):\n", 25 | " if len(self.stack) < 1:\n", 26 | " return None\n", 27 | " return self.stack.pop()\n", 28 | "\n", 29 | " def size(self):\n", 30 | " return len(self.stack)" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": 11, 36 | "metadata": {}, 37 | "outputs": [], 38 | "source": [ 39 | "def dfs(graph, start, end):\n", 40 | " visited = []\n", 41 | " stack = Stack()\n", 42 | " stack.push(start)\n", 43 | "\n", 44 | " while stack.size():\n", 45 | " node = stack.pop()\n", 46 | " if node not in visited:\n", 47 | " visited.append(node)\n", 48 | "\n", 49 | " if node == end:\n", 50 | " return visited\n", 51 | "\n", 52 | " for neighbor in graph[node]:\n", 53 | " stack.push(neighbor)\n", 54 | "\n", 55 | " return []" 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": 12, 61 | "metadata": {}, 62 | "outputs": [ 63 | { 64 | "name": "stdout", 65 | "output_type": "stream", 66 | "text": [ 67 | "['A', 'D', 'I', 'C', 'H', 'O', 'R']\n" 68 | ] 69 | } 70 | ], 71 | "source": [ 72 | "graph = {\n", 73 | " 'A': ['B', 'C', 'D'],\n", 74 | " 'B': ['A', 'E', 'F'],\n", 75 | " 'C': ['A', 'G', 'H'],\n", 76 | " 'D': ['A', 'I'],\n", 77 | " 'E': ['B', 'J'],\n", 78 | " 'F': ['B', 'K', 'L'],\n", 79 | " 'G': ['C', 'M'],\n", 80 | " 'H': ['C', 'N', 'O'],\n", 81 | " 'I': ['D'],\n", 82 | " 'J': ['E', 'P'],\n", 83 | " 'K': ['F'],\n", 84 | " 'L': ['F', 'Q'],\n", 85 | " 'M': ['G'],\n", 86 | " 'N': ['H'],\n", 87 | " 'O': ['H', 'R'],\n", 88 | " 'P': ['J'],\n", 89 | " 'Q': ['L'],\n", 90 | " 'R': ['O']\n", 91 | "}\n", 92 | "\n", 93 | "print(dfs(graph, 'A', 'R'))" 94 | ] 95 | }, 96 | { 97 | "attachments": {}, 98 | "cell_type": "markdown", 99 | "metadata": {}, 100 | "source": [ 101 | "# Breath First Search" 102 | ] 103 | }, 104 | { 105 | "cell_type": "code", 106 | "execution_count": 13, 107 | "metadata": {}, 108 | "outputs": [], 109 | "source": [ 110 | "class Queue(Stack):\n", 111 | " def pop(self):\n", 112 | " if len(self.stack) < 1:\n", 113 | " return None\n", 114 | " return self.stack.pop(0)" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": 14, 120 | "metadata": {}, 121 | "outputs": [], 122 | "source": [ 123 | "def bfs(graph, start, end):\n", 124 | " visited = []\n", 125 | " queue = Queue()\n", 126 | " queue.push(start)\n", 127 | "\n", 128 | " while queue.size():\n", 129 | " node = queue.pop()\n", 130 | " if node not in visited:\n", 131 | " visited.append(node)\n", 132 | "\n", 133 | " if node == end:\n", 134 | " return visited\n", 135 | "\n", 136 | " for neighbor in graph[node]:\n", 137 | " queue.push(neighbor)\n", 138 | "\n", 139 | " return []" 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": 15, 145 | "metadata": {}, 146 | "outputs": [ 147 | { 148 | "name": "stdout", 149 | "output_type": "stream", 150 | "text": [ 151 | "['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R']\n" 152 | ] 153 | } 154 | ], 155 | "source": [ 156 | "print(bfs(graph, 'A', 'R')) " 157 | ] 158 | }, 159 | { 160 | "attachments": {}, 161 | "cell_type": "markdown", 162 | "metadata": {}, 163 | "source": [ 164 | "# A-Star Search" 165 | ] 166 | }, 167 | { 168 | "cell_type": "code", 169 | "execution_count": 22, 170 | "metadata": {}, 171 | "outputs": [], 172 | "source": [ 173 | "class Node():\n", 174 | " def __init__(self, parent=None, position=None):\n", 175 | " self.parent = parent\n", 176 | " self.position = position\n", 177 | "\n", 178 | " self.g = 0\n", 179 | " self.h = 0\n", 180 | " self.f = 0\n", 181 | "\n", 182 | " def __eq__(self, other):\n", 183 | " return self.position == other.position" 184 | ] 185 | }, 186 | { 187 | "cell_type": "code", 188 | "execution_count": 23, 189 | "metadata": {}, 190 | "outputs": [], 191 | "source": [ 192 | "def astar(maze, start, end):\n", 193 | " start_node = Node(None, start)\n", 194 | " end_node = Node(None, end)\n", 195 | "\n", 196 | " open_list = []\n", 197 | " closed_list = []\n", 198 | "\n", 199 | " open_list.append(start_node)\n", 200 | "\n", 201 | " while len(open_list) > 0:\n", 202 | " current_node = open_list[0]\n", 203 | " current_index = 0\n", 204 | " for index, item in enumerate(open_list):\n", 205 | " if item.f < current_node.f:\n", 206 | " current_node = item\n", 207 | " current_index = index\n", 208 | "\n", 209 | " open_list.pop(current_index)\n", 210 | " closed_list.append(current_node)\n", 211 | "\n", 212 | " if current_node == end_node:\n", 213 | " path = []\n", 214 | " current = current_node\n", 215 | " while current is not None:\n", 216 | " path.append(current.position)\n", 217 | " current = current.parent\n", 218 | " return path[::-1]\n", 219 | "\n", 220 | " children = []\n", 221 | " for new_position in [(0, -1), (0, 1), (-1, 0), (1, 0), (-1, -1), (-1, 1), (1, -1), (1, 1)]:\n", 222 | " node_position = (current_node.position[0] + new_position[0], current_node.position[1] + new_position[1])\n", 223 | "\n", 224 | " if node_position[0] > (len(maze) - 1) or node_position[0] < 0 or node_position[1] > (len(maze[len(maze)-1]) -1) or node_position[1] < 0:\n", 225 | " continue\n", 226 | "\n", 227 | " if maze[node_position[0]][node_position[1]] != 0:\n", 228 | " continue\n", 229 | "\n", 230 | " new_node = Node(current_node, node_position)\n", 231 | "\n", 232 | " children.append(new_node)\n", 233 | "\n", 234 | " for child in children:\n", 235 | " for closed_child in closed_list:\n", 236 | " if child == closed_child:\n", 237 | " continue\n", 238 | "\n", 239 | " child.g = current_node.g + 1\n", 240 | " child.h = ((child.position[0] - end_node.position[0]) ** 2) + ((child.position[1] - end_node.position[1]) ** 2)\n", 241 | " child.f = child.g + child.h\n", 242 | "\n", 243 | " for open_node in open_list:\n", 244 | " if child == open_node and child.g > open_node.g:\n", 245 | " continue\n", 246 | "\n", 247 | " open_list.append(child)" 248 | ] 249 | }, 250 | { 251 | "cell_type": "code", 252 | "execution_count": 25, 253 | "metadata": {}, 254 | "outputs": [ 255 | { 256 | "name": "stdout", 257 | "output_type": "stream", 258 | "text": [ 259 | "[(0, 0), (0, 1), (1, 2), (1, 3), (2, 4), (3, 4), (4, 5)]\n" 260 | ] 261 | } 262 | ], 263 | "source": [ 264 | "maze = [[0, 0, 0, 0, 1, 0],\n", 265 | " [1, 1, 0, 0, 1, 0],\n", 266 | " [0, 0, 0, 1, 0, 0],\n", 267 | " [0, 1, 1, 0, 0, 1],\n", 268 | " [0, 0, 0, 0, 0, 0]]\n", 269 | "\n", 270 | "start = (0, 0)\n", 271 | "end = (4, 5)\n", 272 | "\n", 273 | "path = astar(maze, start, end)\n", 274 | "print(path)" 275 | ] 276 | }, 277 | { 278 | "cell_type": "code", 279 | "execution_count": 15, 280 | "metadata": {}, 281 | "outputs": [ 282 | { 283 | "name": "stdout", 284 | "output_type": "stream", 285 | "text": [ 286 | "-1\n" 287 | ] 288 | } 289 | ], 290 | "source": [] 291 | }, 292 | { 293 | "cell_type": "code", 294 | "execution_count": 16, 295 | "metadata": {}, 296 | "outputs": [], 297 | "source": [ 298 | "a = 0" 299 | ] 300 | }, 301 | { 302 | "cell_type": "code", 303 | "execution_count": 17, 304 | "metadata": {}, 305 | "outputs": [ 306 | { 307 | "data": { 308 | "text/plain": [ 309 | "-1" 310 | ] 311 | }, 312 | "execution_count": 17, 313 | "metadata": {}, 314 | "output_type": "execute_result" 315 | } 316 | ], 317 | "source": [ 318 | "~a" 319 | ] 320 | }, 321 | { 322 | "cell_type": "code", 323 | "execution_count": null, 324 | "metadata": {}, 325 | "outputs": [], 326 | "source": [] 327 | } 328 | ], 329 | "metadata": { 330 | "kernelspec": { 331 | "display_name": "aai", 332 | "language": "python", 333 | "name": "python3" 334 | }, 335 | "language_info": { 336 | "codemirror_mode": { 337 | "name": "ipython", 338 | "version": 3 339 | }, 340 | "file_extension": ".py", 341 | "mimetype": "text/x-python", 342 | "name": "python", 343 | "nbconvert_exporter": "python", 344 | "pygments_lexer": "ipython3", 345 | "version": "3.10.14" 346 | }, 347 | "orig_nbformat": 4, 348 | "vscode": { 349 | "interpreter": { 350 | "hash": "f13c07b14fad073544cf30f313c5e372bd0c64a011009a03bcc2bc0bcba6863d" 351 | } 352 | } 353 | }, 354 | "nbformat": 4, 355 | "nbformat_minor": 2 356 | } 357 | -------------------------------------------------------------------------------- /search/search_algo_demo/maze.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Tuple 2 | from cell import Cell 3 | from graphics import Window 4 | from time import sleep 5 | from queue import PriorityQueue 6 | import random 7 | 8 | 9 | class Maze: 10 | def __init__( 11 | self, 12 | x: int, 13 | y: int, 14 | num_rows: int, 15 | num_cols: int, 16 | cell_size_x: int, 17 | cell_size_y: int, 18 | win: Window | None = None, 19 | seed: int | None = None, 20 | ): 21 | self._x = x 22 | self._y = y 23 | self._num_rows = num_rows 24 | self._num_cols = num_cols 25 | self._cell_size_x = cell_size_x 26 | self._cell_size_y = cell_size_y 27 | self._win = win 28 | self._cells: List[List[Cell]] = [] 29 | self._create_cells() 30 | self._break_entrance_and_exit() 31 | if seed is not None: 32 | random.seed(seed) 33 | self._break_walls_r(0, 0) 34 | self._write_enter_exit() 35 | self._reset_cells_visited() 36 | 37 | def _create_cells(self): 38 | for i in range(0, self._num_rows): 39 | self._cells.append([]) 40 | for j in range(0, self._num_cols): 41 | self._cells[i].append(Cell(self._win)) 42 | 43 | for i in range(0, self._num_rows): 44 | for j in range(0, self._num_cols): 45 | self._draw_cell(i, j, 0.001) 46 | 47 | def _draw_cell(self, i: int, j: int, animation_speed=0.05): 48 | if self._win is None: 49 | return 50 | x1 = self._x + self._cell_size_x * (j) 51 | y1 = self._y + self._cell_size_y * (i) 52 | x2 = self._x + self._cell_size_x * (j+1) 53 | y2 = self._y + self._cell_size_y * (i+1) 54 | self._cells[i][j].draw(x1, y1, x2, y2) 55 | self._animate(animation_speed) 56 | 57 | def _animate(self, animation_speed=0.05): 58 | if self._win is None: 59 | return 60 | self._win.redraw() 61 | sleep(animation_speed) 62 | 63 | def _break_entrance_and_exit(self): 64 | self._cells[0][0].has_top_wall = False 65 | self._cells[self._num_rows-1][self._num_cols-1].has_bottom_wall = False 66 | self._draw_cell(0, 0) 67 | self._draw_cell(self._num_rows-1, self._num_cols-1) 68 | 69 | def _break_walls_r(self, i, j): 70 | self._cells[i][j].visited = True 71 | while True: 72 | row = [-1, 0, 1, 0] 73 | col = [0, -1, 0, 1] 74 | possible_directions: List[List[int]] = [] 75 | 76 | for k in range(0, 4): 77 | if ( 78 | row[k] + i >= 0 79 | and row[k] + i < self._num_rows 80 | and col[k] + j >= 0 81 | and col[k] + j < self._num_cols 82 | and not self._cells[row[k] + i][col[k] + j].visited 83 | ): 84 | possible_directions.append([row[k]+i, col[k] + j]) 85 | 86 | if len(possible_directions) == 0: 87 | self._draw_cell(i, j, 0.001) 88 | return 89 | 90 | direction = possible_directions[int( 91 | random.random() * len(possible_directions) 92 | )] 93 | 94 | if direction[0] > i: 95 | self._cells[i][j].has_bottom_wall = False 96 | self._cells[direction[0]][j].has_top_wall = False 97 | elif direction[0] < i: 98 | self._cells[i][j].has_top_wall = False 99 | self._cells[direction[0]][j].has_bottom_wall = False 100 | elif direction[1] < j: 101 | self._cells[i][j].has_left_wall = False 102 | self._cells[i][direction[1]].has_right_wall = False 103 | elif direction[1] > j: 104 | self._cells[i][j].has_right_wall = False 105 | self._cells[i][direction[1]].has_left_wall = False 106 | 107 | self._break_walls_r(direction[0], direction[1]) 108 | 109 | def _write_enter_exit(self): 110 | if self._win is None: 111 | return 112 | self._win.write_text( 113 | self._x + (self._cell_size_x//2), 114 | self._y, 115 | "CL 701" 116 | ) 117 | self._win.write_text( 118 | self._x + self._cell_size_x * 119 | self._num_cols - (self._cell_size_x//2), 120 | self._y + self._cell_size_y * self._num_rows, 121 | "CL" 122 | ) 123 | 124 | def _reset_cells_visited(self): 125 | for i in range(0, self._num_rows): 126 | for j in range(0, self._num_cols): 127 | self._cells[i][j].visited = False 128 | 129 | def __can_move(self, i, j, x, y): 130 | if ( 131 | x > i 132 | and not self._cells[i][j].has_bottom_wall 133 | and not self._cells[x][j].has_top_wall 134 | ): 135 | return True 136 | elif ( 137 | x < i 138 | and not self._cells[i][j].has_top_wall 139 | and not self._cells[x][j].has_bottom_wall 140 | ): 141 | return True 142 | elif ( 143 | y < j 144 | and not self._cells[i][j].has_left_wall 145 | and not self._cells[i][y].has_right_wall 146 | ): 147 | return True 148 | elif ( 149 | y > j 150 | and not self._cells[i][j].has_right_wall 151 | and not self._cells[i][y].has_left_wall 152 | ): 153 | return True 154 | else: 155 | return False 156 | 157 | def _solve_r(self, i: int, j: int): 158 | if i == self._num_rows - 1 and j == self._num_cols - 1: 159 | return True 160 | self._animate() 161 | self._cells[i][j].visited = True 162 | 163 | dr = [-1, 0, 1, 0] 164 | dc = [0, -1, 0, 1] 165 | for k in range(0, 4): 166 | if ( 167 | dr[k] + i >= 0 168 | and dr[k] + i < self._num_rows 169 | and dc[k] + j >= 0 170 | and dc[k] + j < self._num_cols 171 | and not self._cells[dr[k] + i][dc[k] + j].visited 172 | and self.__can_move(i, j, i+dr[k], j+dc[k]) 173 | ): 174 | self._cells[i][j].draw_move(self._cells[dr[k]+i][dc[k]+j]) 175 | if self._solve_r(dr[k]+i, dc[k]+j): 176 | return True 177 | else: 178 | self._cells[i][j].draw_move( 179 | self._cells[dr[k]+i][dc[k]+j], 180 | undo=True 181 | ) 182 | self._animate(0.08) 183 | return False 184 | 185 | def solve(self): 186 | return self._solve_r(0, 0) 187 | 188 | def _build_path( 189 | self, 190 | path_map: Dict, 191 | start_cell: Tuple[int, int] = (0, 0), 192 | end_cell: Tuple[int, int] | None = None 193 | ): 194 | if end_cell is None: 195 | end_cell = (self._num_rows-1, self._num_cols-1) 196 | cell = end_cell 197 | path: List[Tuple[int, int]] = [cell] 198 | while cell != start_cell: 199 | path.append(path_map[cell]) 200 | cell = path_map[cell] 201 | return path[::-1] 202 | 203 | def _draw_path(self, path: List[Tuple[int, int]]): 204 | for i in range(len(path)-1): 205 | self._cells[path[i+1][0]][path[i+1][1]].draw_move( 206 | self._cells[path[i][0]][path[i][1]] 207 | ) 208 | self._animate(0.015) 209 | 210 | def _solve_via_bfs(self): 211 | path_map = {} 212 | que: List[Tuple[int, int]] = [] 213 | 214 | que.append((0, 0)) 215 | while len(que) > 0: 216 | i, j = que.pop(0) 217 | 218 | if i == self._num_rows - 1 and j == self._num_cols - 1: 219 | path = self._build_path(path_map) 220 | self._draw_path(path) 221 | return True 222 | 223 | self._cells[i][j].visited = True 224 | 225 | dr = [-1, 0, 1, 0] 226 | dc = [0, -1, 0, 1] 227 | for k in range(4): 228 | nr = dr[k] + i 229 | nc = dc[k] + j 230 | if ( 231 | nr >= 0 232 | and nr < self._num_rows 233 | and nc >= 0 234 | and nc < self._num_cols 235 | and not self._cells[nr][nc].visited 236 | and self.__can_move(i, j, nr, nc) 237 | ): 238 | next_cell = (nr, nc) 239 | que.append(next_cell) 240 | path_map[next_cell] = (i, j) 241 | self._cells[i][j].draw_move( 242 | self._cells[next_cell[0]][next_cell[1]], 243 | undo=True 244 | ) 245 | self._animate(0.1) 246 | return False 247 | 248 | def solve_bfs(self): 249 | return self._solve_via_bfs() 250 | 251 | def _solve_via_a_star(self): 252 | start_cell = (0, 0) 253 | end_cell = (self._num_rows-1, self._num_cols-1) 254 | 255 | def _h(cell1: Tuple[int, int], cell2: Tuple[int, int] = end_cell): 256 | return abs(cell1[0] - cell2[0]) + abs(cell1[1] - cell2[1]) 257 | 258 | cells = [] 259 | for i in range(self._num_rows): 260 | for j in range(self._num_cols): 261 | cells.append((i, j)) 262 | 263 | g_score = {cell: float('inf') for cell in cells} 264 | g_score[start_cell] = 0 265 | f_score = {cell: float('inf') for cell in cells} 266 | f_score[start_cell] = _h(start_cell) 267 | 268 | path_map = {} 269 | 270 | pq = PriorityQueue() 271 | pq.put((f_score[start_cell], f_score[start_cell], start_cell)) 272 | 273 | while not pq.empty(): 274 | current_cell = pq.get()[2] 275 | 276 | if current_cell == end_cell: 277 | path = self._build_path(path_map) 278 | self._draw_path(path) 279 | return True 280 | 281 | dr = [-1, 0, 1, 0] 282 | dc = [0, -1, 0, 1] 283 | for k in range(4): 284 | nr = dr[k] + current_cell[0] 285 | nc = dc[k] + current_cell[1] 286 | if ( 287 | nr >= 0 288 | and nr < self._num_rows 289 | and nc >= 0 290 | and nc < self._num_cols 291 | and self.__can_move( 292 | current_cell[0], current_cell[1], nr, nc) 293 | ): 294 | new_cell = (nr, nc) 295 | new_g_score = g_score[current_cell] + 1 296 | new_f_scroe = new_g_score + _h(new_cell) 297 | if new_f_scroe < f_score[new_cell]: 298 | f_score[new_cell] = new_f_scroe 299 | g_score[new_cell] = new_g_score 300 | pq.put((new_f_scroe, _h(new_cell), new_cell)) 301 | path_map[new_cell] = current_cell 302 | self._cells[new_cell[0]][new_cell[1]].draw_move( 303 | self._cells[current_cell[0]][current_cell[1]], 304 | undo=True 305 | ) 306 | self._animate(0.08) 307 | return False 308 | 309 | def solve_a_star(self): 310 | return self._solve_via_a_star() 311 | -------------------------------------------------------------------------------- /oops/oop_notes.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "metadata": {}, 7 | "source": [ 8 | "# Basics" 9 | ] 10 | }, 11 | { 12 | "attachments": {}, 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "First lets write a simple programm to extract student name and their house" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": 3, 22 | "metadata": {}, 23 | "outputs": [ 24 | { 25 | "name": "stdout", 26 | "output_type": "stream", 27 | "text": [ 28 | "Harry from Gryffindor\n" 29 | ] 30 | } 31 | ], 32 | "source": [ 33 | "name = input(\"Name: \")\n", 34 | "house = input(\"House: \")\n", 35 | "print(f\"{name} from {house}\")" 36 | ] 37 | }, 38 | { 39 | "attachments": {}, 40 | "cell_type": "markdown", 41 | "metadata": {}, 42 | "source": [ 43 | "Notice that this program follows a procedural, step-by-step paradigm. Lets make a function to make it simpler.\n", 44 | "\n", 45 | "\n", 46 | "Further, notice how the final lines of the code above tell the compiler to run the main function." 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": null, 52 | "metadata": {}, 53 | "outputs": [], 54 | "source": [ 55 | "def main():\n", 56 | " name, house = get_student()\n", 57 | " print(f\"{name} from {house}\")\n", 58 | "\n", 59 | "def get_student():\n", 60 | " name = input(\"Name: \")\n", 61 | " house = input(\"House: \")\n", 62 | " return name, house\n", 63 | "\n", 64 | "if __name__ == \"__main__\":\n", 65 | " main()" 66 | ] 67 | }, 68 | { 69 | "attachments": {}, 70 | "cell_type": "markdown", 71 | "metadata": {}, 72 | "source": [ 73 | "Explain about tuples and how we can use this function with [0] and [1] by returning (name,house)" 74 | ] 75 | }, 76 | { 77 | "attachments": {}, 78 | "cell_type": "markdown", 79 | "metadata": {}, 80 | "source": [ 81 | "Let this example fail for Padma's Example" 82 | ] 83 | }, 84 | { 85 | "cell_type": "code", 86 | "execution_count": 2, 87 | "metadata": {}, 88 | "outputs": [ 89 | { 90 | "name": "stdout", 91 | "output_type": "stream", 92 | "text": [ 93 | "Harry from Gryffindor\n" 94 | ] 95 | } 96 | ], 97 | "source": [ 98 | "def main():\n", 99 | " student = get_student()\n", 100 | " if student[0] == \"Padma\":\n", 101 | " student[1] = \"Ravenclaw\"\n", 102 | " print(f\"{student[0]} from {student[1]}\")\n", 103 | "\n", 104 | "def get_student():\n", 105 | " name = input(\"Name: \")\n", 106 | " house = input(\"House: \")\n", 107 | " return name, house\n", 108 | "\n", 109 | "if __name__ == \"__main__\":\n", 110 | " main()" 111 | ] 112 | }, 113 | { 114 | "attachments": {}, 115 | "cell_type": "markdown", 116 | "metadata": {}, 117 | "source": [ 118 | "Explain why this works" 119 | ] 120 | }, 121 | { 122 | "cell_type": "code", 123 | "execution_count": 5, 124 | "metadata": {}, 125 | "outputs": [ 126 | { 127 | "name": "stdout", 128 | "output_type": "stream", 129 | "text": [ 130 | "Padma from Ravenclaw\n" 131 | ] 132 | } 133 | ], 134 | "source": [ 135 | "def main():\n", 136 | " student = get_student()\n", 137 | " if student[0] == \"Padma\":\n", 138 | " student[1] = \"Ravenclaw\"\n", 139 | " print(f\"{student[0]} from {student[1]}\")\n", 140 | "\n", 141 | "def get_student():\n", 142 | " name = input(\"Name: \")\n", 143 | " house = input(\"House: \")\n", 144 | " return [name, house]\n", 145 | "\n", 146 | "if __name__ == \"__main__\":\n", 147 | " main()" 148 | ] 149 | }, 150 | { 151 | "attachments": {}, 152 | "cell_type": "markdown", 153 | "metadata": {}, 154 | "source": [ 155 | "Explain the concept of Dictinaries " 156 | ] 157 | }, 158 | { 159 | "cell_type": "code", 160 | "execution_count": 7, 161 | "metadata": {}, 162 | "outputs": [ 163 | { 164 | "name": "stdout", 165 | "output_type": "stream", 166 | "text": [ 167 | "Padma from Ravenclaw\n" 168 | ] 169 | } 170 | ], 171 | "source": [ 172 | "def main():\n", 173 | " student = get_student()\n", 174 | " if student[\"name\"] == \"Padma\":\n", 175 | " student[\"house\"] = \"Ravenclaw\"\n", 176 | " print(f\"{student['name']} from {student['house']}\")\n", 177 | "\n", 178 | "\n", 179 | "def get_student():\n", 180 | " name = input(\"Name: \")\n", 181 | " house = input(\"House: \")\n", 182 | " return {\"name\": name, \"house\": house}\n", 183 | "\n", 184 | "\n", 185 | "if __name__ == \"__main__\":\n", 186 | " main()" 187 | ] 188 | }, 189 | { 190 | "attachments": {}, 191 | "cell_type": "markdown", 192 | "metadata": {}, 193 | "source": [ 194 | "Classes are a way by which, in object-oriented programming, we can create our own type of data and give them names.\n", 195 | "\n", 196 | "A class is like a mold for a type of data – where we can invent our own data type and give them a name.\n", 197 | "\n", 198 | "We can modify our code as follows to implement our own class called Student:" 199 | ] 200 | }, 201 | { 202 | "cell_type": "code", 203 | "execution_count": 8, 204 | "metadata": {}, 205 | "outputs": [ 206 | { 207 | "name": "stdout", 208 | "output_type": "stream", 209 | "text": [ 210 | "Harry from Fd\n" 211 | ] 212 | } 213 | ], 214 | "source": [ 215 | "class Student:\n", 216 | " ...\n", 217 | "\n", 218 | "def main():\n", 219 | " student = get_student()\n", 220 | " print(f\"{student.name} from {student.house}\")\n", 221 | "\n", 222 | "def get_student():\n", 223 | " student = Student()\n", 224 | " student.name = input(\"Name: \")\n", 225 | " student.house = input(\"House: \")\n", 226 | " return student\n", 227 | "\n", 228 | "if __name__ == \"__main__\":\n", 229 | " main()" 230 | ] 231 | }, 232 | { 233 | "attachments": {}, 234 | "cell_type": "markdown", 235 | "metadata": {}, 236 | "source": [ 237 | "Notice by convention that Student is capitalized. Further, notice the ... simply means that we will later return to finish that portion of our code. Further, notice that in get_student, we can create a student of class Student using the syntax student = Student(). Further, notice that we utilize “dot notation” to access attributes of this variable student of class Student.\n", 238 | "\n", 239 | "Any time you create a class and you utilize that blueprint to create something, you create what is called an “object” or an “instance”. In the case of our code, student is an object." 240 | ] 241 | }, 242 | { 243 | "cell_type": "code", 244 | "execution_count": null, 245 | "metadata": {}, 246 | "outputs": [], 247 | "source": [ 248 | "class Student:\n", 249 | " def __init__(self, name, house):\n", 250 | " self.name = name\n", 251 | " self.house = house\n", 252 | "\n", 253 | "def main():\n", 254 | " student = get_student()\n", 255 | " print(f\"{student.name} from {student.house}\")\n", 256 | "\n", 257 | "def get_student():\n", 258 | " name = input(\"Name: \")\n", 259 | " house = input(\"House: \")\n", 260 | " student = Student(name, house)\n", 261 | " return student\n", 262 | "\n", 263 | "if __name__ == \"__main__\":\n", 264 | " main()" 265 | ] 266 | }, 267 | { 268 | "attachments": {}, 269 | "cell_type": "markdown", 270 | "metadata": {}, 271 | "source": [ 272 | "Notice that within Student, we standardize the attributes of this class. We can create a function within class Student, called a “method”, that determines the behavior of an object of class Student. Within this function, it takes the name and house passed to it and assigns these variables to this object. Further, notice how the constructor student = Student(name, house) calls this function within the Student class and creates a student. self refers to the current object that was just created" 273 | ] 274 | }, 275 | { 276 | "cell_type": "code", 277 | "execution_count": null, 278 | "metadata": {}, 279 | "outputs": [], 280 | "source": [ 281 | "class Student:\n", 282 | " def __init__(self, name, house):\n", 283 | " if not name:\n", 284 | " raise ValueError(\"Missing name\")\n", 285 | " if house not in [\"Gryffindor\", \"Hufflepuff\", \"Ravenclaw\", \"Slytherin\"]:\n", 286 | " raise ValueError(\"Invalid house\")\n", 287 | " self.name = name\n", 288 | " self.house = house\n", 289 | "\n", 290 | "def main():\n", 291 | " student = get_student()\n", 292 | " print(f\"{student.name} from {student.house}\")\n", 293 | "\n", 294 | "def get_student():\n", 295 | " name = input(\"Name: \")\n", 296 | " house = input(\"House: \")\n", 297 | " return Student(name, house)\n", 298 | "\n", 299 | "if __name__ == \"__main__\":\n", 300 | " main()" 301 | ] 302 | }, 303 | { 304 | "cell_type": "code", 305 | "execution_count": 4, 306 | "metadata": {}, 307 | "outputs": [ 308 | { 309 | "name": "stdout", 310 | "output_type": "stream", 311 | "text": [ 312 | "Harry from Gryffindor\n" 313 | ] 314 | } 315 | ], 316 | "source": [ 317 | "class Student:\n", 318 | " def __init__(self, name, house):\n", 319 | " if not name:\n", 320 | " raise ValueError(\"Missing name\")\n", 321 | " if house not in [\"Gryffindor\", \"Hufflepuff\", \"Ravenclaw\", \"Slytherin\"]:\n", 322 | " raise ValueError(\"Invalid house\")\n", 323 | " self.name = name\n", 324 | " self.house = house\n", 325 | "\n", 326 | " def __str__(self) -> str:\n", 327 | " return f\"{self.name} from {self.house}\"\n", 328 | "\n", 329 | "def main():\n", 330 | " student = get_student()\n", 331 | " print(student)\n", 332 | "\n", 333 | "def get_student():\n", 334 | " name = input(\"Name: \")\n", 335 | " house = input(\"House: \")\n", 336 | " return Student(name, house)\n", 337 | "\n", 338 | "if __name__ == \"__main__\":\n", 339 | " main()" 340 | ] 341 | }, 342 | { 343 | "attachments": {}, 344 | "cell_type": "markdown", 345 | "metadata": {}, 346 | "source": [ 347 | "# Class Methods\n", 348 | "hat.py" 349 | ] 350 | }, 351 | { 352 | "cell_type": "code", 353 | "execution_count": 5, 354 | "metadata": {}, 355 | "outputs": [ 356 | { 357 | "name": "stdout", 358 | "output_type": "stream", 359 | "text": [ 360 | "EXPECTO PATRONUM\n", 361 | "🐴\n" 362 | ] 363 | } 364 | ], 365 | "source": [ 366 | "class Student:\n", 367 | " def __init__(self, name, house, patronous):\n", 368 | " if not name:\n", 369 | " raise ValueError(\"Missing name\")\n", 370 | " if house not in [\"Gryffindor\", \"Hufflepuff\", \"Ravenclaw\", \"Slytherin\"]:\n", 371 | " raise ValueError(\"Invalid house\")\n", 372 | " self.name = name\n", 373 | " self.house = house\n", 374 | " self.patronous = patronous\n", 375 | "\n", 376 | " def __str__(self):\n", 377 | " return f\"{self.name} from {self.house}\"\n", 378 | " \n", 379 | " def expecto_patronum(self):\n", 380 | " if self.patronous == \"Stag\":\n", 381 | " return \"🐴\"\n", 382 | " elif self.patronous == \"Dog\":\n", 383 | " return \"🐶\"\n", 384 | " else:\n", 385 | " return \"🦜\"\n", 386 | "\n", 387 | "def main():\n", 388 | " student = get_student()\n", 389 | " print(\"EXPECTO PATRONUM\")\n", 390 | " print(student.expecto_patronum())\n", 391 | "\n", 392 | "def get_student():\n", 393 | " name = input(\"Name: \")\n", 394 | " house = input(\"House: \")\n", 395 | " patronous = input(\"Patronous: \")\n", 396 | " return Student(name, house, patronous)\n", 397 | "\n", 398 | "if __name__ == \"__main__\":\n", 399 | " main()" 400 | ] 401 | }, 402 | { 403 | "cell_type": "code", 404 | "execution_count": 6, 405 | "metadata": {}, 406 | "outputs": [ 407 | { 408 | "name": "stdout", 409 | "output_type": "stream", 410 | "text": [ 411 | "Harry is in Ravenclaw\n" 412 | ] 413 | } 414 | ], 415 | "source": [ 416 | "import random\n", 417 | "\n", 418 | "class Hat:\n", 419 | "\n", 420 | " houses = [\"Gryffindor\", \"Hufflepuff\", \"Ravenclaw\", \"Slytherin\"]\n", 421 | "\n", 422 | " @classmethod\n", 423 | " def sort(cls, name):\n", 424 | " print(name, \"is in\", random.choice(cls.houses))\n", 425 | "\n", 426 | "Hat.sort(\"Harry\")" 427 | ] 428 | }, 429 | { 430 | "attachments": {}, 431 | "cell_type": "markdown", 432 | "metadata": {}, 433 | "source": [ 434 | "Notice how the __init__ method is removed because we don’t need to instantiate a hat anywhere in our code. self, therefore, is no longer relevant and is removed. We specify this sort as a @classmethod, replacing self with cls. Finally, notice how Hat is capitalized by convention near the end of this code, because this is the name of our class." 435 | ] 436 | }, 437 | { 438 | "attachments": {}, 439 | "cell_type": "markdown", 440 | "metadata": {}, 441 | "source": [ 442 | "# Inheritence\n", 443 | "wizard.py" 444 | ] 445 | }, 446 | { 447 | "cell_type": "code", 448 | "execution_count": 7, 449 | "metadata": {}, 450 | "outputs": [ 451 | { 452 | "name": "stdout", 453 | "output_type": "stream", 454 | "text": [ 455 | "Khatam\n" 456 | ] 457 | } 458 | ], 459 | "source": [ 460 | "class Wizard:\n", 461 | " def __init__(self, name):\n", 462 | " if not name:\n", 463 | " raise ValueError(\"Missing name\")\n", 464 | " self.name = name\n", 465 | "\n", 466 | " ...\n", 467 | "\n", 468 | "class Student(Wizard):\n", 469 | " def __init__(self, name, house):\n", 470 | " super().__init__(name)\n", 471 | " self.house = house\n", 472 | "\n", 473 | " ...\n", 474 | "\n", 475 | "class Professor(Wizard):\n", 476 | " def __init__(self, name, subject):\n", 477 | " super().__init__(name)\n", 478 | " self.subject = subject\n", 479 | "\n", 480 | " ...\n", 481 | "\n", 482 | "student = Student(\"Harry\", \"Gryffindor\")\n", 483 | "professor = Professor(\"Severus\", \"Defense Against the Dark Arts\")\n", 484 | "wizard = Wizard(\"Albus\")\n", 485 | "print(\"Khatam\")" 486 | ] 487 | }, 488 | { 489 | "attachments": {}, 490 | "cell_type": "markdown", 491 | "metadata": {}, 492 | "source": [ 493 | "Inheritance is, perhaps, the most powerful feature of object-oriented programming.\n", 494 | "\n", 495 | "It just so happens that you can create a class that “inherits” methods, variables, and attributes from another class.\n", 496 | "\n", 497 | "Notice that there is a class above called Wizard and a class called Student. Further, notice that there is a class called Professor. Both students and professors have names. Also, both students and professors are wizards. Therefore, both Student and Professor inherit the characteristics of Wizard. Within the “child” class Student, Student can inherit from the “parent” or “super” class Wizard as the line super().__init__(name) runs the init method of Wizard. Finally, notice that the last lines of this code create a wizard called Albus, a student called Harry, and so on." 498 | ] 499 | } 500 | ], 501 | "metadata": { 502 | "kernelspec": { 503 | "display_name": "aai", 504 | "language": "python", 505 | "name": "python3" 506 | }, 507 | "language_info": { 508 | "codemirror_mode": { 509 | "name": "ipython", 510 | "version": 3 511 | }, 512 | "file_extension": ".py", 513 | "mimetype": "text/x-python", 514 | "name": "python", 515 | "nbconvert_exporter": "python", 516 | "pygments_lexer": "ipython3", 517 | "version": "3.10.14" 518 | }, 519 | "vscode": { 520 | "interpreter": { 521 | "hash": "f13c07b14fad073544cf30f313c5e372bd0c64a011009a03bcc2bc0bcba6863d" 522 | } 523 | } 524 | }, 525 | "nbformat": 4, 526 | "nbformat_minor": 2 527 | } 528 | --------------------------------------------------------------------------------