From 0aa80053b3bc1f590be2882c72786ed5e847a7a1 Mon Sep 17 00:00:00 2001 From: mathlete1618 Date: Mon, 17 Feb 2025 12:55:22 -0600 Subject: [PATCH 1/5] Lab 2 Solution --- Labs/Lab.2/lab2_sol.ipynb | 711 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 711 insertions(+) create mode 100644 Labs/Lab.2/lab2_sol.ipynb diff --git a/Labs/Lab.2/lab2_sol.ipynb b/Labs/Lab.2/lab2_sol.ipynb new file mode 100644 index 000000000..2cc7c5c4c --- /dev/null +++ b/Labs/Lab.2/lab2_sol.ipynb @@ -0,0 +1,711 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#Lab 2- Tic Tac Toe\n", + "import random" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Exercise 1: Write a function that creates an n by n matrix (of list of lists) which will represent the state of a Tic Tac Toe game. Let 0, 1, and 2 represent empty, \"X\", and \"O\", respectively." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "import random\n", + "import string" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0, 0, 0]\n", + "[0, 0, 0]\n", + "[0, 0, 0]\n" + ] + } + ], + "source": [ + "def create_board(n, value = 0):\n", + " return [ [value for _ in range(n)] for _ in range(n)]\n", + "\n", + "standard_board = create_board(3, value = 0)\n", + "\n", + "for row in standard_board:\n", + " print(row)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Exercise 2: Write a function that takes 2 integers n and m as input and draws a n by m game board. For example the following is a 3x3 board:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0, 0, 0]\n", + "[0, 0, 0]\n", + "[0, 0, 0]\n" + ] + } + ], + "source": [ + "def game_board(value = 0):\n", + " n = int(input(\"Please enter your value for n: \"))\n", + " m = int(input(\"Please enter your value for m: \"))\n", + " return [ [value for _ in range(n)] for _ in range(m)]\n", + "\n", + "my_board = game_board()\n", + "\n", + "for row in my_board:\n", + " print(row)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Exercise 3: Modify exercise 2, so that it takes a matrix of the form from exercise 1 and draws a tic-tac-tie board with \"X\"s and \"O\"s." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['O', 'X', 'O']\n", + "['X', 'O', 'X']\n", + "['O', 'X', 'O']\n" + ] + } + ], + "source": [ + "def game_board(value = [\"O\", \"X\"]):\n", + " n = int(input(\"Please enter your value for n: \"))\n", + " m = int(input(\"Please enter your value for m: \"))\n", + " return [[value[(i * m + j) % len(value)] for j in range(m)] for i in range(n)]\n", + "\n", + "my_board = game_board()\n", + "\n", + "for row in my_board:\n", + " print(row)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Exercise 4: Write a function that takes a n by n matrix representing a tic-tac-toe game, and returns -1, 0, 1, or 2 indicating the game is incomplete, the game is a draw, player 1 has won, or player 2 has won, respectively. Here are some example inputs you can use to test your code:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "def check_game_board(board):\n", + " n = len(board) # setting n equal to the dimensions of the matrix.\n", + " for i in range(n):\n", + " if all(board[i][0] != 0 and board[i][j] == board[i][0] for j in range(n)): # Check the rows\n", + " return board[i][0]\n", + " if all(board[0][i] != 0 and board[j][i] == board[0][i] for j in range(n)): # Check the columns\n", + " return board[0][i]\n", + " if all(board[0][0] != 0 and board[i][i] == board[0][0] for i in range(n)): # Check the diagonal from top left to bottom right\n", + " return board[0][0]\n", + " if all(board[0][n-1] != 0 and board[i][n-1-i] == board[0][n-1] for i in range(n)): # Check the diagonal from top right to bottom left\n", + " return board[0][n-1]\n", + " if any(board[i][j] == 0 for i in range(n) for j in range(n)): # Check for any empty entries in the matrix\n", + " return -1\n", + " return 0 # Draw condition if all else fails." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [], + "source": [ + "winner_is_2 = [[2, 2, 0],\n", + "\t[2, 1, 0],\n", + "\t[2, 1, 1]]\n", + "\n", + "winner_is_1 = [[1, 2, 0],\n", + "\t[2, 1, 0],\n", + "\t[2, 1, 1]]\n", + "\n", + "\n", + "winner_is_also_1 = [[0, 1, 0],\n", + "\t[2, 1, 0],\n", + "\t[2, 1, 1]]\n", + "\n", + "\n", + "no_winner = [[1, 2, 0],\n", + "\t[2, 1, 0],\n", + "\t[2, 1, 2]]\n", + "\n", + "\n", + "also_no_winner = [[1, 2, 0],\n", + "\t[2, 1, 0],\n", + "\t[2, 1, 0]]\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Exercise 5: Write a function that takes a game board, player number, and (x,y) coordinates and places \"X\" or \"O\" in the correct location of the game board. Make sure that you only allow filling previously empty locations. Return True or False to indicate successful placement of \"X\" or \"O\"." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "def play_game(game_board, player, x, y):\n", + " n = len(board) # setting n equal to the dimensions of the matrix.\n", + " if not (0 <= x < n and 0 <= y < n): # Checking if the move is possible\n", + " print(\"Error: This move is out of bounds.\")\n", + " return False\n", + " if board[x][y] == 0: # Checking if the space is free\n", + " board[x][y] = \"X\" if player == 1 else \"O\"\n", + " return True\n", + " else: # Error message if the space is not free\n", + " print(\"Error: This space is already occupied.\")\n", + " return False\n", + " \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Exercise 6: Modify Exercise 3 to show column and row labels so that players can specify location using \"A2\" or \"C1\"." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " A B C\n", + "1 O | X | O\n", + " ---+---+---\n", + "2 X | O | X\n", + " ---+---+---\n", + "3 O | X | O\n" + ] + } + ], + "source": [ + "import string\n", + "\n", + "def game_board(value=[\"O\", \"X\"]):\n", + " n = int(input(\"Please enter your value for n: \"))\n", + " m = int(input(\"Please enter your value for m: \"))\n", + "\n", + " board = [[value[(i * m + j) % len(value)] for j in range(m)] for i in range(n)]\n", + " \n", + " # Print column labels (A, B, C...)\n", + " columns = \" \" + \" \".join(string.ascii_uppercase[:m])\n", + " print(columns)\n", + "\n", + " for i in range(n):\n", + " row_label = f\"{i+1} \" # Row labels (1, 2, 3, ...)\n", + " row_content = \" | \".join(board[i]) # Convert list to formatted string\n", + " print(row_label + row_content)\n", + " if i < n - 1:\n", + " print(\" \" + \"---+\" * (m - 1) + \"---\") # Row separators\n", + " \n", + " return board\n", + "\n", + "my_board = game_board()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Exercise 7: Write a function that takes a board, player number, and location specified as in exercise 6 and then calls exercise 5 to correctly modify the board." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [], + "source": [ + "import string\n", + "\n", + "def game_board():\n", + " n = int(input(\"Please enter your value for n: \"))\n", + " m = int(input(\"Please enter your value for m: \"))\n", + "\n", + " # Generate empty board filled with 0s\n", + " board = [[0 for _ in range(m)] for _ in range(n)]\n", + " print_board(board)\n", + " \n", + " return board\n", + "\n", + "def print_board(board):\n", + " n, m = len(board), len(board[0])\n", + " columns = \" \" + \" \".join(string.ascii_uppercase[:m]) # Column labels (A, B, C...)\n", + " print(columns)\n", + "\n", + " for i in range(n):\n", + " row_label = f\"{i+1} \" # Row numbers (1, 2, 3, ...)\n", + " row_content = \" | \".join(str(cell) if cell != 0 else \" \" for cell in board[i])\n", + " print(row_label + row_content)\n", + " if i < n - 1:\n", + " print(\" \" + \"---+\" * (m - 1) + \"---\") # Row separators\n", + "\n", + "def play_game(board, player, position):\n", + " n, m = len(board), len(board[0]) # Get board dimensions\n", + " letters = string.ascii_uppercase[:m] # Column labels (A, B, C, ...)\n", + "\n", + " # Validate input format (e.g., \"A1\", \"C2\")\n", + " if len(position) < 2 or position[0] not in letters or not position[1:].isdigit():\n", + " print(\"Error: Invalid move format. Use A1, B2, etc.\")\n", + " return False\n", + " \n", + " col = letters.index(position[0]) # Convert column letter to index\n", + " row = int(position[1:]) - 1 # Convert row number to index \n", + "\n", + " # Check if move is out of bounds\n", + " if not (0 <= row < n and 0 <= col < m):\n", + " print(\"Error: This move is out of bounds.\")\n", + " return False\n", + "\n", + " # Check if the space is free\n", + " if board[row][col] != 0:\n", + " print(\"Error: This space is already occupied.\")\n", + " return False\n", + "\n", + " # Place the move\n", + " board[row][col] = \"X\" if player == 1 else \"O\"\n", + "\n", + " # Print updated board\n", + " print_board(board)\n", + " return True" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " A B C\n", + "1 | | \n", + " ---+---+---\n", + "2 | | \n", + " ---+---+---\n", + "3 | | \n", + " A B C\n", + "1 | | \n", + " ---+---+---\n", + "2 | X | \n", + " ---+---+---\n", + "3 | | \n" + ] + } + ], + "source": [ + "board = game_board()\n", + "play_game1(board, 1, \"B2\") # Player 1 places \"X\" at B2\n", + "print_board(board)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Exercise 8: Write a function is called with a board and player number, takes input from the player using python's input, and modifies the board using your function from exercise 7. Note that you should keep asking for input until you have gotten a valid input that results in a valid move." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [], + "source": [ + "def player_turn(board, player):\n", + " while True:\n", + " position = input(f\"Player {player} ({'X' if player == 1 else 'O'}), enter your move (e.g., A1, B2): \").upper()\n", + "\n", + " if position.lower() == \"quit\":\n", + " print(\"Game ended.\")\n", + " return False # Return False to indicate game termination\n", + "\n", + " success = play_game(board, player, position)\n", + "\n", + " if success:\n", + " return True # Move was successful" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " A B C\n", + "1 | | \n", + " ---+---+---\n", + "2 | | \n", + " ---+---+---\n", + "3 | | \n", + " A B C\n", + "1 X | | \n", + " ---+---+---\n", + "2 | | \n", + " ---+---+---\n", + "3 | | \n", + " A B C\n", + "1 X | | \n", + " ---+---+---\n", + "2 | O | \n", + " ---+---+---\n", + "3 | | \n" + ] + }, + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "board = game_board() # Create an empty game board\n", + "player_turn(board, 1) # Player 1's turn\n", + "player_turn(board, 2) # Player 2's turn" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Exercise 9: Use all of the previous exercises to implement a full tic-tac-toe game, where an appropriate board is drawn, 2 players are repeatedly asked for a location coordinates of where they wish to place a mark, and the game status is checked until a player wins or a draw occurs." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " A B C D E\n", + "1 _ | _ | _ | _ | _\n", + " ---+---+---+---+---\n", + "2 _ | _ | _ | _ | _\n", + " ---+---+---+---+---\n", + "3 _ | _ | _ | _ | _\n", + " ---+---+---+---+---\n", + "4 _ | _ | _ | _ | _\n", + " ---+---+---+---+---\n", + "5 _ | _ | _ | _ | _\n", + "\n", + "\n", + " A B C D E\n", + "1 X | _ | _ | _ | _\n", + " ---+---+---+---+---\n", + "2 _ | _ | _ | _ | _\n", + " ---+---+---+---+---\n", + "3 _ | _ | _ | _ | _\n", + " ---+---+---+---+---\n", + "4 _ | _ | _ | _ | _\n", + " ---+---+---+---+---\n", + "5 _ | _ | _ | _ | _\n", + "\n", + "\n", + " A B C D E\n", + "1 X | O | _ | _ | _\n", + " ---+---+---+---+---\n", + "2 _ | _ | _ | _ | _\n", + " ---+---+---+---+---\n", + "3 _ | _ | _ | _ | _\n", + " ---+---+---+---+---\n", + "4 _ | _ | _ | _ | _\n", + " ---+---+---+---+---\n", + "5 _ | _ | _ | _ | _\n", + "\n", + "\n", + " A B C D E\n", + "1 X | O | _ | _ | _\n", + " ---+---+---+---+---\n", + "2 X | _ | _ | _ | _\n", + " ---+---+---+---+---\n", + "3 _ | _ | _ | _ | _\n", + " ---+---+---+---+---\n", + "4 _ | _ | _ | _ | _\n", + " ---+---+---+---+---\n", + "5 _ | _ | _ | _ | _\n", + "\n", + "\n", + " A B C D E\n", + "1 X | O | _ | _ | _\n", + " ---+---+---+---+---\n", + "2 X | O | _ | _ | _\n", + " ---+---+---+---+---\n", + "3 _ | _ | _ | _ | _\n", + " ---+---+---+---+---\n", + "4 _ | _ | _ | _ | _\n", + " ---+---+---+---+---\n", + "5 _ | _ | _ | _ | _\n", + "\n", + "\n", + " A B C D E\n", + "1 X | O | _ | _ | _\n", + " ---+---+---+---+---\n", + "2 X | O | _ | _ | _\n", + " ---+---+---+---+---\n", + "3 X | _ | _ | _ | _\n", + " ---+---+---+---+---\n", + "4 _ | _ | _ | _ | _\n", + " ---+---+---+---+---\n", + "5 _ | _ | _ | _ | _\n", + "\n", + "\n", + " A B C D E\n", + "1 X | O | _ | _ | _\n", + " ---+---+---+---+---\n", + "2 X | O | _ | _ | _\n", + " ---+---+---+---+---\n", + "3 X | O | _ | _ | _\n", + " ---+---+---+---+---\n", + "4 _ | _ | _ | _ | _\n", + " ---+---+---+---+---\n", + "5 _ | _ | _ | _ | _\n", + "\n", + "\n", + " A B C D E\n", + "1 X | O | _ | _ | _\n", + " ---+---+---+---+---\n", + "2 X | O | _ | _ | _\n", + " ---+---+---+---+---\n", + "3 X | O | _ | _ | _\n", + " ---+---+---+---+---\n", + "4 X | _ | _ | _ | _\n", + " ---+---+---+---+---\n", + "5 _ | _ | _ | _ | _\n", + "\n", + "\n", + " A B C D E\n", + "1 X | O | _ | _ | _\n", + " ---+---+---+---+---\n", + "2 X | O | _ | _ | _\n", + " ---+---+---+---+---\n", + "3 X | O | _ | _ | _\n", + " ---+---+---+---+---\n", + "4 X | O | _ | _ | _\n", + " ---+---+---+---+---\n", + "5 _ | _ | _ | _ | _\n", + "\n", + "\n", + " A B C D E\n", + "1 X | O | _ | _ | _\n", + " ---+---+---+---+---\n", + "2 X | O | _ | _ | _\n", + " ---+---+---+---+---\n", + "3 X | O | _ | _ | _\n", + " ---+---+---+---+---\n", + "4 X | O | _ | _ | _\n", + " ---+---+---+---+---\n", + "5 X | _ | _ | _ | _\n", + "\n", + "\n", + "Player 1 (X) wins!\n" + ] + } + ], + "source": [ + "import string\n", + "def game_board():\n", + " n = int(input(\"Please enter your value for n: \"))\n", + " m = int(input(\"Please enter your value for m: \"))\n", + "\n", + " # Generate empty board filled with 0s\n", + " board = [[0 for _ in range(m)] for _ in range(n)]\n", + " print_board(board) # Show empty board initially\n", + " \n", + " return board\n", + "\n", + "def print_board(board):\n", + " n, m = len(board), len(board[0])\n", + " columns = \" \" + \" \".join(string.ascii_uppercase[:m]) # Column labels (A, B, C...)\n", + " print(columns)\n", + "\n", + " for i in range(n):\n", + " row_label = f\"{i+1} \" # Row numbers (1, 2, 3, ...)\n", + " row_content = \" | \".join(str(cell) if cell in [\"X\", \"O\"] else \"_\" for cell in board[i])\n", + " print(row_label + row_content)\n", + " if i < n - 1:\n", + " print(\" \" + \"---+\" * (m - 1) + \"---\") # Row separators\n", + " print(\"\\n\") # Extra line break for readability\n", + "\n", + "def play_game(board, player, position):\n", + " n, m = len(board), len(board[0]) # Get board dimensions\n", + " letters = string.ascii_uppercase[:m] # Column labels (A, B, C, ...)\n", + "\n", + " # Validate input format (e.g., \"A1\", \"C2\")\n", + " if len(position) < 2 or position[0] not in letters or not position[1:].isdigit():\n", + " print(\"Error: Invalid move format. Use A1, B2, etc.\")\n", + " return False\n", + " \n", + " col = letters.index(position[0]) # Convert column letter to index \n", + " row = int(position[1:]) - 1 # Convert row number to index \n", + "\n", + " # Check if move is out of bounds\n", + " if not (0 <= row < n and 0 <= col < m):\n", + " print(\"Error: This move is out of bounds.\")\n", + " return False\n", + "\n", + " # Check if the space is free\n", + " if board[row][col] != 0:\n", + " print(\"Error: This space is already occupied.\")\n", + " return False\n", + "\n", + " # Place the move\n", + " board[row][col] = \"X\" if player == 1 else \"O\"\n", + "\n", + " # Print updated board\n", + " print_board(board)\n", + " \n", + " return True # Move was successful\n", + "\n", + "def player_turn(board, player):\n", + " while True:\n", + " position = input(f\"Player {player} ({'X' if player == 1 else 'O'}), enter your move (e.g., A1, B2): \").upper()\n", + "\n", + " if position.lower() == \"quit\":\n", + " print(\"Game ended.\")\n", + " return False # Return False to indicate game termination\n", + "\n", + " success = play_game(board, player, position)\n", + "\n", + " if success:\n", + " return True # Move was successful, exit loop\n", + "\n", + "def check_winner(board):\n", + " n, m = len(board), len(board[0])\n", + "\n", + " # Check rows and columns\n", + " for i in range(n):\n", + " if all(board[i][0] != 0 and board[i][j] == board[i][0] for j in range(m)): # Row check\n", + " return 1 if board[i][0] == \"X\" else 2\n", + " for i in range(m):\n", + " if all(board[0][i] != 0 and board[j][i] == board[0][i] for j in range(n)): # Column check\n", + " return 1 if board[0][i] == \"X\" else 2\n", + "\n", + " # Check diagonals (only if square board)\n", + " if n == m:\n", + " if all(board[0][0] != 0 and board[i][i] == board[0][0] for i in range(n)):\n", + " return 1 if board[0][0] == \"X\" else 2\n", + " if all(board[0][m-1] != 0 and board[i][m-1-i] == board[0][m-1] for i in range(n)):\n", + " return 1 if board[0][m-1] == \"X\" else 2\n", + "\n", + " # Check if there are empty spaces (game is incomplete)\n", + " if any(board[i][j] == 0 for i in range(n) for j in range(m)):\n", + " return -1\n", + "\n", + " return 0 # It's a draw\n", + "\n", + "def start_game():\n", + " board = game_board() # Create an empty board\n", + " current_player = 1 # Player 1 starts\n", + "\n", + " while True:\n", + " result = player_turn(board, current_player)\n", + "\n", + " if result is False: # Player entered \"quit\"\n", + " break\n", + "\n", + " game_status = check_winner(board)\n", + "\n", + " if game_status == 1:\n", + " print(\"Player 1 (X) wins!\")\n", + " break\n", + " elif game_status == 2:\n", + " print(\"Player 2 (O) wins!\")\n", + " break\n", + " elif game_status == 0:\n", + " print(\"It's a draw!\")\n", + " break\n", + "\n", + " # Switch players\n", + " current_player = 2 if current_player == 1 else 1\n", + "\n", + "# Run the game\n", + "start_game()" + ] + } + ], + "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.12.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 4f8de15cdeaee44b090878fc361be509a52070a8 Mon Sep 17 00:00:00 2001 From: mathlete1618 Date: Mon, 17 Feb 2025 12:58:22 -0600 Subject: [PATCH 2/5] Lab 3 Solutions --- Labs/Lab.3/Lab.3.solution.ipynb | 1181 +++++++++++++++++++++++++++++++ 1 file changed, 1181 insertions(+) create mode 100644 Labs/Lab.3/Lab.3.solution.ipynb diff --git a/Labs/Lab.3/Lab.3.solution.ipynb b/Labs/Lab.3/Lab.3.solution.ipynb new file mode 100644 index 000000000..d6eaa9ae0 --- /dev/null +++ b/Labs/Lab.3/Lab.3.solution.ipynb @@ -0,0 +1,1181 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Lab 3\n", + "\n", + "In this lab we will become familiar with distributions, histograms, and functional programming. Do not use numpy or any other library for this lab.\n", + "\n", + "Before that, lets get setup homework submission and submit your previous lab. \n", + "\n", + "## Working on the Command-line.\n", + "\n", + "It is important for you to learn to work on the command line and to be familiar with the Unix environment (e.g. Linux, Mac OS, or Windows Linux Subsystem). We'll go over working on the command-line in detail later in the course.\n", + "\n", + "You are required to submit your work in this course via GitHub. Today in class, you will setup everything on the command-line.\n", + "\n", + "### Command-line basics\n", + "\n", + "There is plenty of material online that will help you figure out how to do various tasks on the command line. Commands you may need to know today:\n", + "\n", + "* `ls`: lists the contents of the current directory.\n", + "* `pwd`: prints the path of the current directory.\n", + "* `cd `: changes your current directory to the specified directory.\n", + "* `cd ..`: changes current directory to the previous directory. Basically steps out of the current directory to the directory containing the current directory.\n", + "* `mkdir `: create a new directory with the specified name.\n", + "* `rmdir `: removes the specified directory. Note it has to be empty.\n", + "* `rm `: deletes the specified file.\n", + "* `mv `: Moves or renames a file.\n", + "* `cp `: copies an file. If you just provide a path to a directory, it copies the file into that directory with the same filename. If you specifiy a new filename, the copy has a new name. For example `cp File.1.txt File.2.txt` creates a copy of `File.1.txt` with the name `File.2.txt`. Meanwhile `cp File.1.txt my_directory`, where `my_directory` is a directory, creates a copy of `File.1.txt` in directory `my_directory` with the name `File.1.txt`.\n", + "\n", + "For reference, here are some example resources I found by googling:\n", + "\n", + "* Paths and Wildcards: https://www.warp.dev/terminus/linux-wildcards\n", + "* Basic commands like copy: https://kb.iu.edu/d/afsk\n", + "* General introduction to shell: https://github-pages.ucl.ac.uk/RCPSTrainingMaterials/HPCandHTCusingLegion/2_intro_to_shell.html\n", + "* Manual pages: https://www.geeksforgeeks.org/linux-man-page-entries-different-types/?ref=ml_lbp\n", + "* Chaining commands: https://www.geeksforgeeks.org/chaining-commands-in-linux/?ref=ml_lbp\n", + "* Piping: https://www.geeksforgeeks.org/piping-in-unix-or-linux/\n", + "* Using sed: https://www.geeksforgeeks.org/sed-command-linux-set-2/?ref=ml_lbp\n", + "* Various Unix commands: https://www.geeksforgeeks.org/linux-commands/?ref=lbp\n", + "* Cheat sheets:\n", + " * https://www.stationx.net/unix-commands-cheat-sheet/\n", + " * https://cheatography.com/davechild/cheat-sheets/linux-command-line/\n", + " * https://www.theknowledgeacademy.com/blog/unix-commands-cheat-sheet/\n", + " \n", + "These aren't necessarily the best resources. Feel free to search for better ones. Also, don't forget that Unix has built-in manual pages for all of its commands. Just type `man ` at the command prompt. Use the space-bar to scroll through the documentation and \"q\" to exit.\n", + "\n", + "\n", + "### Setup and Submission\n", + "\n", + "Our course repository is public. The instructions here aim to have you setup a fork of the course repository. Unfortunately because you are forking a public repo, your fork will have to be public also. \n", + "\n", + "You should be familiar with git from the first semester of this course. I assume that you all have github accounts and have setup things to be able to [push to github using ssh](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh). The instuctions here lead you to:\n", + "\n", + "We'll overview what you will do before going through step by step instructions.\n", + "\n", + "1. Setup:\n", + " 1. Fork the class repository. Some directions in [fork-a-repo](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo).\n", + " 1. Create a directory on your personal system where you will keep all course materials.\n", + " 1. In that directory, clone your fork of the repository.\n", + " 1. Using `git remote`, set the upstream to be the class repo, so you can pull from the class and push to your fork.\n", + "\n", + "1. Submission:\n", + " 1. Copy your solutions into the appropriate directory (e.g. into `Labs/Lab.2/`) and with appropriate filename `Lab.2.solution.ipynb'.\n", + " 1. Commit / push your solutions.\n", + " 1. Grant access to course instructors.\n", + "\n", + "Below are step by step instructions with examples (including example directory naming convention). Feel free to modify things as you see fit. \n", + "\n", + "#### Setup\n", + "You should only need to follow this instructions once. Here are some useful git commands:\n", + "\n", + "* Git help: `git help`\n", + "* Git remote help: `git help remote`\n", + "* Check remote status: `git remote -v`\n", + "* Add a remote: `git remote add `\n", + "* Add a remove: `git remote remove `\n", + "\n", + "Steps:\n", + "1. In a browser, log into GitHub and navigate to the [course repository](https://github.com/UTA-DataScience/DATA3402.Spring.2025).\n", + "1. On the top right of the page, press the fork button to create a new fork into your own GitHub account.\n", + "1. After successful fork, you should find the browser showing your fork of the course repository. Use the green \"Code\" button to copy path to the repo into your the clipboard of your computer.\n", + "1. Open a shell on your personal computer.\n", + "1. If you have not done so already, create a new directory/folder where you will keep all course material to navigate to it. For example: `mkdir Data-3402` and `cd Data-3402`.\n", + "1. Clone your fork of the repository using `git clone` followed by the path you copied into your clipboard. (copy/paste)\n", + "1. Paste the URL to your fork in the worksheet for the TAs and instructors.\n", + "1. Now go into the directory of your clone (`cd DATA3402.Spring.2025`).\n", + "1. Type `git remote -v` to see the current setup for fetch and pull.\n", + "1. Note the URL you see. This should be the same as what you used for your clone for both push and fetch.\n", + "1. Delete the origin remote using `git remote remove origin`.\n", + "1. Add the course repo as your remote using `git remote add origin https://github.com/UTA-DataScience/DATA3402.Spring.2025.git`.\n", + "1. Change the push to point to your fork. This means you will need the URL to your clone we copied earlier and confirmed as the original origin. The command will look something like: `git remote set-url --push origin https://github.com/XXXXXX/DATA3402.Spring.2025.git`, where XXXXX is your username on GitHub.\n", + "1. Note that if you setup everything correctly, you now should be able to do `git pull` to get updates from the course repo, and do `git push` to push your commits into your own fork.\n", + "\n", + "### Submission\n", + "These instructions outline how you submit files. Some useful commands:\n", + "* To add a file to local repository: `git add `.\n", + "* To commit all changed files into local repository: `git -a -m \"A message\"`. You need to provide some comment when you commit. \n", + "* To push the commited files from the local repository to GitHub: `git push`.\n", + "* To get updates from GitHub: `git pull`.\n", + "\n", + "Steps:\n", + "1. To submit your labs, navigate to your clone of your fork of the course repository. \n", + "1. Use `git pull` to make sure you have the latest updates. \n", + "1. Make sure your copy of the lab your are working on is in the appropriate place in this clone. That means if you have the file elsewhere, copy it to the same directory in your clone of your fork. \n", + "1. Note that in order to avoid future conflicts, you should always name your solution differently than the original file in the class repo. For example if your file is still named `Lab.2.ipynb` you should rename it using the `mv` command: `mv Lab.2.ipynb Lab.2.solution.ipynb`. \n", + "1. Add and files you wish to submit into the repo. For example: `git add Labs/Lab.2/Lab.2.solution.ipynb`\n", + "1. Commit any changes: `git commit -a -m \"Lab 2 updates\"`\n", + "1. Push your changes: `git push`\n", + "1. Check on github website that your solutions have been properly submitted.\n", + "\n", + "Before you leave the session today, make sure your GitHub Repo is setup. If you need to work further on your lab, navigate jupyter to the copy of the lab you just submitted and work there. Once done, repeat the commit and push commands to submit your updated solution. Note that lab 2 is due by midnight Friay 1/31/2025.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Uniform Distribution\n", + "Lets start with generating some fake random data. You can get a random number between 0 and 1 using the python random module as follow:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The Value of x is 0.3038213563963127\n" + ] + } + ], + "source": [ + "import random\n", + "x=random.random()\n", + "print(\"The Value of x is\", x)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Everytime you call random, you will get a new number.\n", + "\n", + "*Exercise 1:* Using random, write a function `generate_uniform(N, mymin, mymax)`, that returns a python list containing N random numbers between specified minimum and maximum value. Note that you may want to quickly work out on paper how to turn numbers between 0 and 1 to between other values. " + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "# Skeleton\n", + "def generate_uniform(N,x_min,x_max):\n", + " out = [random.uniform(x_min, x_max) for _ in range(N)]\n", + " ### BEGIN SOLUTION\n", + " return out\n", + "\n", + " \n", + " ### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[4.797515009795637, 8.43802053771656, 6.999224937849511, 1.3064293011275612, 9.834405724594555, 9.244146662646529, 7.320616149758561, 9.676285256607045, 1.9771658257600162, 2.5830798179433785, 9.690879532079073, 2.3593930053462078, 7.838340631689281, 8.863384835241973, 4.322521432680164, 3.97297300367378, 7.579088591612718, 1.1341802263886815, 6.6205366524886555, 6.341989198209182, 5.923527985120415, 6.610006163517976, 9.969390528011953, 9.634268002681141, 7.661135618647696, 8.189283753227343, 2.029577894260367, 7.832145561948382, 5.195754672332317, 1.1065699981166273, 9.684686244027775, 8.79409140855331, 6.56286542478127, 8.606549597405484, 5.088758680870245, 8.987819131884853, 9.608213180955294, 2.9399161674464604, 8.00798081669694, 7.514164213874496, 7.685565885329513, 6.055083237564146, 6.869506103483499, 5.126225600107013, 9.701116812720189, 8.127847436200575, 3.7184541315678894, 8.289574404258481, 4.830629130140072, 3.036504676396639]\n" + ] + } + ], + "source": [ + "test_1 = generate_uniform(50, 1, 10)\n", + "print(test_1)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Data Type: \n", + "Data Length: 1000\n", + "Type of Data Contents: \n", + "Data Minimum: -9.964161110037551\n", + "Data Maximum: 9.998773388299508\n" + ] + } + ], + "source": [ + "# Test your solution here\n", + "data=generate_uniform(1000,-10,10)\n", + "print (\"Data Type:\", type(data))\n", + "print (\"Data Length:\", len(data))\n", + "if len(data)>0: \n", + " print (\"Type of Data Contents:\", type(data[0]))\n", + " print (\"Data Minimum:\", min(data))\n", + " print (\"Data Maximum:\", max(data))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*Exercise 2a:* \n", + "Write a function that computes the mean of values in a list. Recall the equation for the mean of a random variable $\\bf{x}$ computed on a data set of $n$ values $\\{ x_i \\} = \\{x_1, x_2, ..., x_n\\}$ is ${\\bf\\bar{x}} = \\frac{1}{n} \\sum_i^n x_i$." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [], + "source": [ + "# Skeleton\n", + "def mean(Data):\n", + " \n", + " ### BEGIN SOLUTION\n", + " \n", + " # Fill in your solution here \n", + " n = len(Data)\n", + " total_value = sum(Data)\n", + " mean = (total_value / n)\n", + " ### END SOLUTION\n", + " \n", + " return mean" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mean of Data: 0.017456673194431793\n" + ] + } + ], + "source": [ + "# Test your solution here\n", + "print (\"Mean of Data:\", mean(data))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*Exercise 2b:* \n", + "Write a function that computes the variance of values in a list. Recall the equation for the variance of a random variable $\\bf{x}$ computed on a data set of $n$ values $\\{ x_i \\} = \\{x_1, x_2, ..., x_n\\}$ is ${\\bf\\langle x \\rangle} = \\frac{1}{n} \\sum_i^n (x_i - {\\bf\\bar{x}})$." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [], + "source": [ + "# Skeleton\n", + "def variance(Data):\n", + " \n", + " ### BEGIN SOLUTION\n", + "\n", + " # Fill in your solution here \n", + " n = len(Data)\n", + " total_value = sum(Data)\n", + " mean = (total_value / n)\n", + " deviation = 0\n", + " for i in Data:\n", + " deviation += (i - mean)**2\n", + " variance = (deviation / n)\n", + " return variance\n", + " ### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Variance of Data: 31.748298741987046\n" + ] + } + ], + "source": [ + "# Test your solution here\n", + "print (\"Variance of Data:\", variance(data))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Histogramming" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*Exercise 3:* Write a function that bins the data so that you can create a histogram. An example of how to implement histogramming is the following logic:\n", + "\n", + "* User inputs a list of values `x` and optionally `n_bins` which defaults to 10.\n", + "* If not supplied, find the minimum and maximum (`x_min`,`x_max`) of the values in x.\n", + "* Determine the bin size (`bin_size`) by dividing the range of the function by the number of bins.\n", + "* Create an empty list of zeros of size `n_bins`, call it `hist`.\n", + "* Loop over the values in `x`\n", + " * Loop over the values in `hist` with index `i`:\n", + " * If x is between `x_min+i*bin_size` and `x_min+(i+1)*bin_size`, increment `hist[i].` \n", + " * For efficiency, try to use continue to goto the next bin and data point.\n", + "* Return `hist` and the list corresponding of the bin edges (i.e. of `x_min+i*bin_size`). " + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [], + "source": [ + "# Solution\n", + "def histogram(x,n_bins=10,x_min=None,x_max=None):\n", + " ### BEGIN SOLUTION\n", + " # Fill in your solution here \n", + " if x_min is None: ## Finding mins and max if not already provided.\n", + " x_min = min(x)\n", + " if x_max is None:\n", + " x_max = max(x)\n", + " bin_size = (x_max - x_min) / n_bins # compute bin size\n", + " hist = [0 for _ in range(n_bins)] # list comp for initialzing hist with all bins at 0\n", + " bin_edges = [x_min + i * bin_size for i in range(n_bins + 1)] # list comp for creating the bin edges\n", + " for value in x: # main loop to place values from the list provided in corresponding bins and then updating hist with those values\n", + " bin_index = int((value - x_min) / bin_size) # Compute correct bin index\n", + " if bin_index == n_bins: # Edge case: value == x_max\n", + " bin_index -= 1 \n", + " hist[bin_index] += 1 # Increment correct bin\n", + " continue \n", + " ### END SOLUTION\n", + "\n", + " return hist,bin_edges" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[10, 5, 7, 16, 6, 13, 7, 6, 13, 12, 7, 13, 10, 9, 12, 5, 6, 13, 9, 11, 9, 8, 3, 15, 11, 15, 9, 15, 7, 15, 9, 9, 7, 9, 11, 10, 13, 14, 14, 12, 9, 11, 9, 10, 8, 16, 10, 4, 6, 9, 8, 16, 6, 11, 9, 11, 12, 6, 12, 7, 14, 8, 8, 12, 16, 13, 5, 9, 14, 15, 5, 8, 10, 16, 11, 14, 8, 13, 4, 7, 9, 12, 11, 6, 18, 11, 10, 8, 12, 16, 7, 14, 8, 10, 9, 5, 4, 10, 6, 9]\n", + "The minimum number of elements in a bin are: 3\n" + ] + } + ], + "source": [ + "# Test your solution here\n", + "hist,bin_edges=histogram(data,100)\n", + "print(hist)\n", + "print(\"The minimum number of elements in a bin are:\", min(hist))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*Exercise 4:* Write a function that uses the histogram function in the previous exercise to create a text-based \"graph\". For example the output could look like the following:\n", + "```\n", + "[ 0, 1] : ######\n", + "[ 1, 2] : #####\n", + "[ 2, 3] : ######\n", + "[ 3, 4] : ####\n", + "[ 4, 5] : ####\n", + "[ 5, 6] : ######\n", + "[ 6, 7] : #####\n", + "[ 7, 8] : ######\n", + "[ 8, 9] : ####\n", + "[ 9, 10] : #####\n", + "```\n", + "\n", + "Where each line corresponds to a bin and the number of `#`'s are proportional to the value of the data in the bin. " + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [], + "source": [ + "# Solution\n", + "def draw_histogram(x,n_bins,x_min=None,x_max=None,character=\"#\",max_character_per_line=20):\n", + " ### BEGIN SOLUTION\n", + " \n", + " # Fill in your solution here \n", + " hist, bin_edges = histogram(x, n_bins, x_min, x_max) \n", + " # Find the max count in any bin for scaling\n", + " max_count = max(hist) if hist else 1 # Else condition to avoid division by zero\n", + "\n", + " #### Loop through bins and print the text histogram\n", + " for i in range(n_bins): #### Compute bin width,\n", + " bin_start = bin_edges[i] # This is the first component of a bin's width\n", + " bin_end = bin_edges[i + 1] if i < len(bin_edges) - 1 else x_max # Use next bin edge, or x_max for the last bin\n", + " # Scale the number of characters to fit within max_character_per_line\n", + " ## Example: if hist = [10, 5, 8] and max_count = 10, then hist[0] = 10 and 10/10 = 1, then 1*max_characters_per_line will yield the number of characters in that bin\n", + " bar_length = int((hist[i] / max_count) * max_character_per_line) if max_count > 0 else 0\n", + " bar = character * bar_length # This simply sets how many characters correspond to a bin for a particular iteration in the loop\n", + "\n", + " # Print formatted bin range and bar\n", + " print(f\"Bin {i+1}: [{bin_start:.1f}, {bin_end:.1f}] : {bar}\") # .1f sets this to 1 decimal place, for readability\n", + " ### END SOLUTION\n", + " return hist,bin_edges" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Bin 1: [-10.0, -9.0] : #############\n", + "Bin 2: [-9.0, -8.0] : ################\n", + "Bin 3: [-8.0, -7.0] : ################\n", + "Bin 4: [-7.0, -6.0] : #############\n", + "Bin 5: [-6.0, -5.0] : ##############\n", + "Bin 6: [-5.0, -4.0] : ###################\n", + "Bin 7: [-4.0, -3.0] : ##############\n", + "Bin 8: [-3.0, -2.0] : ####################\n", + "Bin 9: [-2.0, -1.0] : ##############\n", + "Bin 10: [-1.0, 0.0] : ##############\n", + "Bin 11: [0.0, 1.0] : ###############\n", + "Bin 12: [1.0, 2.0] : ###############\n", + "Bin 13: [2.0, 3.0] : ##################\n", + "Bin 14: [3.0, 4.0] : #################\n", + "Bin 15: [4.0, 5.0] : ###############\n", + "Bin 16: [5.0, 6.0] : ##############\n", + "Bin 17: [6.0, 7.0] : #################\n", + "Bin 18: [7.0, 8.0] : ##################\n", + "Bin 19: [8.0, 9.0] : ###############\n", + "Bin 20: [9.0, 10.0] : ##########\n" + ] + }, + { + "data": { + "text/plain": [ + "([44,\n", + " 51,\n", + " 51,\n", + " 44,\n", + " 46,\n", + " 61,\n", + " 45,\n", + " 63,\n", + " 47,\n", + " 45,\n", + " 50,\n", + " 48,\n", + " 58,\n", + " 56,\n", + " 50,\n", + " 46,\n", + " 56,\n", + " 57,\n", + " 48,\n", + " 34],\n", + " [-9.964161110037551,\n", + " -8.966014385120697,\n", + " -7.967867660203845,\n", + " -6.969720935286992,\n", + " -5.9715742103701395,\n", + " -4.9734274854532865,\n", + " -3.9752807605364335,\n", + " -2.9771340356195806,\n", + " -1.9789873107027276,\n", + " -0.9808405857858737,\n", + " 0.017306139130978337,\n", + " 1.0154528640478304,\n", + " 2.0135995889646843,\n", + " 3.011746313881538,\n", + " 4.00989303879839,\n", + " 5.008039763715242,\n", + " 6.006186488632096,\n", + " 7.00433321354895,\n", + " 8.002479938465804,\n", + " 9.000626663382654,\n", + " 9.998773388299508])" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Test your solution here\n", + "draw_histogram(data, 20)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Functional Programming\n", + "\n", + "*Exercise 5:* Write a function the applies a booling function (that returns true/false) to every element in data, and return a list of indices of elements where the result was true. Use this function to find the indices of entries greater than 0.5. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### In order to solve exercise 5 and 6 which builds on 5, I had to implement the bool_list comprehenssion so that the indicies list comprhession could take the additional functions from exercise 6. The is_greater_than_half function is here to directly find the position of elements in the data and return their indicies when the values in data are greater than 0.5" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [], + "source": [ + "def where(mylist, myfunc):\n", + " ### Begin Solution\n", + " # Apply the myfunc function to all elements\n", + " bool_list = [myfunc(x) for x in mylist] # This is required in order to be interoperable with exercise 6\n", + "\n", + " # Get indices where myfunc is True\n", + " indices = [i for i, is_true in enumerate(bool_list) if is_true]\n", + "\n", + " # Print results for debugging\n", + " print(f\"Condition '{myfunc}' applied. True at indices: {indices}\")\n", + "\n", + " return indices, bool_list\n", + "\n", + "def is_greater_than_half(): # This function is to explicity answer exercise 5\n", + " return lambda x: x > 0.5\n", + " ### End Solution" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Condition '. at 0x118479e40>' applied. True at indices: [2, 4, 5, 6, 8, 11, 13, 17, 21, 22, 23, 25, 28, 29, 31, 34, 35, 36, 39, 40, 41, 42, 44, 49, 51, 52, 56, 60, 72, 74, 77, 81, 82, 83, 84, 86, 89, 90, 92, 96, 101, 104, 114, 116, 117, 119, 120, 123, 124, 125, 127, 130, 133, 134, 139, 140, 141, 143, 145, 148, 149, 150, 151, 152, 155, 156, 158, 159, 160, 161, 163, 165, 168, 169, 171, 174, 176, 177, 179, 182, 183, 186, 187, 188, 195, 201, 202, 203, 204, 205, 207, 209, 210, 211, 212, 213, 214, 217, 218, 219, 220, 221, 222, 225, 226, 227, 228, 230, 233, 234, 245, 246, 249, 250, 252, 253, 255, 256, 260, 261, 262, 264, 266, 267, 268, 269, 270, 272, 277, 278, 279, 280, 282, 283, 284, 285, 287, 290, 291, 293, 296, 297, 301, 304, 307, 311, 312, 313, 314, 316, 317, 321, 323, 327, 330, 333, 334, 335, 338, 339, 340, 342, 343, 351, 352, 354, 355, 356, 357, 358, 364, 365, 366, 370, 373, 375, 376, 377, 378, 380, 385, 386, 388, 389, 391, 392, 397, 399, 400, 401, 402, 403, 404, 405, 408, 409, 410, 411, 412, 413, 417, 420, 421, 422, 423, 426, 427, 428, 429, 432, 433, 434, 435, 436, 437, 438, 441, 443, 444, 447, 448, 449, 455, 456, 457, 460, 461, 462, 463, 464, 466, 468, 471, 473, 478, 479, 481, 485, 487, 489, 490, 494, 495, 496, 499, 501, 505, 506, 510, 515, 516, 519, 520, 521, 525, 532, 534, 535, 540, 544, 545, 547, 548, 549, 552, 553, 554, 558, 563, 564, 565, 566, 568, 571, 572, 574, 576, 578, 579, 580, 583, 586, 589, 590, 593, 594, 595, 598, 602, 603, 605, 607, 609, 610, 611, 612, 613, 616, 618, 620, 622, 624, 625, 627, 629, 632, 636, 641, 642, 643, 645, 646, 649, 650, 651, 652, 653, 655, 658, 666, 667, 669, 670, 672, 673, 674, 675, 676, 678, 679, 685, 688, 689, 690, 692, 693, 694, 700, 701, 702, 708, 712, 713, 714, 720, 724, 725, 726, 728, 729, 731, 735, 736, 737, 742, 743, 744, 745, 746, 747, 748, 749, 752, 753, 755, 756, 758, 762, 763, 767, 768, 775, 776, 777, 778, 779, 784, 790, 792, 794, 796, 797, 801, 805, 806, 808, 809, 818, 819, 829, 832, 833, 835, 840, 843, 844, 847, 851, 852, 853, 856, 857, 858, 863, 864, 867, 870, 871, 875, 876, 877, 883, 886, 888, 891, 892, 894, 895, 898, 901, 904, 905, 907, 912, 916, 917, 918, 920, 921, 922, 923, 925, 926, 927, 928, 929, 931, 933, 934, 936, 940, 943, 944, 946, 950, 951, 953, 954, 955, 956, 957, 958, 961, 962, 964, 965, 966, 967, 968, 969, 970, 971, 973, 975, 976, 978, 979, 980, 982, 984, 987, 989, 993, 994, 995]\n", + "([2, 4, 5, 6, 8, 11, 13, 17, 21, 22, 23, 25, 28, 29, 31, 34, 35, 36, 39, 40, 41, 42, 44, 49, 51, 52, 56, 60, 72, 74, 77, 81, 82, 83, 84, 86, 89, 90, 92, 96, 101, 104, 114, 116, 117, 119, 120, 123, 124, 125, 127, 130, 133, 134, 139, 140, 141, 143, 145, 148, 149, 150, 151, 152, 155, 156, 158, 159, 160, 161, 163, 165, 168, 169, 171, 174, 176, 177, 179, 182, 183, 186, 187, 188, 195, 201, 202, 203, 204, 205, 207, 209, 210, 211, 212, 213, 214, 217, 218, 219, 220, 221, 222, 225, 226, 227, 228, 230, 233, 234, 245, 246, 249, 250, 252, 253, 255, 256, 260, 261, 262, 264, 266, 267, 268, 269, 270, 272, 277, 278, 279, 280, 282, 283, 284, 285, 287, 290, 291, 293, 296, 297, 301, 304, 307, 311, 312, 313, 314, 316, 317, 321, 323, 327, 330, 333, 334, 335, 338, 339, 340, 342, 343, 351, 352, 354, 355, 356, 357, 358, 364, 365, 366, 370, 373, 375, 376, 377, 378, 380, 385, 386, 388, 389, 391, 392, 397, 399, 400, 401, 402, 403, 404, 405, 408, 409, 410, 411, 412, 413, 417, 420, 421, 422, 423, 426, 427, 428, 429, 432, 433, 434, 435, 436, 437, 438, 441, 443, 444, 447, 448, 449, 455, 456, 457, 460, 461, 462, 463, 464, 466, 468, 471, 473, 478, 479, 481, 485, 487, 489, 490, 494, 495, 496, 499, 501, 505, 506, 510, 515, 516, 519, 520, 521, 525, 532, 534, 535, 540, 544, 545, 547, 548, 549, 552, 553, 554, 558, 563, 564, 565, 566, 568, 571, 572, 574, 576, 578, 579, 580, 583, 586, 589, 590, 593, 594, 595, 598, 602, 603, 605, 607, 609, 610, 611, 612, 613, 616, 618, 620, 622, 624, 625, 627, 629, 632, 636, 641, 642, 643, 645, 646, 649, 650, 651, 652, 653, 655, 658, 666, 667, 669, 670, 672, 673, 674, 675, 676, 678, 679, 685, 688, 689, 690, 692, 693, 694, 700, 701, 702, 708, 712, 713, 714, 720, 724, 725, 726, 728, 729, 731, 735, 736, 737, 742, 743, 744, 745, 746, 747, 748, 749, 752, 753, 755, 756, 758, 762, 763, 767, 768, 775, 776, 777, 778, 779, 784, 790, 792, 794, 796, 797, 801, 805, 806, 808, 809, 818, 819, 829, 832, 833, 835, 840, 843, 844, 847, 851, 852, 853, 856, 857, 858, 863, 864, 867, 870, 871, 875, 876, 877, 883, 886, 888, 891, 892, 894, 895, 898, 901, 904, 905, 907, 912, 916, 917, 918, 920, 921, 922, 923, 925, 926, 927, 928, 929, 931, 933, 934, 936, 940, 943, 944, 946, 950, 951, 953, 954, 955, 956, 957, 958, 961, 962, 964, 965, 966, 967, 968, 969, 970, 971, 973, 975, 976, 978, 979, 980, 982, 984, 987, 989, 993, 994, 995], [False, False, True, False, True, True, True, False, True, False, False, True, False, True, False, False, False, True, False, False, False, True, True, True, False, True, False, False, True, True, False, True, False, False, True, True, True, False, False, True, True, True, True, False, True, False, False, False, False, True, False, True, True, False, False, False, True, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, True, False, True, False, False, True, False, False, False, True, True, True, True, False, True, False, False, True, True, False, True, False, False, False, True, False, False, False, False, True, False, False, True, False, False, False, False, False, False, False, False, False, True, False, True, True, False, True, True, False, False, True, True, True, False, True, False, False, True, False, False, True, True, False, False, False, False, True, True, True, False, True, False, True, False, False, True, True, True, True, True, False, False, True, True, False, True, True, True, True, False, True, False, True, False, False, True, True, False, True, False, False, True, False, True, True, False, True, False, False, True, True, False, False, True, True, True, False, False, False, False, False, False, True, False, False, False, False, False, True, True, True, True, True, False, True, False, True, True, True, True, True, True, False, False, True, True, True, True, True, True, False, False, True, True, True, True, False, True, False, False, True, True, False, False, False, False, False, False, False, False, False, False, True, True, False, False, True, True, False, True, True, False, True, True, False, False, False, True, True, True, False, True, False, True, True, True, True, True, False, True, False, False, False, False, True, True, True, True, False, True, True, True, True, False, True, False, False, True, True, False, True, False, False, True, True, False, False, False, True, False, False, True, False, False, True, False, False, False, True, True, True, True, False, True, True, False, False, False, True, False, True, False, False, False, True, False, False, True, False, False, True, True, True, False, False, True, True, True, False, True, True, False, False, False, False, False, False, False, True, True, False, True, True, True, True, True, False, False, False, False, False, True, True, True, False, False, False, True, False, False, True, False, True, True, True, True, False, True, False, False, False, False, True, True, False, True, True, False, True, True, False, False, False, False, True, False, True, True, True, True, True, True, True, False, False, True, True, True, True, True, True, False, False, False, True, False, False, True, True, True, True, False, False, True, True, True, True, False, False, True, True, True, True, True, True, True, False, False, True, False, True, True, False, False, True, True, True, False, False, False, False, False, True, True, True, False, False, True, True, True, True, True, False, True, False, True, False, False, True, False, True, False, False, False, False, True, True, False, True, False, False, False, True, False, True, False, True, True, False, False, False, True, True, True, False, False, True, False, True, False, False, False, True, True, False, False, False, True, False, False, False, False, True, True, False, False, True, True, True, False, False, False, True, False, False, False, False, False, False, True, False, True, True, False, False, False, False, True, False, False, False, True, True, False, True, True, True, False, False, True, True, True, False, False, False, True, False, False, False, False, True, True, True, True, False, True, False, False, True, True, False, True, False, True, False, True, True, True, False, False, True, False, False, True, False, False, True, True, False, False, True, True, True, False, False, True, False, False, False, True, True, False, True, False, True, False, True, True, True, True, True, False, False, True, False, True, False, True, False, True, False, True, True, False, True, False, True, False, False, True, False, False, False, True, False, False, False, False, True, True, True, False, True, True, False, False, True, True, True, True, True, False, True, False, False, True, False, False, False, False, False, False, False, True, True, False, True, True, False, True, True, True, True, True, False, True, True, False, False, False, False, False, True, False, False, True, True, True, False, True, True, True, False, False, False, False, False, True, True, True, False, False, False, False, False, True, False, False, False, True, True, True, False, False, False, False, False, True, False, False, False, True, True, True, False, True, True, False, True, False, False, False, True, True, True, False, False, False, False, True, True, True, True, True, True, True, True, False, False, True, True, False, True, True, False, True, False, False, False, True, True, False, False, False, True, True, False, False, False, False, False, False, True, True, True, True, True, False, False, False, False, True, False, False, False, False, False, True, False, True, False, True, False, True, True, False, False, False, True, False, False, False, True, True, False, True, True, False, False, False, False, False, False, False, False, True, True, False, False, False, False, False, False, False, False, False, True, False, False, True, True, False, True, False, False, False, False, True, False, False, True, True, False, False, True, False, False, False, True, True, True, False, False, True, True, True, False, False, False, False, True, True, False, False, True, False, False, True, True, False, False, False, True, True, True, False, False, False, False, False, True, False, False, True, False, True, False, False, True, True, False, True, True, False, False, True, False, False, True, False, False, True, True, False, True, False, False, False, False, True, False, False, False, True, True, True, False, True, True, True, True, False, True, True, True, True, True, False, True, False, True, True, False, True, False, False, False, True, False, False, True, True, False, True, False, False, False, True, True, False, True, True, True, True, True, True, False, False, True, True, False, True, True, True, True, True, True, True, True, False, True, False, True, True, False, True, True, True, False, True, False, True, False, False, True, False, True, False, False, False, True, True, True, False, False, False, False])\n" + ] + } + ], + "source": [ + "# Test your solution here\n", + "new_test = where(data, is_greater_than_half()) # This test uses the data that was generated from exercise 1 and is defined as \"data\"\n", + "print(new_test)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*Exercise 6:* The `inrange(mymin,mymax)` function below returns a function that tests if it's input is between the specified values. Write corresponding functions that test:\n", + "* Even\n", + "* Odd\n", + "* Greater than\n", + "* Less than\n", + "* Equal\n", + "* Divisible by" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [], + "source": [ + "## Begin Solution\n", + "def in_range(mymin, mymax): \n", + " \"\"\"Returns a function that checks if a number is within the given range [mymin, mymax).\"\"\"\n", + " return lambda x: mymin <= x < mymax\n", + "\n", + "def is_even():\n", + " \"\"\"Returns a function that checks if a number is even.\"\"\"\n", + " return lambda x: x % 2 == 0\n", + "\n", + "def is_odd():\n", + " \"\"\"Returns a function that checks if a number is odd.\"\"\"\n", + " return lambda x: x % 2 != 0\n", + "\n", + "def is_greater_than(value):\n", + " \"\"\"Returns a function that checks if a number is greater than a given value.\"\"\"\n", + " return lambda x: x > value\n", + "\n", + "def is_less_than(value):\n", + " \"\"\"Returns a function that checks if a number is less than a given value.\"\"\"\n", + " return lambda x: x < value\n", + "\n", + "def is_equal_to(value):\n", + " \"\"\"Returns a function that checks if a number is equal to a given value.\"\"\"\n", + " return lambda x: x == value\n", + "\n", + "def is_divisible_by(divisor):\n", + " \"\"\"Returns a function that checks if a number is divisible by a given divisor.\"\"\"\n", + " if divisor == 0:\n", + " raise ZeroDivisionError(\"Division by zero is not allowed\")\n", + " return lambda x: x % divisor == 0\n", + "\n", + "def apply_conditions(data):\n", + " # Automate process of calling the new functions\n", + " \"\"\"Returns a dictionary of results from the automated test of the new conditions\"\"\"\n", + " conditions = {\n", + " \"in_range_0_10\": in_range(0, 10),\n", + " \"in_range_10_20\": in_range(10, 20),\n", + " \"is_even\": is_even(),\n", + " \"is_odd\": is_odd(),\n", + " \"greater_than_5\": is_greater_than(5),\n", + " \"less_than_5\": is_less_than(5),\n", + " \"equal_to_3\": is_equal_to(3),\n", + " \"divisible_by_3\": is_divisible_by(3)\n", + " }\n", + "\n", + " # Apply each condition using `where()` and store yield in results\n", + " results = {name: where(data, func)[0] for name, func in conditions.items()}\n", + "\n", + " return results\n", + " ## End Solution\n", + " \n" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]\n", + "50\n" + ] + } + ], + "source": [ + "n = 50\n", + "new_data = list(range(n)) # This is a new random list of data to test the new conditions from exercise 6\n", + "print(new_data)\n", + "print(len(new_data))" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True True False False False\n", + "False False True True False\n", + "Condition '. at 0x1186545e0>' applied. True at indices: [2, 3, 4, 5, 6, 8, 11, 13, 17, 21, 22, 23, 25, 28, 29, 31, 34, 35, 36, 38, 39, 40, 41, 42, 44, 49, 51, 52, 56, 60, 72, 74, 77, 81, 82, 83, 84, 86, 89, 90, 92, 96, 101, 104, 109, 112, 114, 116, 117, 119, 120, 123, 124, 125, 127, 130, 133, 134, 139, 140, 141, 143, 145, 148, 149, 150, 151, 152, 155, 156, 158, 159, 160, 161, 163, 165, 167, 168, 169, 171, 172, 174, 176, 177, 179, 182, 183, 186, 187, 188, 195, 201, 202, 203, 204, 205, 207, 209, 210, 211, 212, 213, 214, 217, 218, 219, 220, 221, 222, 225, 226, 227, 228, 230, 233, 234, 245, 246, 249, 250, 252, 253, 255, 256, 260, 261, 262, 264, 266, 267, 268, 269, 270, 272, 277, 278, 279, 280, 282, 283, 284, 285, 287, 289, 290, 291, 293, 296, 297, 301, 304, 307, 311, 312, 313, 314, 316, 317, 321, 323, 327, 329, 330, 333, 334, 335, 336, 338, 339, 340, 342, 343, 345, 351, 352, 354, 355, 356, 357, 358, 364, 365, 366, 370, 373, 374, 375, 376, 377, 378, 380, 385, 386, 388, 389, 391, 392, 397, 398, 399, 400, 401, 402, 403, 404, 405, 408, 409, 410, 411, 412, 413, 417, 420, 421, 422, 423, 426, 427, 428, 429, 432, 433, 434, 435, 436, 437, 438, 441, 443, 444, 447, 448, 449, 451, 453, 455, 456, 457, 460, 461, 462, 463, 464, 466, 468, 471, 473, 478, 479, 481, 485, 487, 489, 490, 494, 495, 496, 499, 501, 504, 505, 506, 510, 514, 515, 516, 519, 520, 521, 525, 532, 534, 535, 540, 544, 545, 547, 548, 549, 552, 553, 554, 558, 563, 564, 565, 566, 568, 571, 572, 574, 576, 578, 579, 580, 583, 586, 589, 590, 593, 594, 595, 598, 602, 603, 605, 607, 609, 610, 611, 612, 613, 616, 618, 620, 621, 622, 624, 625, 627, 629, 631, 632, 636, 641, 642, 643, 645, 646, 649, 650, 651, 652, 653, 655, 658, 666, 667, 669, 670, 672, 673, 674, 675, 676, 678, 679, 684, 685, 688, 689, 690, 692, 693, 694, 700, 701, 702, 708, 710, 712, 713, 714, 720, 724, 725, 726, 728, 729, 731, 735, 736, 737, 742, 743, 744, 745, 746, 747, 748, 749, 752, 753, 755, 756, 758, 761, 762, 763, 767, 768, 775, 776, 777, 778, 779, 784, 790, 792, 794, 796, 797, 801, 805, 806, 808, 809, 811, 812, 813, 818, 819, 825, 829, 832, 833, 835, 837, 840, 842, 843, 844, 847, 850, 851, 852, 853, 856, 857, 858, 863, 864, 867, 870, 871, 875, 876, 877, 883, 886, 888, 891, 892, 894, 895, 898, 901, 904, 905, 907, 912, 915, 916, 917, 918, 920, 921, 922, 923, 925, 926, 927, 928, 929, 931, 933, 934, 936, 940, 943, 944, 946, 950, 951, 953, 954, 955, 956, 957, 958, 961, 962, 964, 965, 966, 967, 968, 969, 970, 971, 973, 975, 976, 977, 978, 979, 980, 982, 984, 987, 989, 993, 994, 995]\n", + "Number of Entries passing F1: 2\n", + "Condition '. at 0x118654680>' applied. True at indices: []\n", + "Number of Entries passing F2: 2\n" + ] + } + ], + "source": [ + "\n", + "# Examples:\n", + "F1=in_range(0,10)\n", + "F2=in_range(10,20)\n", + "\n", + "# Test of in_range\n", + "print (F1(0), F1(1), F1(10), F1(15), F1(20))\n", + "print (F2(0), F2(1), F2(10), F2(15), F2(20))\n", + "\n", + "print (\"Number of Entries passing F1:\", len(where(data,F1))) # Testing in_range on the data from exercise 5\n", + "print (\"Number of Entries passing F2:\", len(where(data,F2)))\n" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Condition '. at 0x118654720>' applied. True at indices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n", + "Condition '. at 0x118654540>' applied. True at indices: [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n", + "Condition '. at 0x1186547c0>' applied. True at indices: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48]\n", + "Condition '. at 0x118654860>' applied. True at indices: [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49]\n", + "Condition '. at 0x118654900>' applied. True at indices: [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]\n", + "Condition '. at 0x1186549a0>' applied. True at indices: [0, 1, 2, 3, 4]\n", + "Condition '. at 0x118654a40>' applied. True at indices: [3]\n", + "Condition '. at 0x118654ae0>' applied. True at indices: [0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48]\n" + ] + } + ], + "source": [ + "F3 = apply_conditions(new_data) # Testing the new conditions from exercise 6 on a list of integers" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*Exercise 7:* Repeat the previous exercise using `lambda` and the built-in python functions sum and map instead of your solution above. " + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [], + "source": [ + "### BEGIN SOLUTION\n", + "\n", + "def where(mylist, myfunc):\n", + " # Apply the condition function using map()\n", + " bool_list = list(map(myfunc, mylist))\n", + "\n", + " # Use map() to get indices and filter out False values\n", + " indices = list(map(lambda pair: pair[0], filter(lambda pair: pair[1], enumerate(bool_list))))\n", + "\n", + " # Use sum() to count how many True values exist\n", + " true_count = sum(bool_list) # This works because True = 1 and False = 0 so the sum is of how many values in my list satisfy the conditions\n", + " print(f\"Elements in mylist sastifysing condition: {true_count}\")\n", + " print(f\"Condition applied. True at indices: {indices}\")\n", + " return indices, true_count \n", + " \n", + "### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Elements in mylist sastifysing condition: 30\n", + "Condition applied. True at indices: [1, 2, 4, 7, 8, 13, 16, 17, 18, 20, 21, 23, 24, 25, 26, 27, 28, 29, 30, 32, 34, 35, 38, 39, 42, 43, 44, 46, 47, 48]\n" + ] + } + ], + "source": [ + "data2 = generate_uniform(50,-10,10)\n", + "test_2 = where(data2, F1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Monte Carlo\n", + "\n", + "*Exercise 7:* Write a \"generator\" function called `generate_function(func,x_min,x_max,N)`, that instead of generating a flat distribution, generates a distribution with functional form coded in `func`. Note that `func` will always be > 0. \n", + "\n", + "Use the test function below and your histogramming functions above to demonstrate that your generator is working properly.\n", + "\n", + "Hint: A simple, but slow, solution is to a draw random number `test_x` within the specified range and another number `p` between the `min` and `max` of the function (which you will have to determine). If `p<=function(test_x)`, then place `test_x` on the output. If not, repeat the process, drawing two new numbers. Repeat until you have the specified number of generated numbers, `N`. For this problem, it's OK to determine the `min` and `max` by numerically sampling the function. " + ] + }, + { + "cell_type": "code", + "execution_count": 115, + "metadata": {}, + "outputs": [], + "source": [ + "import math\n", + "def generate_function(func,x_min,x_max,N=1000):\n", + " ### BEGIN SOLUTION\n", + "\n", + " num_samples = 10000 # The more samples, the better approximation\n", + " step_size = (x_max - x_min) / num_samples\n", + " func_max = max(func(x_min + i * step_size) for i in range(num_samples))\n", + " samples = []\n", + " neg_count, pos_count = 0, 0\n", + " ## Sampling loop\n", + " while len(samples) < N:\n", + " if len(samples) % 2 == 0: # Alternate between negative and positive samples\n", + " test_x = -random.uniform(0, abs(x_min)) # Sample from negative side\n", + " else:\n", + " test_x = random.uniform(0, x_max) # Sample from positive side\n", + " \n", + " p = random.uniform(0, func_max) # Generate probability\n", + "\n", + " if p <= func(test_x): # Accept sample if condition is met\n", + " samples.append(test_x)\n", + "\n", + " return samples # Return generated values \n", + " \n", + " ### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 101, + "metadata": {}, + "outputs": [], + "source": [ + "# A test function\n", + "def test_func(x,a=1,b=1):\n", + " return abs(a*x+b)" + ] + }, + { + "cell_type": "code", + "execution_count": 116, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Bin 1: [-2.0, -1.8] : ###################\n", + "Bin 2: [-1.8, -1.6] : ###############\n", + "Bin 3: [-1.6, -1.4] : ##########\n", + "Bin 4: [-1.4, -1.2] : #######\n", + "Bin 5: [-1.2, -1.0] : ##\n", + "Bin 6: [-1.0, -0.8] : ##\n", + "Bin 7: [-0.8, -0.6] : ######\n", + "Bin 8: [-0.6, -0.4] : ##########\n", + "Bin 9: [-0.4, -0.2] : ###############\n", + "Bin 10: [-0.2, 0.0] : ####################\n", + "Bin 11: [0.0, 0.2] : ######\n", + "Bin 12: [0.2, 0.4] : #######\n", + "Bin 13: [0.4, 0.6] : #######\n", + "Bin 14: [0.6, 0.8] : #########\n", + "Bin 15: [0.8, 1.0] : ###########\n", + "Bin 16: [1.0, 1.2] : ###########\n", + "Bin 17: [1.2, 1.4] : ############\n", + "Bin 18: [1.4, 1.6] : ##############\n", + "Bin 19: [1.6, 1.8] : ###############\n", + "Bin 20: [1.8, 2.0] : ###############\n" + ] + }, + { + "data": { + "text/plain": [ + "([884,\n", + " 685,\n", + " 495,\n", + " 330,\n", + " 104,\n", + " 115,\n", + " 306,\n", + " 477,\n", + " 703,\n", + " 901,\n", + " 289,\n", + " 327,\n", + " 360,\n", + " 445,\n", + " 500,\n", + " 499,\n", + " 559,\n", + " 633,\n", + " 702,\n", + " 686],\n", + " [-2.0,\n", + " -1.8,\n", + " -1.6,\n", + " -1.4,\n", + " -1.2,\n", + " -1.0,\n", + " -0.7999999999999998,\n", + " -0.5999999999999999,\n", + " -0.3999999999999999,\n", + " -0.19999999999999996,\n", + " 0.0,\n", + " 0.20000000000000018,\n", + " 0.40000000000000036,\n", + " 0.6000000000000001,\n", + " 0.8000000000000003,\n", + " 1.0,\n", + " 1.2000000000000002,\n", + " 1.4000000000000004,\n", + " 1.6,\n", + " 1.8000000000000003,\n", + " 2.0])" + ] + }, + "execution_count": 116, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Define parameters\n", + "x_min, x_max = -2, 2 # Range of x values\n", + "N = 10000 # Number of samples\n", + "\n", + "# Generate samples using test_func\n", + "generated_data = generate_function(lambda x: test_func(x, a=1, b=1), x_min, x_max, N)\n", + "\n", + "# Use histogram function from Exercise 6 to visualize results\n", + "draw_histogram(generated_data, n_bins=20, x_min=x_min, x_max=x_max)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*Exercise 8:* Use your function to generate 1000 numbers that are normal distributed, using the `gaussian` function below. Confirm the mean and variance of the data is close to the mean and variance you specify when building the Gaussian. Histogram the data. " + ] + }, + { + "cell_type": "code", + "execution_count": 117, + "metadata": {}, + "outputs": [], + "source": [ + "import math\n", + "\n", + "def gaussian(mean, sigma):\n", + " def f(x):\n", + " return math.exp(-((x-mean)**2)/(2*sigma**2))/math.sqrt(2*math.pi*sigma) #This function is incorrect and was causing my mean to be off\n", + " return f\n", + "\n", + "# Example Instantiation\n", + "g1=gaussian(0,1)\n", + "g2=gaussian(10,3)" + ] + }, + { + "cell_type": "code", + "execution_count": 119, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Bin 1: [-2.0, -1.8] : ######\n", + "Bin 2: [-1.8, -1.6] : ####\n", + "Bin 3: [-1.6, -1.4] : ####\n", + "Bin 4: [-1.4, -1.2] : #######\n", + "Bin 5: [-1.2, -1.0] : #########\n", + "Bin 6: [-1.0, -0.8] : ############\n", + "Bin 7: [-0.8, -0.6] : ##################\n", + "Bin 8: [-0.6, -0.4] : ##################\n", + "Bin 9: [-0.4, -0.2] : ###################\n", + "Bin 10: [-0.2, 0.0] : ####################\n", + "Bin 11: [0.0, 0.2] : ###################\n", + "Bin 12: [0.2, 0.4] : #################\n", + "Bin 13: [0.4, 0.6] : ##################\n", + "Bin 14: [0.6, 0.8] : ##############\n", + "Bin 15: [0.8, 1.0] : ############\n", + "Bin 16: [1.0, 1.2] : ############\n", + "Bin 17: [1.2, 1.4] : ########\n", + "Bin 18: [1.4, 1.6] : #######\n", + "Bin 19: [1.6, 1.8] : ####\n", + "Bin 20: [1.8, 2.0] : ####\n", + "([28, 19, 19, 30, 39, 52, 75, 76, 79, 83, 79, 74, 76, 62, 52, 52, 37, 31, 19, 18], [-2.0, -1.8, -1.6, -1.4, -1.2, -1.0, -0.7999999999999998, -0.5999999999999999, -0.3999999999999999, -0.19999999999999996, 0.0, 0.20000000000000018, 0.40000000000000036, 0.6000000000000001, 0.8000000000000003, 1.0, 1.2000000000000002, 1.4000000000000004, 1.6, 1.8000000000000003, 2.0])\n", + "Bin 1: [-2.0, -1.8] : ##\n", + "Bin 2: [-1.8, -1.6] : ###\n", + "Bin 3: [-1.6, -1.4] : ####\n", + "Bin 4: [-1.4, -1.2] : #####\n", + "Bin 5: [-1.2, -1.0] : ######\n", + "Bin 6: [-1.0, -0.8] : #######\n", + "Bin 7: [-0.8, -0.6] : #########\n", + "Bin 8: [-0.6, -0.4] : ############\n", + "Bin 9: [-0.4, -0.2] : ################\n", + "Bin 10: [-0.2, 0.0] : ####################\n", + "Bin 11: [0.0, 0.2] : ###\n", + "Bin 12: [0.2, 0.4] : ####\n", + "Bin 13: [0.4, 0.6] : ####\n", + "Bin 14: [0.6, 0.8] : #####\n", + "Bin 15: [0.8, 1.0] : ########\n", + "Bin 16: [1.0, 1.2] : #########\n", + "Bin 17: [1.2, 1.4] : ##########\n", + "Bin 18: [1.4, 1.6] : ############\n", + "Bin 19: [1.6, 1.8] : ############\n", + "Bin 20: [1.8, 2.0] : #################\n", + "([12, 22, 26, 33, 36, 45, 53, 69, 91, 113, 17, 24, 28, 29, 47, 55, 60, 73, 69, 98], [-2.0, -1.8, -1.6, -1.4, -1.2, -1.0, -0.7999999999999998, -0.5999999999999999, -0.3999999999999999, -0.19999999999999996, 0.0, 0.20000000000000018, 0.40000000000000036, 0.6000000000000001, 0.8000000000000003, 1.0, 1.2000000000000002, 1.4000000000000004, 1.6, 1.8000000000000003, 2.0])\n" + ] + } + ], + "source": [ + "\n", + "# Define sampling range\n", + "x_min, x_max = -2, 2\n", + "N = 1000 # Number of samples\n", + "\n", + "# Generate samples using rejection sampling\n", + "new_generated_data = generate_function(g1, x_min, x_max, N)\n", + "new_generated_data2 = generate_function(g2, x_min, x_max, N)\n", + "\n", + "# Histogram the generated data\n", + "print(draw_histogram(new_generated_data, n_bins=20, x_min=x_min, x_max=x_max))\n", + "print(draw_histogram(new_generated_data2, n_bins=20, x_min=x_min, x_max=x_max))" + ] + }, + { + "cell_type": "code", + "execution_count": 120, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Min Sampled Value: -1.9998421812361133\n", + "Max Sampled Value: 1.9992118524749172\n", + "Sampled Mean (Raw Data): 0.08086419771253543\n", + "Negative Values: 5000, Positive Values: 5000\n", + "Expected Mean: 0, Sample Mean: 0.0809\n", + "Expected Variance: 1, Sample Variance: 1.5647\n" + ] + } + ], + "source": [ + "print(\"Min Sampled Value:\", min(generated_data))\n", + "print(\"Max Sampled Value:\", max(generated_data))\n", + "print(\"Sampled Mean (Raw Data):\", sum(generated_data) / len(generated_data))\n", + "\n", + "# Count how many values are in negative vs. positive range\n", + "neg_count = sum(1 for x in generated_data if x < 0)\n", + "pos_count = sum(1 for x in generated_data if x > 0)\n", + "\n", + "print(f\"Negative Values: {neg_count}, Positive Values: {pos_count}\")\n", + "# Compute sample mean and variance\n", + "sample_mean = sum(generated_data) / len(generated_data)\n", + "sample_variance = sum((x - sample_mean) ** 2 for x in generated_data) / len(generated_data)\n", + "\n", + "# Expected values\n", + "expected_mean = 0\n", + "expected_variance = 1 # Because sigma^2 = 1^2 = 1\n", + "\n", + "# Print results\n", + "print(f\"Expected Mean: {expected_mean}, Sample Mean: {sample_mean:.4f}\")\n", + "print(f\"Expected Variance: {expected_variance}, Sample Variance: {sample_variance:.4f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*Exercise 9:* Combine your `generate_function`, `where`, and `in_range` functions above to create an integrate function. Use your integrate function to show that approximately 68% of Normal distribution is within one variance." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import random\n", + "import math\n", + "\n", + "def integrate(func, x_min, x_max, N, condition):\n", + " \"\"\"\n", + " Estimates the probability of a function satisfying a condition using Monte Carlo Integration.\n", + "\n", + " Parameters:\n", + " - func: The probability density function (e.g., Gaussian)\n", + " - x_min, x_max: The range of values to sample from\n", + " - N: Number of samples\n", + " - condition: A function that returns True if a sample satisfies the condition\n", + "\n", + " Returns:\n", + " - The estimated probability of the condition being met\n", + " \"\"\"\n", + " # Generate random samples using rejection sampling\n", + " generated_data = generate_function(func, x_min, x_max, N)\n", + "\n", + " # Count how many samples fall within the given condition\n", + " indices, _ = where(generated_data, condition) # Get indices where condition is met\n", + " proportion = len(indices) / N # Estimate probability\n", + "\n", + " return proportion" + ] + }, + { + "cell_type": "code", + "execution_count": 125, + "metadata": {}, + "outputs": [], + "source": [ + "def integrate(func, x_min, x_max, n_points, condition):\n", + " generated_data = generate_function(func, x_min, x_max, N)\n", + "\n", + " # Count how many samples fall within the given condition\n", + " indices, _ = where(generated_data, condition) # Get indices where condition is met\n", + " integral = len(indices) / N # Estimate probability\n", + " return integral" + ] + }, + { + "cell_type": "code", + "execution_count": 133, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Elements in mylist sastifysing condition: 6834\n", + "Condition applied. True at indices: [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 20, 21, 22, 24, 25, 26, 28, 30, 31, 32, 33, 35, 38, 39, 42, 43, 45, 46, 48, 49, 50, 52, 55, 56, 57, 58, 60, 63, 64, 66, 67, 68, 69, 71, 72, 73, 74, 75, 77, 79, 80, 81, 83, 84, 86, 87, 89, 90, 92, 93, 95, 98, 99, 100, 101, 102, 105, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 122, 123, 124, 126, 127, 128, 129, 130, 131, 132, 133, 135, 137, 138, 139, 140, 142, 144, 145, 146, 148, 149, 150, 151, 152, 154, 155, 158, 159, 161, 162, 164, 166, 168, 169, 170, 171, 172, 175, 176, 177, 178, 179, 180, 183, 184, 185, 186, 188, 190, 191, 192, 193, 195, 196, 197, 198, 199, 201, 202, 203, 207, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 221, 222, 223, 225, 226, 227, 228, 230, 233, 234, 236, 238, 239, 241, 242, 243, 244, 247, 248, 251, 254, 255, 256, 257, 258, 259, 260, 262, 263, 264, 265, 266, 267, 269, 270, 271, 273, 274, 276, 277, 279, 280, 281, 282, 283, 284, 285, 287, 288, 289, 290, 291, 293, 294, 295, 298, 299, 300, 302, 303, 304, 305, 306, 307, 308, 309, 310, 312, 313, 317, 318, 319, 320, 322, 323, 325, 326, 327, 328, 331, 332, 334, 336, 338, 339, 340, 341, 343, 347, 348, 354, 356, 357, 359, 362, 363, 364, 365, 366, 367, 368, 369, 371, 374, 375, 376, 377, 378, 379, 380, 381, 383, 385, 387, 388, 391, 392, 394, 395, 398, 399, 400, 401, 402, 405, 406, 407, 408, 409, 410, 412, 415, 416, 417, 419, 420, 421, 424, 425, 427, 428, 429, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 444, 446, 447, 448, 449, 450, 451, 452, 455, 457, 458, 459, 460, 462, 463, 464, 466, 467, 468, 470, 472, 473, 474, 475, 478, 480, 481, 483, 484, 485, 486, 487, 488, 489, 492, 493, 494, 495, 496, 499, 503, 504, 506, 507, 509, 511, 512, 514, 515, 516, 518, 522, 523, 525, 527, 529, 531, 532, 534, 537, 538, 542, 545, 546, 548, 549, 551, 552, 553, 554, 555, 556, 557, 558, 559, 561, 562, 563, 565, 568, 570, 572, 573, 574, 575, 577, 578, 581, 583, 585, 588, 590, 591, 592, 594, 596, 598, 599, 601, 603, 604, 606, 607, 608, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 624, 626, 627, 628, 629, 630, 633, 634, 638, 639, 641, 644, 646, 647, 648, 649, 650, 651, 652, 653, 655, 656, 658, 661, 662, 664, 665, 666, 670, 672, 673, 674, 675, 676, 677, 678, 680, 682, 683, 684, 686, 687, 689, 690, 691, 692, 693, 694, 695, 696, 698, 699, 700, 702, 703, 704, 705, 706, 707, 710, 714, 715, 718, 719, 720, 721, 722, 724, 725, 726, 727, 728, 729, 733, 734, 736, 737, 738, 740, 741, 743, 744, 745, 746, 748, 750, 751, 752, 753, 754, 755, 756, 759, 761, 762, 763, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 788, 789, 790, 791, 793, 794, 795, 796, 799, 800, 801, 802, 804, 805, 806, 808, 809, 810, 812, 813, 814, 815, 817, 818, 820, 821, 822, 823, 825, 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840, 843, 844, 845, 846, 848, 852, 853, 854, 855, 858, 859, 860, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, 874, 878, 879, 880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 893, 894, 895, 896, 897, 898, 899, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 929, 930, 931, 932, 934, 936, 937, 938, 939, 940, 941, 942, 943, 945, 946, 949, 952, 953, 955, 956, 957, 959, 961, 962, 965, 966, 967, 968, 970, 973, 974, 975, 977, 978, 979, 980, 982, 984, 986, 987, 991, 992, 999, 1000, 1002, 1003, 1004, 1005, 1007, 1009, 1011, 1012, 1015, 1016, 1018, 1019, 1020, 1021, 1022, 1023, 1024, 1026, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1036, 1038, 1040, 1042, 1044, 1045, 1047, 1048, 1050, 1051, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1061, 1062, 1064, 1065, 1066, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1082, 1084, 1086, 1087, 1088, 1089, 1090, 1091, 1095, 1096, 1097, 1098, 1100, 1102, 1103, 1104, 1105, 1109, 1111, 1114, 1115, 1116, 1118, 1119, 1120, 1121, 1122, 1123, 1124, 1125, 1128, 1129, 1130, 1131, 1132, 1133, 1134, 1135, 1137, 1139, 1142, 1143, 1144, 1146, 1147, 1148, 1151, 1153, 1154, 1156, 1158, 1159, 1160, 1161, 1162, 1163, 1165, 1166, 1167, 1168, 1169, 1170, 1171, 1172, 1173, 1175, 1178, 1181, 1182, 1184, 1188, 1189, 1190, 1191, 1192, 1193, 1194, 1196, 1197, 1198, 1199, 1200, 1201, 1203, 1204, 1205, 1206, 1207, 1209, 1210, 1211, 1212, 1213, 1215, 1218, 1220, 1222, 1224, 1226, 1227, 1228, 1229, 1233, 1235, 1236, 1237, 1238, 1239, 1240, 1241, 1243, 1244, 1246, 1247, 1248, 1249, 1250, 1252, 1253, 1254, 1255, 1258, 1259, 1260, 1261, 1263, 1266, 1268, 1271, 1272, 1274, 1275, 1276, 1277, 1278, 1279, 1280, 1282, 1283, 1284, 1285, 1286, 1288, 1289, 1290, 1291, 1292, 1293, 1294, 1295, 1296, 1297, 1298, 1300, 1301, 1303, 1305, 1306, 1307, 1309, 1310, 1313, 1314, 1315, 1316, 1318, 1319, 1320, 1322, 1323, 1325, 1326, 1328, 1329, 1332, 1334, 1335, 1337, 1338, 1339, 1341, 1342, 1344, 1345, 1347, 1348, 1351, 1352, 1353, 1354, 1355, 1358, 1359, 1360, 1362, 1363, 1364, 1365, 1366, 1367, 1368, 1369, 1370, 1371, 1372, 1373, 1375, 1376, 1380, 1382, 1385, 1386, 1387, 1388, 1391, 1392, 1394, 1395, 1396, 1397, 1398, 1399, 1401, 1402, 1403, 1404, 1405, 1407, 1408, 1411, 1412, 1414, 1415, 1416, 1417, 1418, 1419, 1420, 1421, 1422, 1423, 1424, 1425, 1427, 1428, 1431, 1432, 1433, 1434, 1435, 1436, 1437, 1438, 1440, 1441, 1442, 1444, 1445, 1448, 1449, 1450, 1452, 1453, 1454, 1457, 1460, 1461, 1463, 1464, 1465, 1466, 1467, 1469, 1470, 1473, 1476, 1477, 1478, 1479, 1481, 1482, 1483, 1485, 1487, 1489, 1490, 1491, 1492, 1493, 1494, 1496, 1497, 1498, 1499, 1500, 1501, 1502, 1503, 1505, 1508, 1509, 1510, 1511, 1512, 1513, 1515, 1517, 1518, 1519, 1520, 1522, 1523, 1524, 1525, 1527, 1528, 1529, 1531, 1532, 1533, 1534, 1535, 1536, 1537, 1538, 1539, 1540, 1541, 1542, 1543, 1545, 1546, 1547, 1548, 1549, 1550, 1551, 1555, 1556, 1557, 1558, 1559, 1561, 1562, 1563, 1564, 1565, 1570, 1571, 1572, 1573, 1574, 1575, 1576, 1577, 1578, 1579, 1580, 1582, 1585, 1587, 1588, 1589, 1590, 1591, 1592, 1593, 1595, 1596, 1599, 1601, 1602, 1603, 1604, 1606, 1608, 1609, 1610, 1615, 1616, 1617, 1618, 1619, 1620, 1621, 1622, 1626, 1629, 1630, 1631, 1632, 1633, 1636, 1637, 1638, 1640, 1643, 1644, 1646, 1647, 1649, 1651, 1652, 1653, 1657, 1659, 1660, 1662, 1664, 1665, 1666, 1667, 1668, 1669, 1673, 1674, 1675, 1677, 1678, 1680, 1681, 1685, 1687, 1688, 1689, 1690, 1691, 1692, 1693, 1695, 1696, 1697, 1698, 1699, 1700, 1702, 1703, 1704, 1705, 1706, 1707, 1709, 1710, 1713, 1714, 1715, 1716, 1717, 1719, 1720, 1721, 1722, 1723, 1724, 1725, 1727, 1728, 1730, 1731, 1734, 1735, 1736, 1737, 1739, 1740, 1741, 1742, 1743, 1746, 1747, 1748, 1750, 1752, 1754, 1755, 1757, 1759, 1762, 1764, 1765, 1769, 1771, 1772, 1774, 1775, 1776, 1778, 1779, 1780, 1781, 1782, 1783, 1784, 1786, 1787, 1788, 1789, 1790, 1791, 1792, 1793, 1794, 1796, 1798, 1800, 1801, 1802, 1803, 1804, 1805, 1806, 1807, 1810, 1811, 1812, 1815, 1816, 1818, 1820, 1821, 1822, 1823, 1824, 1825, 1826, 1830, 1831, 1832, 1833, 1835, 1836, 1837, 1840, 1842, 1843, 1844, 1845, 1846, 1847, 1850, 1851, 1852, 1854, 1855, 1856, 1857, 1858, 1859, 1860, 1861, 1863, 1864, 1865, 1866, 1867, 1868, 1869, 1870, 1872, 1874, 1876, 1878, 1879, 1880, 1882, 1884, 1887, 1888, 1889, 1890, 1891, 1892, 1893, 1894, 1895, 1898, 1900, 1904, 1905, 1906, 1907, 1908, 1909, 1911, 1915, 1916, 1917, 1918, 1919, 1920, 1921, 1922, 1923, 1924, 1925, 1927, 1929, 1930, 1931, 1932, 1934, 1935, 1938, 1939, 1940, 1944, 1945, 1946, 1947, 1948, 1949, 1950, 1953, 1954, 1955, 1958, 1960, 1961, 1962, 1963, 1965, 1966, 1967, 1972, 1973, 1975, 1977, 1979, 1983, 1984, 1985, 1990, 1992, 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2004, 2006, 2009, 2010, 2012, 2013, 2014, 2015, 2017, 2018, 2019, 2020, 2021, 2023, 2024, 2025, 2026, 2029, 2030, 2032, 2034, 2035, 2036, 2037, 2038, 2039, 2040, 2042, 2045, 2047, 2048, 2049, 2050, 2052, 2053, 2055, 2056, 2057, 2059, 2061, 2062, 2063, 2064, 2065, 2067, 2068, 2069, 2070, 2071, 2073, 2074, 2075, 2076, 2079, 2081, 2082, 2085, 2086, 2088, 2089, 2090, 2092, 2094, 2097, 2098, 2099, 2100, 2101, 2102, 2104, 2107, 2108, 2109, 2111, 2113, 2115, 2116, 2118, 2119, 2120, 2122, 2124, 2125, 2126, 2128, 2130, 2131, 2132, 2133, 2135, 2136, 2139, 2140, 2142, 2143, 2148, 2149, 2151, 2152, 2153, 2154, 2155, 2156, 2157, 2158, 2160, 2161, 2163, 2164, 2165, 2167, 2172, 2174, 2175, 2176, 2178, 2179, 2180, 2181, 2182, 2183, 2184, 2185, 2191, 2192, 2193, 2195, 2198, 2199, 2200, 2202, 2203, 2204, 2205, 2208, 2209, 2210, 2211, 2213, 2214, 2215, 2216, 2217, 2220, 2221, 2222, 2223, 2224, 2225, 2226, 2230, 2231, 2233, 2235, 2238, 2239, 2241, 2242, 2244, 2245, 2247, 2248, 2250, 2252, 2254, 2255, 2259, 2261, 2262, 2269, 2271, 2272, 2274, 2275, 2277, 2278, 2279, 2280, 2281, 2282, 2283, 2286, 2288, 2290, 2291, 2292, 2293, 2296, 2297, 2298, 2299, 2300, 2301, 2302, 2304, 2305, 2306, 2308, 2309, 2310, 2311, 2313, 2314, 2317, 2318, 2321, 2322, 2323, 2325, 2328, 2329, 2330, 2331, 2332, 2334, 2335, 2337, 2338, 2341, 2342, 2343, 2345, 2346, 2347, 2350, 2352, 2353, 2354, 2355, 2358, 2359, 2361, 2364, 2365, 2366, 2368, 2370, 2371, 2373, 2375, 2376, 2377, 2378, 2379, 2380, 2381, 2383, 2384, 2385, 2387, 2388, 2389, 2390, 2391, 2395, 2396, 2398, 2399, 2400, 2401, 2402, 2407, 2408, 2411, 2412, 2413, 2415, 2417, 2422, 2425, 2426, 2427, 2428, 2430, 2432, 2433, 2434, 2437, 2441, 2442, 2443, 2446, 2447, 2449, 2450, 2451, 2453, 2454, 2456, 2458, 2459, 2460, 2461, 2462, 2463, 2464, 2467, 2468, 2469, 2473, 2475, 2480, 2481, 2482, 2483, 2484, 2485, 2486, 2487, 2488, 2489, 2490, 2491, 2492, 2494, 2495, 2496, 2497, 2498, 2499, 2500, 2501, 2505, 2506, 2507, 2508, 2510, 2511, 2512, 2513, 2514, 2515, 2516, 2517, 2518, 2519, 2521, 2523, 2525, 2526, 2528, 2529, 2530, 2531, 2532, 2533, 2534, 2538, 2542, 2548, 2549, 2550, 2552, 2553, 2555, 2558, 2559, 2560, 2561, 2562, 2563, 2564, 2565, 2566, 2567, 2568, 2569, 2570, 2572, 2573, 2574, 2575, 2576, 2577, 2578, 2579, 2580, 2581, 2582, 2588, 2589, 2591, 2592, 2594, 2598, 2599, 2602, 2604, 2605, 2609, 2611, 2614, 2615, 2616, 2617, 2618, 2619, 2621, 2623, 2625, 2626, 2628, 2629, 2630, 2631, 2632, 2634, 2635, 2636, 2637, 2638, 2640, 2641, 2644, 2645, 2647, 2649, 2650, 2654, 2655, 2656, 2658, 2659, 2660, 2661, 2662, 2663, 2664, 2665, 2666, 2667, 2668, 2669, 2670, 2672, 2675, 2676, 2680, 2682, 2685, 2687, 2689, 2691, 2694, 2695, 2696, 2698, 2701, 2703, 2704, 2705, 2706, 2707, 2709, 2710, 2711, 2713, 2714, 2716, 2717, 2719, 2720, 2721, 2724, 2727, 2728, 2729, 2730, 2734, 2735, 2736, 2737, 2738, 2739, 2741, 2742, 2743, 2744, 2746, 2748, 2750, 2751, 2752, 2753, 2754, 2755, 2756, 2757, 2758, 2759, 2760, 2761, 2763, 2765, 2768, 2769, 2770, 2771, 2772, 2774, 2775, 2776, 2777, 2778, 2779, 2780, 2781, 2784, 2787, 2788, 2791, 2793, 2796, 2797, 2798, 2800, 2801, 2802, 2803, 2804, 2807, 2809, 2810, 2811, 2812, 2813, 2814, 2815, 2816, 2819, 2821, 2822, 2823, 2824, 2825, 2826, 2827, 2829, 2830, 2831, 2835, 2836, 2837, 2838, 2842, 2844, 2845, 2846, 2847, 2848, 2850, 2852, 2853, 2854, 2855, 2856, 2858, 2859, 2860, 2861, 2863, 2864, 2865, 2866, 2870, 2871, 2874, 2875, 2877, 2878, 2879, 2881, 2883, 2884, 2885, 2886, 2887, 2888, 2890, 2891, 2896, 2897, 2898, 2901, 2902, 2903, 2904, 2905, 2906, 2907, 2908, 2909, 2910, 2911, 2912, 2913, 2915, 2917, 2920, 2922, 2923, 2925, 2926, 2927, 2928, 2930, 2933, 2934, 2936, 2939, 2941, 2944, 2945, 2946, 2947, 2949, 2950, 2951, 2952, 2953, 2954, 2955, 2956, 2957, 2958, 2959, 2962, 2964, 2966, 2969, 2970, 2972, 2974, 2975, 2979, 2980, 2982, 2983, 2985, 2986, 2987, 2988, 2990, 2993, 2994, 2997, 2999, 3000, 3001, 3003, 3004, 3005, 3006, 3007, 3008, 3009, 3011, 3013, 3015, 3016, 3017, 3018, 3019, 3021, 3022, 3023, 3024, 3025, 3026, 3027, 3028, 3029, 3030, 3031, 3035, 3037, 3041, 3042, 3044, 3045, 3048, 3050, 3051, 3052, 3054, 3055, 3056, 3057, 3059, 3060, 3061, 3062, 3063, 3065, 3066, 3067, 3068, 3071, 3072, 3073, 3074, 3075, 3079, 3081, 3082, 3083, 3084, 3086, 3089, 3092, 3093, 3095, 3096, 3097, 3098, 3099, 3100, 3101, 3103, 3104, 3105, 3106, 3107, 3108, 3109, 3111, 3112, 3114, 3116, 3117, 3118, 3119, 3120, 3121, 3122, 3128, 3130, 3131, 3132, 3133, 3134, 3136, 3137, 3139, 3141, 3142, 3143, 3144, 3145, 3146, 3147, 3148, 3149, 3151, 3152, 3153, 3154, 3155, 3157, 3159, 3160, 3162, 3163, 3165, 3166, 3168, 3170, 3171, 3172, 3173, 3176, 3177, 3178, 3179, 3181, 3182, 3184, 3185, 3186, 3188, 3189, 3190, 3192, 3193, 3194, 3195, 3196, 3197, 3198, 3199, 3200, 3204, 3205, 3206, 3207, 3208, 3210, 3211, 3213, 3214, 3215, 3216, 3217, 3218, 3219, 3220, 3222, 3223, 3225, 3226, 3227, 3228, 3229, 3230, 3231, 3232, 3233, 3234, 3236, 3237, 3238, 3239, 3240, 3243, 3244, 3247, 3249, 3251, 3252, 3253, 3254, 3255, 3256, 3259, 3262, 3266, 3268, 3269, 3270, 3274, 3275, 3276, 3277, 3278, 3279, 3280, 3282, 3283, 3284, 3286, 3289, 3290, 3291, 3292, 3293, 3295, 3298, 3299, 3300, 3301, 3303, 3305, 3306, 3307, 3309, 3310, 3311, 3312, 3314, 3315, 3317, 3318, 3319, 3320, 3321, 3322, 3323, 3324, 3325, 3327, 3328, 3329, 3332, 3333, 3335, 3336, 3337, 3338, 3339, 3341, 3342, 3344, 3345, 3346, 3347, 3348, 3349, 3354, 3355, 3357, 3359, 3360, 3361, 3362, 3363, 3364, 3365, 3366, 3367, 3368, 3370, 3371, 3372, 3373, 3376, 3379, 3380, 3382, 3383, 3385, 3386, 3387, 3389, 3390, 3391, 3395, 3396, 3397, 3399, 3401, 3403, 3404, 3405, 3407, 3408, 3409, 3410, 3411, 3412, 3414, 3415, 3416, 3417, 3418, 3419, 3421, 3422, 3423, 3425, 3427, 3428, 3429, 3430, 3431, 3432, 3433, 3434, 3435, 3438, 3439, 3440, 3441, 3442, 3443, 3444, 3445, 3446, 3447, 3448, 3451, 3452, 3453, 3456, 3458, 3460, 3467, 3470, 3472, 3475, 3476, 3477, 3482, 3484, 3485, 3486, 3487, 3488, 3489, 3492, 3493, 3494, 3495, 3496, 3497, 3498, 3499, 3500, 3501, 3502, 3503, 3505, 3506, 3507, 3508, 3509, 3510, 3511, 3512, 3513, 3516, 3518, 3519, 3520, 3521, 3522, 3523, 3524, 3525, 3527, 3528, 3529, 3530, 3531, 3532, 3533, 3534, 3535, 3537, 3538, 3539, 3540, 3541, 3543, 3545, 3546, 3547, 3548, 3549, 3550, 3552, 3553, 3554, 3555, 3556, 3557, 3559, 3560, 3561, 3562, 3563, 3564, 3565, 3566, 3567, 3569, 3570, 3571, 3572, 3573, 3575, 3576, 3577, 3579, 3580, 3582, 3583, 3584, 3585, 3586, 3587, 3588, 3589, 3590, 3591, 3592, 3594, 3595, 3596, 3597, 3599, 3600, 3601, 3602, 3605, 3606, 3607, 3608, 3609, 3610, 3612, 3613, 3614, 3615, 3616, 3617, 3618, 3622, 3623, 3624, 3625, 3627, 3628, 3629, 3630, 3633, 3634, 3635, 3637, 3638, 3639, 3642, 3644, 3645, 3646, 3648, 3649, 3650, 3651, 3653, 3654, 3655, 3656, 3657, 3659, 3661, 3662, 3663, 3664, 3665, 3666, 3667, 3671, 3672, 3673, 3674, 3675, 3680, 3681, 3682, 3683, 3685, 3686, 3687, 3691, 3692, 3693, 3694, 3696, 3698, 3699, 3700, 3701, 3702, 3703, 3704, 3705, 3706, 3707, 3708, 3710, 3711, 3712, 3713, 3714, 3715, 3717, 3718, 3719, 3720, 3722, 3723, 3724, 3725, 3727, 3728, 3729, 3730, 3732, 3733, 3734, 3735, 3736, 3738, 3739, 3740, 3741, 3742, 3746, 3747, 3749, 3750, 3751, 3752, 3754, 3755, 3756, 3757, 3758, 3760, 3761, 3762, 3765, 3766, 3767, 3768, 3770, 3771, 3772, 3774, 3775, 3778, 3780, 3781, 3782, 3783, 3786, 3787, 3788, 3789, 3790, 3791, 3793, 3794, 3796, 3797, 3799, 3800, 3801, 3802, 3803, 3804, 3805, 3806, 3807, 3808, 3814, 3815, 3817, 3818, 3819, 3820, 3821, 3825, 3826, 3828, 3830, 3831, 3832, 3833, 3834, 3835, 3837, 3838, 3839, 3840, 3842, 3843, 3844, 3845, 3848, 3849, 3850, 3851, 3853, 3854, 3855, 3856, 3857, 3858, 3860, 3863, 3864, 3865, 3866, 3867, 3868, 3870, 3871, 3875, 3876, 3877, 3882, 3883, 3884, 3885, 3886, 3888, 3889, 3892, 3893, 3894, 3895, 3899, 3900, 3901, 3902, 3903, 3904, 3905, 3909, 3910, 3911, 3913, 3914, 3917, 3920, 3921, 3922, 3925, 3926, 3928, 3929, 3932, 3933, 3934, 3935, 3937, 3938, 3939, 3941, 3942, 3945, 3946, 3947, 3948, 3949, 3950, 3951, 3952, 3953, 3954, 3955, 3957, 3958, 3960, 3961, 3962, 3963, 3965, 3966, 3968, 3970, 3971, 3972, 3973, 3974, 3976, 3977, 3978, 3979, 3980, 3981, 3982, 3983, 3984, 3985, 3987, 3988, 3990, 3992, 3993, 3997, 3998, 4000, 4002, 4003, 4004, 4006, 4009, 4010, 4012, 4014, 4015, 4017, 4018, 4019, 4020, 4021, 4022, 4023, 4024, 4026, 4030, 4031, 4032, 4033, 4034, 4035, 4036, 4038, 4040, 4041, 4042, 4044, 4045, 4046, 4047, 4049, 4050, 4051, 4052, 4053, 4054, 4056, 4057, 4059, 4060, 4061, 4062, 4063, 4064, 4065, 4067, 4068, 4069, 4070, 4071, 4072, 4073, 4074, 4076, 4077, 4079, 4080, 4081, 4084, 4086, 4088, 4090, 4091, 4092, 4094, 4095, 4096, 4098, 4099, 4100, 4101, 4104, 4105, 4106, 4107, 4109, 4110, 4111, 4112, 4115, 4116, 4117, 4122, 4123, 4125, 4126, 4127, 4128, 4130, 4131, 4132, 4134, 4135, 4137, 4138, 4140, 4141, 4142, 4146, 4148, 4149, 4150, 4151, 4154, 4155, 4156, 4157, 4159, 4161, 4162, 4164, 4165, 4167, 4168, 4169, 4171, 4175, 4178, 4179, 4180, 4181, 4182, 4183, 4184, 4186, 4187, 4189, 4191, 4192, 4195, 4196, 4197, 4199, 4202, 4203, 4204, 4205, 4207, 4209, 4210, 4211, 4212, 4215, 4216, 4217, 4219, 4220, 4221, 4222, 4223, 4224, 4225, 4226, 4228, 4229, 4230, 4231, 4232, 4235, 4236, 4237, 4238, 4239, 4240, 4242, 4243, 4244, 4245, 4246, 4247, 4248, 4249, 4250, 4251, 4252, 4255, 4256, 4257, 4258, 4260, 4261, 4263, 4264, 4265, 4266, 4267, 4268, 4270, 4271, 4273, 4274, 4275, 4277, 4278, 4279, 4281, 4282, 4283, 4285, 4286, 4287, 4288, 4289, 4293, 4294, 4295, 4296, 4298, 4300, 4301, 4303, 4305, 4308, 4309, 4310, 4312, 4313, 4316, 4317, 4321, 4322, 4323, 4324, 4326, 4327, 4328, 4329, 4330, 4331, 4332, 4335, 4336, 4337, 4339, 4340, 4343, 4345, 4346, 4348, 4349, 4350, 4353, 4355, 4358, 4359, 4360, 4361, 4362, 4363, 4364, 4365, 4366, 4367, 4368, 4369, 4370, 4372, 4375, 4377, 4378, 4379, 4380, 4381, 4382, 4383, 4384, 4386, 4387, 4388, 4390, 4391, 4393, 4394, 4395, 4396, 4397, 4398, 4400, 4401, 4402, 4403, 4404, 4405, 4406, 4407, 4408, 4411, 4412, 4413, 4415, 4416, 4417, 4418, 4419, 4421, 4423, 4424, 4426, 4427, 4428, 4430, 4434, 4435, 4436, 4438, 4439, 4440, 4441, 4442, 4443, 4444, 4445, 4446, 4447, 4449, 4450, 4451, 4452, 4453, 4454, 4459, 4460, 4461, 4463, 4464, 4465, 4466, 4468, 4469, 4470, 4471, 4473, 4475, 4476, 4477, 4478, 4479, 4480, 4483, 4484, 4485, 4487, 4488, 4489, 4490, 4491, 4492, 4493, 4495, 4496, 4499, 4501, 4502, 4503, 4504, 4505, 4506, 4509, 4511, 4512, 4513, 4515, 4516, 4517, 4518, 4519, 4520, 4522, 4523, 4524, 4525, 4530, 4531, 4532, 4533, 4534, 4536, 4537, 4538, 4540, 4542, 4544, 4545, 4546, 4547, 4548, 4549, 4551, 4552, 4553, 4554, 4556, 4557, 4558, 4559, 4560, 4561, 4563, 4565, 4566, 4567, 4568, 4570, 4572, 4573, 4575, 4577, 4578, 4581, 4582, 4586, 4589, 4590, 4591, 4592, 4593, 4595, 4596, 4597, 4598, 4599, 4600, 4601, 4602, 4603, 4604, 4606, 4607, 4609, 4611, 4612, 4613, 4616, 4618, 4620, 4621, 4624, 4626, 4627, 4628, 4631, 4632, 4634, 4638, 4640, 4641, 4642, 4646, 4649, 4650, 4651, 4652, 4653, 4654, 4655, 4656, 4657, 4660, 4662, 4663, 4665, 4666, 4667, 4669, 4670, 4671, 4672, 4674, 4675, 4676, 4678, 4679, 4682, 4683, 4685, 4686, 4687, 4688, 4689, 4690, 4693, 4694, 4695, 4698, 4699, 4701, 4704, 4706, 4707, 4708, 4712, 4713, 4714, 4715, 4716, 4719, 4720, 4721, 4722, 4723, 4724, 4726, 4727, 4728, 4731, 4733, 4734, 4736, 4737, 4738, 4739, 4740, 4741, 4742, 4743, 4746, 4750, 4751, 4752, 4754, 4755, 4756, 4758, 4759, 4760, 4761, 4762, 4763, 4765, 4766, 4767, 4769, 4770, 4772, 4773, 4776, 4779, 4780, 4781, 4782, 4784, 4785, 4786, 4787, 4788, 4790, 4791, 4793, 4796, 4797, 4799, 4800, 4801, 4803, 4804, 4805, 4806, 4807, 4808, 4809, 4811, 4812, 4813, 4814, 4815, 4816, 4818, 4819, 4820, 4821, 4822, 4823, 4824, 4826, 4827, 4829, 4830, 4831, 4832, 4833, 4834, 4835, 4836, 4838, 4840, 4841, 4842, 4843, 4844, 4845, 4846, 4847, 4848, 4850, 4851, 4852, 4853, 4854, 4856, 4857, 4858, 4859, 4861, 4865, 4867, 4868, 4869, 4871, 4874, 4876, 4878, 4880, 4881, 4882, 4883, 4885, 4886, 4887, 4888, 4889, 4891, 4892, 4896, 4897, 4898, 4899, 4900, 4901, 4902, 4903, 4904, 4906, 4907, 4908, 4909, 4910, 4911, 4912, 4913, 4914, 4918, 4919, 4920, 4921, 4925, 4926, 4927, 4928, 4929, 4930, 4931, 4932, 4933, 4934, 4935, 4936, 4937, 4938, 4940, 4941, 4943, 4944, 4945, 4947, 4949, 4950, 4951, 4952, 4954, 4955, 4956, 4957, 4958, 4959, 4960, 4961, 4962, 4963, 4964, 4965, 4966, 4967, 4968, 4969, 4972, 4974, 4975, 4976, 4977, 4980, 4981, 4982, 4984, 4986, 4987, 4988, 4989, 4992, 4993, 4994, 4996, 4997, 4999, 5000, 5001, 5004, 5007, 5008, 5009, 5010, 5012, 5013, 5014, 5015, 5016, 5017, 5018, 5019, 5022, 5023, 5024, 5025, 5026, 5029, 5030, 5032, 5033, 5034, 5035, 5036, 5037, 5038, 5041, 5042, 5043, 5044, 5046, 5047, 5048, 5049, 5050, 5051, 5052, 5053, 5055, 5056, 5057, 5058, 5059, 5060, 5063, 5064, 5065, 5066, 5067, 5068, 5070, 5071, 5072, 5073, 5074, 5075, 5076, 5077, 5078, 5079, 5080, 5081, 5082, 5084, 5085, 5090, 5095, 5096, 5097, 5099, 5100, 5101, 5102, 5103, 5104, 5105, 5106, 5108, 5110, 5113, 5115, 5118, 5119, 5122, 5123, 5124, 5127, 5128, 5129, 5133, 5136, 5139, 5142, 5143, 5145, 5146, 5148, 5149, 5152, 5153, 5154, 5155, 5156, 5158, 5159, 5160, 5162, 5163, 5164, 5165, 5169, 5171, 5172, 5175, 5176, 5178, 5179, 5180, 5181, 5182, 5183, 5184, 5185, 5186, 5187, 5188, 5191, 5194, 5196, 5198, 5199, 5203, 5204, 5205, 5207, 5209, 5214, 5215, 5217, 5218, 5219, 5222, 5223, 5224, 5225, 5226, 5228, 5229, 5230, 5231, 5233, 5235, 5237, 5238, 5240, 5241, 5243, 5244, 5246, 5247, 5248, 5249, 5250, 5253, 5254, 5258, 5259, 5260, 5261, 5265, 5266, 5267, 5268, 5270, 5273, 5274, 5275, 5277, 5278, 5280, 5282, 5283, 5284, 5286, 5288, 5289, 5290, 5291, 5293, 5295, 5296, 5297, 5299, 5301, 5302, 5303, 5304, 5305, 5306, 5307, 5308, 5309, 5313, 5314, 5315, 5318, 5322, 5323, 5324, 5325, 5327, 5331, 5332, 5333, 5334, 5335, 5336, 5337, 5338, 5339, 5341, 5342, 5343, 5344, 5345, 5346, 5347, 5348, 5351, 5352, 5354, 5355, 5356, 5357, 5362, 5363, 5364, 5365, 5366, 5368, 5369, 5370, 5371, 5372, 5373, 5375, 5377, 5379, 5380, 5383, 5385, 5387, 5388, 5389, 5390, 5391, 5395, 5396, 5401, 5403, 5404, 5406, 5407, 5408, 5413, 5416, 5417, 5418, 5421, 5422, 5423, 5424, 5425, 5426, 5427, 5428, 5431, 5433, 5435, 5436, 5437, 5438, 5439, 5440, 5441, 5443, 5444, 5445, 5446, 5447, 5448, 5449, 5450, 5452, 5453, 5455, 5457, 5460, 5462, 5463, 5464, 5465, 5466, 5468, 5473, 5476, 5477, 5479, 5482, 5483, 5484, 5486, 5487, 5488, 5489, 5490, 5492, 5493, 5495, 5496, 5497, 5498, 5499, 5500, 5502, 5508, 5509, 5510, 5511, 5512, 5513, 5517, 5518, 5519, 5521, 5522, 5524, 5526, 5527, 5528, 5531, 5533, 5535, 5536, 5539, 5541, 5542, 5543, 5544, 5545, 5548, 5549, 5551, 5552, 5553, 5555, 5557, 5558, 5560, 5561, 5563, 5564, 5566, 5567, 5568, 5569, 5570, 5571, 5573, 5575, 5576, 5577, 5578, 5579, 5580, 5581, 5582, 5583, 5584, 5585, 5587, 5588, 5589, 5590, 5593, 5594, 5596, 5597, 5598, 5600, 5603, 5604, 5605, 5607, 5609, 5610, 5611, 5613, 5615, 5616, 5620, 5621, 5622, 5625, 5626, 5628, 5629, 5631, 5633, 5634, 5635, 5636, 5637, 5638, 5641, 5642, 5643, 5645, 5646, 5649, 5650, 5652, 5654, 5655, 5656, 5657, 5658, 5661, 5662, 5664, 5665, 5666, 5668, 5669, 5671, 5674, 5676, 5677, 5679, 5681, 5682, 5683, 5684, 5685, 5686, 5688, 5691, 5693, 5694, 5695, 5696, 5697, 5698, 5699, 5701, 5702, 5704, 5705, 5708, 5710, 5711, 5712, 5713, 5714, 5716, 5717, 5718, 5719, 5720, 5721, 5723, 5726, 5727, 5731, 5732, 5733, 5734, 5735, 5739, 5740, 5742, 5743, 5744, 5746, 5747, 5748, 5749, 5750, 5751, 5755, 5756, 5757, 5758, 5759, 5760, 5761, 5762, 5763, 5765, 5766, 5767, 5769, 5772, 5773, 5774, 5776, 5777, 5778, 5780, 5781, 5782, 5783, 5784, 5786, 5788, 5789, 5790, 5792, 5793, 5794, 5795, 5797, 5801, 5803, 5805, 5806, 5807, 5808, 5809, 5810, 5813, 5815, 5817, 5819, 5820, 5821, 5822, 5826, 5827, 5828, 5830, 5831, 5832, 5833, 5836, 5837, 5838, 5839, 5840, 5841, 5845, 5848, 5850, 5851, 5852, 5853, 5854, 5855, 5857, 5858, 5859, 5860, 5861, 5862, 5864, 5865, 5866, 5867, 5868, 5871, 5872, 5873, 5876, 5878, 5879, 5880, 5882, 5883, 5884, 5885, 5886, 5888, 5889, 5890, 5891, 5893, 5895, 5899, 5900, 5902, 5905, 5908, 5909, 5911, 5912, 5913, 5914, 5915, 5920, 5921, 5923, 5924, 5925, 5926, 5928, 5929, 5930, 5932, 5933, 5934, 5935, 5937, 5938, 5940, 5943, 5944, 5945, 5946, 5948, 5949, 5952, 5954, 5956, 5957, 5962, 5963, 5964, 5966, 5967, 5968, 5971, 5972, 5973, 5974, 5976, 5978, 5980, 5982, 5984, 5985, 5986, 5988, 5989, 5990, 5991, 5992, 5994, 5995, 5997, 5998, 5999, 6001, 6004, 6007, 6009, 6010, 6012, 6013, 6014, 6016, 6017, 6018, 6020, 6021, 6022, 6023, 6024, 6026, 6027, 6029, 6031, 6032, 6035, 6036, 6037, 6039, 6040, 6041, 6046, 6047, 6048, 6049, 6050, 6051, 6052, 6053, 6054, 6055, 6056, 6057, 6058, 6059, 6061, 6064, 6065, 6067, 6068, 6069, 6073, 6075, 6077, 6080, 6081, 6082, 6084, 6086, 6087, 6090, 6091, 6092, 6093, 6096, 6097, 6098, 6099, 6100, 6101, 6102, 6106, 6107, 6108, 6109, 6111, 6112, 6113, 6114, 6117, 6119, 6120, 6121, 6122, 6124, 6125, 6127, 6128, 6129, 6131, 6133, 6134, 6135, 6136, 6137, 6138, 6139, 6142, 6143, 6144, 6145, 6146, 6147, 6148, 6149, 6151, 6152, 6153, 6154, 6156, 6157, 6158, 6159, 6160, 6162, 6163, 6165, 6166, 6167, 6168, 6169, 6170, 6171, 6174, 6176, 6179, 6182, 6184, 6185, 6186, 6187, 6188, 6189, 6191, 6192, 6194, 6195, 6196, 6197, 6198, 6199, 6200, 6202, 6203, 6204, 6205, 6208, 6209, 6210, 6211, 6212, 6215, 6216, 6217, 6218, 6221, 6222, 6224, 6227, 6228, 6229, 6230, 6231, 6232, 6233, 6234, 6235, 6236, 6237, 6239, 6240, 6241, 6242, 6243, 6244, 6245, 6246, 6247, 6248, 6249, 6250, 6251, 6253, 6254, 6255, 6258, 6260, 6261, 6264, 6265, 6266, 6267, 6271, 6272, 6273, 6274, 6275, 6277, 6278, 6279, 6280, 6281, 6282, 6283, 6284, 6285, 6287, 6288, 6289, 6290, 6291, 6292, 6293, 6295, 6297, 6298, 6299, 6300, 6301, 6302, 6303, 6304, 6305, 6307, 6308, 6309, 6310, 6311, 6312, 6314, 6317, 6319, 6320, 6321, 6322, 6323, 6325, 6327, 6329, 6330, 6331, 6332, 6333, 6334, 6336, 6337, 6339, 6340, 6341, 6342, 6343, 6345, 6346, 6347, 6351, 6352, 6353, 6354, 6355, 6356, 6357, 6358, 6359, 6363, 6364, 6365, 6366, 6369, 6370, 6371, 6372, 6373, 6374, 6375, 6376, 6377, 6378, 6380, 6382, 6383, 6385, 6387, 6389, 6391, 6393, 6394, 6398, 6399, 6401, 6402, 6403, 6404, 6405, 6407, 6408, 6411, 6412, 6413, 6417, 6418, 6420, 6421, 6422, 6423, 6424, 6426, 6430, 6431, 6432, 6433, 6434, 6435, 6436, 6437, 6439, 6440, 6441, 6443, 6445, 6446, 6447, 6449, 6450, 6452, 6453, 6454, 6455, 6456, 6458, 6459, 6460, 6463, 6465, 6466, 6467, 6468, 6470, 6471, 6473, 6474, 6475, 6476, 6481, 6482, 6483, 6485, 6486, 6487, 6488, 6490, 6491, 6492, 6493, 6494, 6495, 6497, 6499, 6501, 6502, 6503, 6505, 6506, 6507, 6508, 6509, 6511, 6512, 6515, 6516, 6517, 6519, 6521, 6522, 6524, 6527, 6529, 6530, 6531, 6532, 6533, 6534, 6535, 6536, 6538, 6539, 6542, 6543, 6544, 6545, 6547, 6548, 6549, 6550, 6552, 6553, 6555, 6556, 6557, 6558, 6560, 6561, 6563, 6564, 6565, 6566, 6569, 6571, 6572, 6573, 6574, 6576, 6578, 6580, 6581, 6582, 6583, 6584, 6585, 6586, 6592, 6593, 6594, 6595, 6597, 6598, 6599, 6600, 6601, 6602, 6604, 6605, 6606, 6608, 6609, 6610, 6611, 6612, 6613, 6614, 6615, 6616, 6617, 6618, 6619, 6620, 6621, 6622, 6623, 6624, 6627, 6628, 6629, 6630, 6631, 6633, 6634, 6636, 6637, 6638, 6639, 6641, 6643, 6644, 6645, 6646, 6647, 6649, 6650, 6651, 6652, 6655, 6656, 6657, 6659, 6662, 6665, 6666, 6667, 6668, 6669, 6670, 6671, 6673, 6674, 6675, 6679, 6680, 6681, 6682, 6684, 6685, 6686, 6687, 6689, 6690, 6691, 6692, 6693, 6695, 6696, 6697, 6700, 6701, 6703, 6704, 6707, 6708, 6709, 6710, 6711, 6712, 6715, 6716, 6717, 6720, 6722, 6723, 6725, 6726, 6727, 6728, 6729, 6730, 6731, 6735, 6736, 6737, 6741, 6744, 6745, 6746, 6747, 6748, 6749, 6750, 6752, 6753, 6754, 6755, 6757, 6758, 6759, 6761, 6765, 6766, 6767, 6768, 6769, 6770, 6771, 6772, 6775, 6779, 6780, 6782, 6783, 6784, 6786, 6788, 6791, 6792, 6796, 6799, 6801, 6803, 6805, 6806, 6807, 6808, 6810, 6811, 6814, 6815, 6816, 6817, 6818, 6819, 6823, 6827, 6828, 6829, 6831, 6834, 6837, 6838, 6839, 6840, 6841, 6843, 6844, 6846, 6849, 6850, 6851, 6852, 6853, 6854, 6856, 6857, 6859, 6861, 6863, 6865, 6866, 6867, 6868, 6870, 6871, 6872, 6873, 6874, 6876, 6877, 6878, 6879, 6880, 6881, 6882, 6883, 6884, 6885, 6886, 6888, 6889, 6891, 6898, 6900, 6901, 6903, 6904, 6905, 6907, 6909, 6910, 6911, 6912, 6913, 6915, 6916, 6917, 6918, 6919, 6920, 6922, 6923, 6925, 6927, 6928, 6930, 6931, 6932, 6936, 6937, 6938, 6940, 6941, 6942, 6943, 6944, 6945, 6946, 6947, 6948, 6949, 6950, 6951, 6952, 6953, 6955, 6957, 6958, 6959, 6960, 6963, 6965, 6967, 6968, 6970, 6972, 6973, 6974, 6975, 6977, 6979, 6980, 6981, 6982, 6984, 6986, 6987, 6988, 6989, 6990, 6991, 6993, 6994, 6995, 6996, 6998, 6999, 7000, 7001, 7003, 7005, 7006, 7007, 7008, 7009, 7010, 7011, 7012, 7015, 7019, 7020, 7021, 7023, 7025, 7026, 7027, 7030, 7031, 7032, 7033, 7035, 7036, 7037, 7039, 7040, 7041, 7042, 7043, 7044, 7048, 7050, 7051, 7052, 7053, 7054, 7055, 7057, 7059, 7061, 7062, 7063, 7064, 7066, 7067, 7068, 7069, 7070, 7071, 7072, 7073, 7074, 7077, 7078, 7079, 7081, 7082, 7083, 7085, 7087, 7089, 7090, 7092, 7093, 7094, 7095, 7097, 7098, 7100, 7102, 7104, 7105, 7107, 7108, 7109, 7110, 7111, 7112, 7113, 7114, 7116, 7117, 7119, 7123, 7124, 7125, 7126, 7127, 7129, 7130, 7131, 7132, 7133, 7137, 7138, 7140, 7141, 7142, 7143, 7144, 7146, 7147, 7148, 7149, 7150, 7151, 7152, 7153, 7154, 7156, 7157, 7158, 7160, 7162, 7163, 7164, 7165, 7166, 7168, 7169, 7170, 7171, 7174, 7175, 7177, 7181, 7182, 7183, 7184, 7185, 7186, 7187, 7188, 7190, 7191, 7197, 7201, 7202, 7204, 7205, 7206, 7209, 7210, 7211, 7212, 7214, 7216, 7218, 7219, 7220, 7222, 7225, 7226, 7228, 7229, 7230, 7231, 7233, 7235, 7236, 7238, 7239, 7240, 7242, 7243, 7244, 7245, 7246, 7247, 7249, 7251, 7252, 7253, 7255, 7256, 7257, 7258, 7260, 7261, 7262, 7263, 7264, 7265, 7266, 7268, 7270, 7271, 7273, 7274, 7275, 7276, 7277, 7278, 7279, 7280, 7281, 7284, 7285, 7287, 7288, 7289, 7290, 7291, 7292, 7293, 7295, 7296, 7297, 7298, 7299, 7301, 7302, 7303, 7304, 7306, 7307, 7308, 7309, 7310, 7311, 7313, 7315, 7317, 7318, 7319, 7320, 7321, 7322, 7323, 7324, 7325, 7326, 7328, 7329, 7330, 7331, 7334, 7335, 7336, 7337, 7339, 7341, 7342, 7343, 7344, 7345, 7347, 7348, 7349, 7350, 7351, 7352, 7353, 7354, 7356, 7357, 7359, 7360, 7361, 7362, 7363, 7364, 7368, 7370, 7373, 7376, 7377, 7378, 7380, 7382, 7383, 7384, 7385, 7386, 7387, 7388, 7389, 7390, 7391, 7392, 7394, 7396, 7397, 7399, 7400, 7401, 7403, 7404, 7405, 7407, 7408, 7411, 7412, 7413, 7415, 7416, 7417, 7420, 7422, 7423, 7424, 7425, 7426, 7428, 7429, 7430, 7431, 7432, 7433, 7435, 7436, 7437, 7439, 7442, 7444, 7445, 7446, 7448, 7449, 7450, 7452, 7453, 7454, 7456, 7457, 7459, 7462, 7463, 7464, 7465, 7466, 7467, 7469, 7470, 7471, 7473, 7474, 7475, 7476, 7477, 7478, 7479, 7480, 7481, 7482, 7484, 7486, 7488, 7490, 7491, 7492, 7493, 7494, 7496, 7497, 7498, 7499, 7503, 7504, 7505, 7506, 7508, 7513, 7515, 7516, 7517, 7519, 7520, 7521, 7522, 7523, 7526, 7527, 7529, 7531, 7535, 7536, 7537, 7540, 7542, 7543, 7549, 7550, 7551, 7552, 7553, 7555, 7556, 7558, 7559, 7562, 7563, 7565, 7566, 7567, 7568, 7569, 7570, 7573, 7574, 7575, 7577, 7578, 7586, 7587, 7588, 7589, 7591, 7592, 7593, 7594, 7596, 7597, 7598, 7599, 7600, 7601, 7602, 7603, 7605, 7606, 7607, 7608, 7612, 7613, 7615, 7616, 7617, 7619, 7620, 7621, 7622, 7623, 7624, 7626, 7628, 7629, 7634, 7635, 7638, 7641, 7643, 7644, 7645, 7646, 7647, 7654, 7656, 7657, 7658, 7659, 7661, 7662, 7663, 7665, 7667, 7668, 7669, 7670, 7671, 7674, 7675, 7680, 7681, 7682, 7684, 7686, 7687, 7689, 7691, 7692, 7693, 7696, 7697, 7698, 7700, 7702, 7703, 7705, 7707, 7709, 7711, 7712, 7714, 7719, 7720, 7722, 7724, 7725, 7726, 7727, 7729, 7731, 7733, 7735, 7736, 7737, 7739, 7740, 7741, 7742, 7743, 7744, 7746, 7748, 7749, 7752, 7753, 7754, 7755, 7756, 7757, 7758, 7759, 7761, 7763, 7764, 7765, 7766, 7768, 7769, 7771, 7772, 7776, 7777, 7778, 7780, 7781, 7782, 7785, 7786, 7788, 7790, 7791, 7792, 7793, 7794, 7796, 7797, 7799, 7800, 7801, 7803, 7804, 7805, 7807, 7808, 7809, 7810, 7811, 7812, 7814, 7815, 7818, 7819, 7821, 7824, 7825, 7826, 7829, 7830, 7831, 7833, 7834, 7835, 7836, 7838, 7839, 7840, 7842, 7844, 7845, 7846, 7847, 7848, 7849, 7850, 7851, 7852, 7853, 7854, 7855, 7856, 7857, 7858, 7859, 7860, 7867, 7870, 7871, 7872, 7876, 7877, 7878, 7882, 7883, 7884, 7885, 7886, 7888, 7889, 7890, 7891, 7892, 7893, 7896, 7898, 7899, 7901, 7902, 7903, 7906, 7908, 7909, 7910, 7912, 7914, 7915, 7916, 7917, 7918, 7919, 7920, 7921, 7924, 7925, 7927, 7928, 7929, 7933, 7934, 7936, 7938, 7939, 7940, 7941, 7942, 7943, 7944, 7945, 7947, 7948, 7949, 7950, 7953, 7954, 7956, 7958, 7959, 7962, 7963, 7965, 7969, 7971, 7975, 7978, 7979, 7980, 7982, 7983, 7984, 7986, 7987, 7988, 7990, 7991, 7992, 7993, 7994, 7995, 7996, 7997, 7998, 7999, 8000, 8002, 8003, 8004, 8005, 8006, 8007, 8008, 8009, 8010, 8011, 8012, 8014, 8015, 8016, 8017, 8018, 8019, 8021, 8022, 8023, 8024, 8026, 8028, 8029, 8030, 8031, 8034, 8036, 8037, 8038, 8039, 8040, 8041, 8042, 8044, 8045, 8047, 8049, 8050, 8051, 8052, 8055, 8056, 8058, 8060, 8061, 8063, 8065, 8068, 8069, 8070, 8073, 8076, 8080, 8082, 8084, 8085, 8087, 8088, 8089, 8090, 8092, 8093, 8095, 8096, 8097, 8098, 8099, 8100, 8101, 8102, 8103, 8104, 8105, 8106, 8107, 8108, 8110, 8111, 8113, 8114, 8116, 8117, 8119, 8121, 8122, 8123, 8124, 8125, 8126, 8127, 8128, 8129, 8130, 8131, 8133, 8134, 8136, 8137, 8139, 8141, 8142, 8143, 8144, 8145, 8147, 8151, 8152, 8153, 8154, 8157, 8158, 8159, 8163, 8164, 8165, 8166, 8167, 8168, 8169, 8173, 8174, 8175, 8176, 8177, 8178, 8180, 8181, 8182, 8183, 8184, 8185, 8186, 8187, 8189, 8193, 8200, 8201, 8202, 8204, 8205, 8206, 8208, 8209, 8211, 8212, 8213, 8216, 8217, 8218, 8219, 8220, 8222, 8224, 8225, 8226, 8229, 8230, 8231, 8233, 8234, 8235, 8237, 8238, 8240, 8241, 8242, 8244, 8248, 8250, 8251, 8252, 8253, 8254, 8256, 8258, 8259, 8260, 8261, 8262, 8264, 8271, 8272, 8273, 8274, 8277, 8278, 8279, 8283, 8284, 8286, 8287, 8289, 8290, 8291, 8292, 8294, 8296, 8302, 8305, 8307, 8308, 8309, 8311, 8312, 8313, 8315, 8317, 8319, 8320, 8321, 8322, 8323, 8324, 8325, 8330, 8331, 8332, 8334, 8335, 8337, 8339, 8340, 8341, 8342, 8344, 8345, 8347, 8348, 8349, 8350, 8352, 8354, 8355, 8356, 8359, 8360, 8362, 8363, 8364, 8365, 8366, 8367, 8368, 8369, 8370, 8372, 8373, 8374, 8376, 8377, 8378, 8379, 8380, 8381, 8382, 8383, 8384, 8385, 8388, 8389, 8390, 8391, 8393, 8394, 8395, 8396, 8397, 8399, 8400, 8401, 8402, 8403, 8404, 8405, 8406, 8407, 8408, 8411, 8413, 8414, 8415, 8416, 8417, 8420, 8421, 8422, 8424, 8425, 8427, 8428, 8429, 8433, 8434, 8436, 8443, 8446, 8448, 8450, 8451, 8452, 8453, 8454, 8455, 8456, 8458, 8461, 8463, 8464, 8466, 8467, 8469, 8472, 8473, 8474, 8475, 8476, 8480, 8481, 8483, 8485, 8486, 8487, 8488, 8489, 8490, 8491, 8492, 8493, 8494, 8495, 8498, 8499, 8500, 8501, 8503, 8504, 8505, 8506, 8507, 8509, 8510, 8511, 8512, 8513, 8514, 8515, 8516, 8517, 8518, 8520, 8524, 8525, 8526, 8527, 8528, 8531, 8532, 8533, 8535, 8536, 8537, 8538, 8540, 8542, 8544, 8546, 8548, 8550, 8551, 8552, 8553, 8554, 8557, 8560, 8561, 8562, 8564, 8566, 8567, 8569, 8570, 8571, 8573, 8574, 8575, 8576, 8577, 8578, 8580, 8581, 8582, 8583, 8584, 8585, 8586, 8587, 8589, 8590, 8591, 8592, 8593, 8594, 8595, 8598, 8599, 8601, 8602, 8603, 8605, 8606, 8607, 8608, 8609, 8611, 8612, 8613, 8615, 8616, 8618, 8619, 8621, 8623, 8624, 8625, 8626, 8627, 8628, 8630, 8631, 8634, 8635, 8636, 8638, 8640, 8641, 8642, 8643, 8645, 8646, 8647, 8648, 8649, 8650, 8651, 8652, 8653, 8654, 8655, 8657, 8658, 8659, 8660, 8661, 8663, 8664, 8665, 8669, 8670, 8671, 8672, 8673, 8678, 8680, 8681, 8682, 8683, 8684, 8685, 8690, 8691, 8692, 8693, 8694, 8695, 8699, 8700, 8702, 8703, 8704, 8705, 8706, 8707, 8708, 8710, 8712, 8713, 8715, 8716, 8718, 8719, 8720, 8721, 8722, 8723, 8724, 8725, 8730, 8731, 8732, 8733, 8734, 8736, 8738, 8739, 8743, 8744, 8746, 8747, 8748, 8749, 8751, 8752, 8754, 8756, 8757, 8758, 8759, 8760, 8762, 8765, 8767, 8768, 8770, 8771, 8773, 8777, 8778, 8781, 8783, 8784, 8787, 8790, 8791, 8794, 8795, 8802, 8803, 8805, 8808, 8809, 8810, 8811, 8813, 8814, 8815, 8816, 8817, 8818, 8819, 8822, 8824, 8825, 8827, 8829, 8831, 8832, 8833, 8835, 8836, 8837, 8838, 8841, 8842, 8843, 8845, 8847, 8850, 8853, 8854, 8855, 8856, 8857, 8860, 8861, 8868, 8869, 8870, 8873, 8874, 8875, 8876, 8877, 8879, 8880, 8881, 8882, 8883, 8884, 8885, 8886, 8889, 8890, 8891, 8892, 8893, 8894, 8895, 8897, 8899, 8900, 8903, 8904, 8905, 8907, 8912, 8913, 8914, 8915, 8916, 8917, 8918, 8921, 8922, 8924, 8925, 8926, 8927, 8929, 8931, 8932, 8933, 8937, 8942, 8943, 8945, 8946, 8947, 8948, 8949, 8950, 8954, 8956, 8957, 8959, 8960, 8962, 8963, 8964, 8966, 8968, 8969, 8970, 8971, 8972, 8975, 8977, 8978, 8979, 8982, 8983, 8984, 8985, 8986, 8987, 8990, 8991, 8993, 8995, 8996, 8998, 9000, 9001, 9002, 9003, 9004, 9007, 9008, 9010, 9012, 9013, 9015, 9016, 9017, 9018, 9019, 9021, 9022, 9023, 9024, 9026, 9029, 9030, 9031, 9032, 9033, 9035, 9036, 9037, 9038, 9039, 9040, 9042, 9044, 9045, 9046, 9047, 9048, 9053, 9054, 9055, 9056, 9057, 9058, 9059, 9061, 9062, 9065, 9066, 9068, 9069, 9071, 9072, 9073, 9075, 9078, 9079, 9081, 9082, 9083, 9084, 9086, 9087, 9088, 9089, 9090, 9091, 9093, 9094, 9095, 9097, 9101, 9103, 9104, 9105, 9106, 9109, 9112, 9115, 9116, 9117, 9118, 9119, 9120, 9121, 9123, 9124, 9125, 9126, 9128, 9129, 9130, 9131, 9133, 9136, 9137, 9138, 9139, 9144, 9145, 9146, 9147, 9148, 9149, 9150, 9151, 9152, 9153, 9154, 9155, 9156, 9157, 9158, 9159, 9160, 9161, 9164, 9165, 9167, 9169, 9170, 9173, 9179, 9180, 9181, 9182, 9183, 9184, 9186, 9187, 9188, 9189, 9190, 9191, 9192, 9193, 9194, 9195, 9196, 9198, 9199, 9200, 9201, 9202, 9204, 9205, 9207, 9208, 9210, 9211, 9212, 9213, 9214, 9216, 9217, 9218, 9221, 9224, 9226, 9227, 9229, 9231, 9232, 9235, 9236, 9239, 9241, 9243, 9245, 9246, 9248, 9250, 9252, 9254, 9256, 9257, 9258, 9260, 9263, 9264, 9265, 9266, 9267, 9270, 9272, 9274, 9275, 9278, 9279, 9280, 9281, 9283, 9286, 9287, 9288, 9291, 9292, 9293, 9296, 9298, 9299, 9300, 9301, 9302, 9304, 9310, 9311, 9312, 9314, 9315, 9317, 9319, 9322, 9323, 9324, 9325, 9326, 9328, 9329, 9330, 9331, 9332, 9333, 9334, 9335, 9337, 9338, 9340, 9341, 9342, 9343, 9345, 9346, 9347, 9349, 9351, 9353, 9354, 9355, 9356, 9358, 9359, 9360, 9361, 9362, 9363, 9364, 9365, 9366, 9367, 9368, 9369, 9372, 9373, 9374, 9375, 9376, 9377, 9378, 9381, 9382, 9385, 9386, 9387, 9389, 9390, 9391, 9394, 9395, 9396, 9397, 9398, 9399, 9400, 9402, 9405, 9406, 9407, 9409, 9410, 9411, 9412, 9413, 9414, 9417, 9419, 9420, 9422, 9423, 9424, 9425, 9427, 9429, 9430, 9432, 9433, 9434, 9435, 9436, 9437, 9439, 9440, 9442, 9443, 9445, 9446, 9447, 9449, 9450, 9451, 9455, 9456, 9458, 9459, 9461, 9464, 9465, 9467, 9468, 9469, 9471, 9472, 9473, 9474, 9475, 9476, 9478, 9479, 9480, 9482, 9483, 9491, 9492, 9493, 9495, 9498, 9500, 9501, 9502, 9506, 9507, 9508, 9510, 9511, 9512, 9513, 9514, 9516, 9517, 9518, 9521, 9522, 9525, 9527, 9528, 9529, 9531, 9532, 9534, 9535, 9536, 9538, 9539, 9543, 9544, 9546, 9547, 9548, 9549, 9550, 9552, 9554, 9555, 9557, 9558, 9559, 9561, 9562, 9567, 9568, 9569, 9570, 9572, 9573, 9574, 9575, 9577, 9578, 9579, 9581, 9585, 9586, 9587, 9590, 9591, 9592, 9593, 9594, 9595, 9596, 9597, 9598, 9599, 9600, 9603, 9605, 9606, 9608, 9610, 9611, 9612, 9613, 9614, 9617, 9618, 9621, 9622, 9623, 9624, 9625, 9626, 9627, 9629, 9630, 9631, 9632, 9634, 9635, 9638, 9639, 9641, 9642, 9643, 9644, 9647, 9649, 9650, 9651, 9653, 9654, 9656, 9658, 9659, 9660, 9663, 9665, 9666, 9669, 9671, 9672, 9673, 9674, 9675, 9676, 9677, 9678, 9679, 9680, 9682, 9683, 9684, 9685, 9686, 9687, 9688, 9689, 9690, 9691, 9694, 9695, 9696, 9698, 9699, 9700, 9701, 9702, 9703, 9704, 9706, 9707, 9708, 9710, 9711, 9712, 9713, 9714, 9715, 9716, 9717, 9718, 9719, 9723, 9724, 9725, 9726, 9728, 9730, 9731, 9732, 9733, 9734, 9735, 9736, 9737, 9738, 9739, 9743, 9745, 9746, 9747, 9748, 9749, 9750, 9751, 9753, 9754, 9755, 9756, 9757, 9758, 9759, 9760, 9761, 9763, 9765, 9766, 9767, 9770, 9771, 9773, 9775, 9776, 9777, 9778, 9779, 9780, 9781, 9782, 9783, 9784, 9785, 9786, 9789, 9790, 9791, 9793, 9795, 9796, 9797, 9798, 9799, 9800, 9803, 9804, 9806, 9807, 9808, 9809, 9810, 9811, 9812, 9813, 9814, 9816, 9817, 9818, 9819, 9827, 9828, 9830, 9831, 9834, 9835, 9836, 9837, 9838, 9839, 9840, 9842, 9843, 9844, 9848, 9849, 9851, 9852, 9853, 9855, 9857, 9860, 9861, 9862, 9864, 9866, 9867, 9870, 9871, 9872, 9876, 9879, 9881, 9882, 9883, 9885, 9886, 9887, 9888, 9889, 9890, 9891, 9892, 9893, 9894, 9895, 9897, 9899, 9900, 9903, 9904, 9905, 9906, 9907, 9908, 9909, 9910, 9911, 9912, 9915, 9920, 9921, 9924, 9925, 9930, 9932, 9934, 9935, 9936, 9938, 9940, 9941, 9942, 9943, 9944, 9945, 9946, 9948, 9949, 9951, 9954, 9955, 9956, 9957, 9958, 9959, 9960, 9961, 9963, 9965, 9966, 9967, 9969, 9971, 9972, 9973, 9974, 9976, 9978, 9979, 9980, 9981, 9982, 9983, 9985, 9986, 9988, 9991, 9992, 9993, 9995, 9997, 9998, 9999]\n", + "Estimated Probability within ±1 sigma: 0.6834\n" + ] + } + ], + "source": [ + "# Define Gaussian function\n", + "mu, sigma = 0, 1 # Standard Normal Distribution\n", + "gauss_func = gaussian(mu, sigma)\n", + "\n", + "# Values within one standard deviation of the mean\n", + "condition = in_range(mu - sigma, mu + sigma)\n", + "\n", + "# Compute the integral\n", + "x_min, x_max = mu - 4*sigma, mu + 4*sigma # Capture 99.99% of the data\n", + "N = 10000 # Large sample size for accuracy\n", + "\n", + "estimated_probability = integrate(gauss_func, x_min, x_max, N, condition)\n", + "\n", + "# Print result\n", + "print(f\"Estimated Probability within ±1 sigma: {estimated_probability:.4f}\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 11760599aaa521262a5ef9bee2e92da3112fca27 Mon Sep 17 00:00:00 2001 From: mathlete1618 Date: Fri, 21 Feb 2025 23:46:56 -0600 Subject: [PATCH 3/5] Add Lab.4 solution files --- Labs/Lab.4/Lab.4_solution.ipynb | 2288 +++++++++++++++++++++++++++++++ Labs/Lab.4/Paint.py | 505 +++++++ Labs/Lab.4/drawing.raster | 1 + 3 files changed, 2794 insertions(+) create mode 100644 Labs/Lab.4/Lab.4_solution.ipynb create mode 100644 Labs/Lab.4/Paint.py create mode 100644 Labs/Lab.4/drawing.raster diff --git a/Labs/Lab.4/Lab.4_solution.ipynb b/Labs/Lab.4/Lab.4_solution.ipynb new file mode 100644 index 000000000..197c0b1a3 --- /dev/null +++ b/Labs/Lab.4/Lab.4_solution.ipynb @@ -0,0 +1,2288 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Lab 4- Object Oriented Programming\n", + "\n", + "For all of the exercises below, make sure you provide tests of your solutions.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "1. Write a \"counter\" class that can be incremented up to a specified maximum value, will print an error if an attempt is made to increment beyond that value, and allows reseting the counter. " + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "metadata": {}, + "outputs": [], + "source": [ + "class Counter:\n", + " def __init__(self, max_value=None):\n", + " self.count = 0\n", + " self.max_value = max_value\n", + " def increment(self):\n", + " if self.max_value is not None and self.count >= self.max_value:\n", + " print(f\"Error: counter exceeded max value. Counter has be reset to 0\")\n", + " self.count = 0\n", + " else: \n", + " self.count += 1\n", + " print(f\"Counter: {self.count}\")\n", + " return self.count\n" + ] + }, + { + "cell_type": "code", + "execution_count": 93, + "metadata": {}, + "outputs": [], + "source": [ + "value_1 = Counter(max_value = 5)" + ] + }, + { + "cell_type": "code", + "execution_count": 99, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Error: counter exceeded max value. Counter has be reset to 0\n", + "Counter: 0\n" + ] + }, + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 99, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "value_1.increment()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "2. Copy and paste your solution to question 1 and modify it so that all the data held by the counter is private. Implement functions to check the value of the counter, check the maximum value, and check if the counter is at the maximum." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [], + "source": [ + "class Counter:\n", + " def __init__(self, max_value=None):\n", + " self.__count = 0\n", + " self.__max_value = max_value\n", + " def increment(self):\n", + " if self.__max_value is not None and self.__count >= self.__max_value:\n", + " print(f\"Error: counter exceeded max value. Counter has be reset to 0\")\n", + " self.__count = 0\n", + " else: \n", + " self.__count += 1\n", + " print(f\"Counter: {self.__count}\")\n", + " return self.__count\n", + " def Check_Counter(self):\n", + " if self.__count >= self.__max_value:\n", + " print(\"Counter has reach the maximum.\")\n", + " else:\n", + " print(f\"Max Value: {self.__max_value}\")\n", + " return self.__count, self.__max_value" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [], + "source": [ + "value_2 = Counter(max_value = 3)" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Error: counter exceeded max value. Counter has be reset to 0\n", + "Counter: 0\n", + "Max Value: 3\n" + ] + }, + { + "data": { + "text/plain": [ + "(0, 3)" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "value_2.increment()\n", + "value_2.Check_Counter()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "3. Implement a class to represent a rectangle, holding the length, width, and $x$ and $y$ coordinates of a corner of the object. Implement functions that compute the area and perimeter of the rectangle. Make all data members private and privide accessors to retrieve values of data members. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To make this code more mathematically sound, I let the length and width be computed dynamially depending on changing variables of x and y. Also, this default for the class of the x and y values are positioned at the origin to prep for the raster drawings. Additionally, @property funciton have been created to read the other specified values, and setters have been created to update values for x and y, constraining them to the first quadrant. Naturally, the length and width functions are optional, because the constructor method assigns defaut funcitons if none are provided. These default functions simply return the original provided dimensions, ensuring that the class behaves predictably even without custom functions." + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": { + "vscode": { + "languageId": "ruby" + } + }, + "outputs": [], + "source": [ + "class Rectangle:\n", + " def __init__(self, length, width, x=0, y=0, length_function=None, width_function=None):\n", + " self.__length = length\n", + " self.__width = width\n", + " self.__x = x\n", + " self.__y = y\n", + " self.__length_function = length_function if length_function else lambda l, w, x, y: l\n", + " self.__width_function = width_function if width_function else lambda l, w, x, y: w\n", + "## The @property decorator is used to define properties in Python. This puts it in a read only property, which is useful for this exercise.\n", + " @property\n", + " def length(self):\n", + " # Dynamically compute length based on the function of x and y\n", + " return self.__length_function(self.__length, self.__width, self.__x, self.__y)\n", + "\n", + " @property\n", + " def width(self):\n", + " # Dynamically compute width based on the function of x and y\n", + " return self.__width_function(self.__length, self.__width, self.__x, self.__y)\n", + "\n", + " @property\n", + " def x(self):\n", + " return self.__x\n", + "\n", + " @x.setter\n", + " def x(self, value):\n", + " if value < 0:\n", + " raise ValueError(\"x cannot be negative\")\n", + " self.__x = value\n", + "\n", + " @property\n", + " def y(self):\n", + " return self.__y\n", + "\n", + " @y.setter\n", + " def y(self, value):\n", + " if value < 0:\n", + " raise ValueError(\"y cannot be negative\")\n", + " self.__y = value\n", + "\n", + " def set_origin(self, origin_x, origin_y):\n", + " \"\"\"Update x and y based on a new origin.\"\"\"\n", + " self.__x = origin_x\n", + " self.__y = origin_y\n", + "\n", + " def compute_area(self):\n", + " return self.length * self.width\n", + "\n", + " def compute_perimeter(self):\n", + " return 2 * (self.length + self.width)\n", + "\n", + " def get_corners(self): # Function to conpute the corners of the rectangle given the length and width and corresponding origin, simplifying the code.\n", + " \"\"\"Returns the coordinates of the rectangle's corners based on (x, y) as the bottom-left corner.\"\"\"\n", + " return {\n", + " \"bottom_left\": (self.__x, self.__y),\n", + " \"bottom_right\": (self.__x + self.width, self.__y),\n", + " \"top_left\": (self.__x, self.__y + self.length),\n", + " \"top_right\": (self.__x + self.width, self.__y + self.length)\n", + " }" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": { + "vscode": { + "languageId": "ruby" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Initial Rectangle Properties:\n", + "Length: 10.0\n", + "Width: 5.0\n", + "Area: 50.0\n", + "Perimeter: 30.0\n", + "Corners: {'bottom_left': (0, 0), 'bottom_right': (5.0, 0), 'top_left': (0, 10.0), 'top_right': (5.0, 10.0)}\n" + ] + } + ], + "source": [ + "# Define dynamic lambda functions for length and width\n", + "compute_length = lambda length, width, x, y: length * (1 + 0.1 * x) # Scaling factor of 1, since x = 0\n", + "compute_width = lambda length, width, x, y: width * (1 + 0.05 * y) # y is 0, so scaling factor is 1\n", + "\n", + "# Initialize the rectangle with starting dimensions and position\n", + "rect = Rectangle(10, 5, x=0, y=0, length_function=compute_length, width_function=compute_width)\n", + "\n", + "# Accessing properties and methods\n", + "print(\"Initial Rectangle Properties:\")\n", + "print(f\"Length: {rect.length}\") # Output: 10 (since x=0)\n", + "print(f\"Width: {rect.width}\") # Output: 5 (since y=0)\n", + "print(f\"Area: {rect.compute_area()}\") # Output: 50\n", + "print(f\"Perimeter: {rect.compute_perimeter()}\") # Output: 30\n", + "print(f\"Corners: {rect.get_corners()}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "4. Implement a class to represent a circle, holding the radius and $x$ and $y$ coordinates of center of the object. Implement functions that compute the area and perimeter of the rectangle. Make all data members private and privide accessors to retrieve values of data members. " + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "metadata": {}, + "outputs": [], + "source": [ + "class Circle:\n", + " def __init__(self, radius, x, y):\n", + " self.__radius = radius\n", + " self.__x = x\n", + " self.__y = y\n", + " def area(self): # compute the area using the formula pi * r^2\n", + " return 3.14 * self.__radius ** 2\n", + " def circumfrence(self): # compute the perimeter using the formula 2 * pi * r\n", + " return round(2 * 3.14 * self.__radius, 2)\n", + " def get_center(self):\n", + " return self.__x, self.__y\n", + " def get_radius(self):\n", + " return self.__radius\n", + " def get_circumfrence(self):\n", + " return self.circumfrence()\n", + " def get_area(self):\n", + " return self.area()\n", + " def return_all(self):\n", + " print(f\"Area: {self.area()}\")\n", + " print(f\"Circumfrence: {self.circumfrence()}\")\n", + " print(f\"Center: {self.get_center()}\")\n", + " print(f\"Radius: {self.get_radius()}\")\n", + " return self.area(), self.circumfrence(), self.get_center(), self.get_radius()\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Area: 78.5\n", + "Circumfrence: 31.4\n", + "Center: (0, 0)\n", + "Radius: 5\n" + ] + }, + { + "data": { + "text/plain": [ + "(78.5, 31.4, (0, 0), 5)" + ] + }, + "execution_count": 60, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "circle_1 = Circle(5, 0, 0)\n", + "circle_1.return_all()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "5. Implement a common base class for the classes implemented in 3 and 4 above which implements all common methods as not implemented functions (virtual). Re-implement your regtangle and circle classes to inherit from the base class and overload the functions accordingly. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For this exercise I implemented the abstract base class as instructed for the Shapes and then reimplemented the Rectangle and Circle Class with the methods corresponding to the @abstractmethods defined in the first code box. The Rectangle and Circle classes are reimplemented in the second and third code boxes, respectively. Furthermore, I have designed a for loop in the fourth and final code box for exercise 4, along with the shapes list with the instances for a rectangle and a circle. Seperate cells are used for shapes to streamline the process of copy and pasting as the objects are iteratively modified in later exercises. The for loop iterates over this list and provides the desired output in a neat format. This allows for additional shapes to be appended to the list, ensuring the ability to scale later on." + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "metadata": {}, + "outputs": [], + "source": [ + "from abc import ABC, abstractmethod\n", + "\n", + "class Shape(ABC):\n", + " \"\"\"Abstract base class for all shapes.\"\"\"\n", + "\n", + " @abstractmethod\n", + " def compute_area(self):\n", + " \"\"\"Compute the area of the shape.\"\"\"\n", + " pass\n", + "\n", + " @abstractmethod\n", + " def compute_perimeter(self):\n", + " \"\"\"Compute the perimeter of the shape.\"\"\"\n", + " pass\n", + "\n", + " @abstractmethod\n", + " def get_center(self):\n", + " \"\"\"Get the center of the shape.\"\"\"\n", + " pass\n", + "\n", + " @abstractmethod\n", + " def get_corners(self):\n", + " \"\"\"Get the corners of the shape, if applicable.\"\"\"\n", + " pass\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "metadata": {}, + "outputs": [], + "source": [ + "class Rectangle(Shape):\n", + " def __init__(self, length, width, x=0, y=0, length_function=None, width_function=None):\n", + " self.__length = length\n", + " self.__width = width\n", + " self.__x = x\n", + " self.__y = y\n", + " self.__length_function = length_function if length_function else lambda l, w, x, y: l\n", + " self.__width_function = width_function if width_function else lambda l, w, x, y: w\n", + "## The @property decorator is used to define properties in Python. This puts it in a read only property, which is useful for this exercise.\n", + " @property\n", + " def length(self):\n", + " # Dynamically compute length based on the function of x and y\n", + " return self.__length_function(self.__length, self.__width, self.__x, self.__y)\n", + "\n", + " @property\n", + " def width(self):\n", + " # Dynamically compute width based on the function of x and y\n", + " return self.__width_function(self.__length, self.__width, self.__x, self.__y)\n", + "\n", + " @property\n", + " def x(self):\n", + " return self.__x\n", + "\n", + " @x.setter\n", + " def x(self, value):\n", + " if value < 0:\n", + " raise ValueError(\"x cannot be negative\")\n", + " self.__x = value\n", + "\n", + " @property\n", + " def y(self):\n", + " return self.__y\n", + "\n", + " @y.setter\n", + " def y(self, value):\n", + " if value < 0:\n", + " raise ValueError(\"y cannot be negative\")\n", + " self.__y = value\n", + "\n", + " def set_origin(self, origin_x, origin_y):\n", + " \"\"\"Update x and y based on a new origin.\"\"\"\n", + " self.__x = origin_x\n", + " self.__y = origin_y\n", + "\n", + " def compute_area(self):\n", + " return self.length * self.width\n", + "\n", + " def compute_perimeter(self):\n", + " return 2 * (self.length + self.width)\n", + "\n", + " def get_corners(self): # Function to conpute the corners of the rectangle given the length and width and corresponding origin, simplifying the code.\n", + " \"\"\"Returns the coordinates of the rectangle's corners based on (x, y) as the bottom-left corner.\"\"\"\n", + " return {\n", + " \"bottom_left\": (self.__x, self.__y),\n", + " \"bottom_right\": (self.__x + self.width, self.__y),\n", + " \"top_left\": (self.__x, self.__y + self.length),\n", + " \"top_right\": (self.__x + self.width, self.__y + self.length)\n", + " }\n", + " # Implementing abstract methods from Shape\n", + " def compute_area(self):\n", + " return self.length * self.width\n", + "\n", + " def compute_perimeter(self):\n", + " return 2 * (self.length + self.width)\n", + "\n", + " def get_center(self):\n", + " return (self.__x, self.__y)\n", + "\n", + " def get_corners(self):\n", + " return {\n", + " \"bottom_left\": (self.__x, self.__y),\n", + " \"bottom_right\": (self.__x + self.width, self.__y),\n", + " \"top_left\": (self.__x, self.__y + self.length),\n", + " \"top_right\": (self.__x + self.width, self.__y + self.length)\n", + " }\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "metadata": {}, + "outputs": [], + "source": [ + "class Circle(Shape):\n", + " def __init__(self, radius, x, y):\n", + " self.__radius = radius\n", + " self.__x = x\n", + " self.__y = y\n", + " def area(self): # compute the area using the formula pi * r^2\n", + " return 3.14 * self.__radius ** 2\n", + " def circumfrence(self): # compute the perimeter using the formula 2 * pi * r\n", + " return round(2 * 3.14 * self.__radius, 2)\n", + " def get_center(self):\n", + " return self.__x, self.__y\n", + " def get_radius(self):\n", + " return self.__radius\n", + " def get_circumfrence(self):\n", + " return self.circumfrence()\n", + " def get_area(self):\n", + " return self.area()\n", + " def return_all(self):\n", + " print(f\"Area: {self.area()}\")\n", + " print(f\"Circumfrence: {self.circumfrence()}\")\n", + " print(f\"Center: {self.get_center()}\")\n", + " print(f\"Radius: {self.get_radius()}\")\n", + " return self.area(), self.circumfrence(), self.get_center(), self.get_radius()\n", + " # Implementing abstract methods from Shape\n", + " def compute_area(self):\n", + " return 3.14 * self.__radius ** 2\n", + "\n", + " def compute_perimeter(self):\n", + " return round(2 * 3.14 * self.__radius, 2)\n", + "\n", + " def get_center(self):\n", + " return (self.__x, self.__y)\n", + "\n", + " def get_corners(self):\n", + " \"\"\"Circles do not have corners, return None.\"\"\"\n", + " return None\n", + "\n", + " def get_radius(self):\n", + " return self.__radius\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Shape: Rectangle\n", + "Area: 50\n", + "Perimeter: 30\n", + "Center: (2, 3)\n", + "Corners: {'bottom_left': (2, 3), 'bottom_right': (7, 3), 'top_left': (2, 13), 'top_right': (7, 13)}\n", + "------------------------------\n", + "Shape: Circle\n", + "Area: 153.86\n", + "Perimeter: 43.96\n", + "Center: (0, 0)\n", + "Corners: Not applicable for this shape\n", + "------------------------------\n" + ] + } + ], + "source": [ + "shapes = [\n", + " Rectangle(10, 5, x=2, y=3),\n", + " Circle(7, x=0, y=0)\n", + "]\n", + "for shape in shapes:\n", + " print(f\"Shape: {shape.__class__.__name__}\")\n", + " print(f\"Area: {shape.compute_area()}\")\n", + " print(f\"Perimeter: {shape.compute_perimeter()}\")\n", + " print(f\"Center: {shape.get_center()}\")\n", + " corners = shape.get_corners()\n", + " if corners:\n", + " print(f\"Corners: {corners}\")\n", + " else:\n", + " print(\"Corners: Not applicable for this shape\")\n", + " print(\"-\" * 30)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "6. Implement a triangle class analogous to the rectangle and circle in question 5." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "I simplified the process of computing the area by using Heron's formula to omit computing the height and base, but the height is approximated so that I could find the corners and subsequently the center using the average of the coerners. I also implemented the abstract base class for polymorphism of the abstract methods. Thus, the output is analogous to that of the previous exercises." + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "metadata": {}, + "outputs": [], + "source": [ + "import math # import the math module to use the sqrt function\n", + "# Simplified Triangle class using Heron's formula\n", + "class Triangle(Shape):\n", + " def __init__(self, a, b, c, x=0, y=0):\n", + " # Validate if the sides can form a triangle\n", + " if not (a + b > c and a + c > b and b + c > a):\n", + " raise ValueError(\"The provided sides do not form a valid triangle.\")\n", + " \n", + " self.__a = a\n", + " self.__b = b\n", + " self.__c = c\n", + " self.__x = x\n", + " self.__y = y\n", + "\n", + " # Implementing abstract methods from Shape\n", + " def compute_area(self):\n", + " \"\"\"Compute the area using Heron's formula.\"\"\"\n", + " s = self.compute_perimeter() / 2 # Semi-perimeter\n", + " area = math.sqrt(s * (s - self.__a) * (s - self.__b) * (s - self.__c))\n", + " return round(area, 2)\n", + "\n", + " def compute_perimeter(self):\n", + " \"\"\"Compute the perimeter as the sum of all sides.\"\"\"\n", + " return round(self.__a + self.__b + self.__c, 2)\n", + "\n", + " def get_center(self):\n", + " \"\"\"Return the centroid of the triangle (average of corners).\"\"\"\n", + " corners = self.get_corners()\n", + " centroid_x = sum([p[0] for p in corners.values()]) / 3\n", + " centroid_y = sum([p[1] for p in corners.values()]) / 3\n", + " return (round(centroid_x, 2), round(centroid_y, 2))\n", + "\n", + " def get_corners(self):\n", + " \"\"\"Assume (x, y) is the bottom-left corner and calculate other corners.\"\"\"\n", + " # Simple placement of the triangle along the base a\n", + " return {\n", + " \"bottom_left\": (self.__x, self.__y),\n", + " \"bottom_right\": (self.__x + self.__a, self.__y),\n", + " \"top_vertex\": (self.__x + self.__a / 2, self.__y + self.get_approximate_height())\n", + " }\n", + " def get_approximate_height(self):\n", + " \"\"\"Compute the approximate height for visualization purposes.\"\"\"\n", + " # Area = 1/2 * base * height => height = (2 * area) / base\n", + " area = self.compute_area()\n", + " return round((2 * area) / self.__a, 2)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Shape: Triangle\n", + "Area: 14.7\n", + "Perimeter: 18\n", + "Center: (2.5, 1.96)\n", + "Corners: {'bottom_left': (0, 0), 'bottom_right': (5, 0), 'top_vertex': (2.5, 5.88)}\n", + "------------------------------\n" + ] + } + ], + "source": [ + "# Reusing the code from previous exercise with the shapes list and for loop to demonstarte the Triangle class's funcctionality with the other shapes.\n", + "shapes = [\n", + " Triangle(5, 6, 7, x=0, y=0)\n", + "]\n", + "\n", + "for shape in shapes:\n", + " print(f\"Shape: {shape.__class__.__name__}\")\n", + " print(f\"Area: {shape.compute_area()}\")\n", + " print(f\"Perimeter: {shape.compute_perimeter()}\")\n", + " print(f\"Center: {shape.get_center()}\")\n", + " corners = shape.get_corners()\n", + " if corners:\n", + " print(f\"Corners: {corners}\")\n", + " else:\n", + " print(\"Corners: Not applicable for this shape\")\n", + " print(\"-\" * 30)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "7. Add a function to the object classes, including the base, that returns a list of up to 16 pairs of $x$ and $y$ points on the parameter of the object. " + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "metadata": {}, + "outputs": [], + "source": [ + "from abc import ABC, abstractmethod # Importing again to avoid potential errors in runnning this cell before previous cells.\n", + "## Reusing the Shape class from the previous exercise\n", + "class Shape(ABC):\n", + " @abstractmethod\n", + " def compute_area(self):\n", + " \"\"\"Compute the area of the shape.\"\"\"\n", + " pass\n", + "\n", + " @abstractmethod\n", + " def compute_perimeter(self):\n", + " \"\"\"Compute the perimeter of the shape.\"\"\"\n", + " pass\n", + "\n", + " @abstractmethod\n", + " def get_center(self):\n", + " \"\"\"Get the center of the shape.\"\"\"\n", + " pass\n", + "\n", + " @abstractmethod\n", + " def get_corners(self):\n", + " \"\"\"Get the corners of the shape, if applicable.\"\"\"\n", + " pass\n", + "## This is the new method addded to the Shape class to return the perimeter points of the the object.\n", + " @abstractmethod\n", + " def get_perimeter_points(self, num_points=16):\n", + " \"\"\"Return up to 16 (x, y) points along the perimeter of the shape.\"\"\"\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "metadata": {}, + "outputs": [], + "source": [ + "class Rectangle(Shape):\n", + " def __init__(self, length, width, x=0, y=0, length_function=None, width_function=None):\n", + " self.__length = length\n", + " self.__width = width\n", + " self.__x = x\n", + " self.__y = y\n", + " self.__length_function = length_function if length_function else lambda l, w, x, y: l\n", + " self.__width_function = width_function if width_function else lambda l, w, x, y: w\n", + "## The @property decorator is used to define properties in Python. This puts it in a read only property, which is useful for this exercise.\n", + " @property\n", + " def length(self):\n", + " # Dynamically compute length based on the function of x and y\n", + " return self.__length_function(self.__length, self.__width, self.__x, self.__y)\n", + "\n", + " @property\n", + " def width(self):\n", + " # Dynamically compute width based on the function of x and y\n", + " return self.__width_function(self.__length, self.__width, self.__x, self.__y)\n", + "\n", + " @property\n", + " def x(self):\n", + " return self.__x\n", + "\n", + " @x.setter\n", + " def x(self, value):\n", + " if value < 0:\n", + " raise ValueError(\"x cannot be negative\")\n", + " self.__x = value\n", + "\n", + " @property\n", + " def y(self):\n", + " return self.__y\n", + "\n", + " @y.setter\n", + " def y(self, value):\n", + " if value < 0:\n", + " raise ValueError(\"y cannot be negative\")\n", + " self.__y = value\n", + "\n", + " def set_origin(self, origin_x, origin_y):\n", + " \"\"\"Update x and y based on a new origin.\"\"\"\n", + " self.__x = origin_x\n", + " self.__y = origin_y\n", + "\n", + " def compute_area(self):\n", + " return self.length * self.width\n", + "\n", + " def compute_perimeter(self):\n", + " return 2 * (self.length + self.width)\n", + "\n", + " def get_corners(self): # Function to conpute the corners of the rectangle given the length and width and corresponding origin, simplifying the code.\n", + " \"\"\"Returns the coordinates of the rectangle's corners based on (x, y) as the bottom-left corner.\"\"\"\n", + " return {\n", + " \"bottom_left\": (self.__x, self.__y),\n", + " \"bottom_right\": (self.__x + self.width, self.__y),\n", + " \"top_left\": (self.__x, self.__y + self.length),\n", + " \"top_right\": (self.__x + self.width, self.__y + self.length)\n", + " }\n", + " # Implementing abstract methods from Shape\n", + " def compute_area(self):\n", + " return self.length * self.width\n", + "\n", + " def compute_perimeter(self):\n", + " return 2 * (self.length + self.width)\n", + "\n", + " def get_center(self):\n", + " return (self.__x, self.__y)\n", + "\n", + " def get_corners(self):\n", + " return {\n", + " \"bottom_left\": (self.__x, self.__y),\n", + " \"bottom_right\": (self.__x + self.width, self.__y),\n", + " \"top_left\": (self.__x, self.__y + self.length),\n", + " \"top_right\": (self.__x + self.width, self.__y + self.length)\n", + " }\n", + " ## This is the new method added to the Rectangle class to return the perimeter points of the rectangle.\n", + " def get_perimeter_points(self, num_points=16):\n", + " \"\"\"Generate up to 16 points along the perimeter of the rectangle.\"\"\"\n", + " corners = self.get_corners()\n", + " points = []\n", + " \n", + " # Extract corner points\n", + " bl = corners[\"bottom_left\"]\n", + " br = corners[\"bottom_right\"]\n", + " tr = corners[\"top_right\"]\n", + " tl = corners[\"top_left\"]\n", + "\n", + " # Calculate the points along each edge\n", + " edge_points = num_points // 4 # Utilizing integer division (floor division operator //) to get the number of points per edge\n", + " \n", + " # Bottom edge\n", + " for i in range(edge_points):\n", + " t = i / edge_points\n", + " points.append((bl[0] + t * (br[0] - bl[0]), bl[1]))\n", + "\n", + " # Right edge\n", + " for i in range(edge_points):\n", + " t = i / edge_points\n", + " points.append((br[0], br[1] + t * (tr[1] - br[1])))\n", + "\n", + " # Top edge\n", + " for i in range(edge_points):\n", + " t = i / edge_points\n", + " points.append((tr[0] - t * (tr[0] - tl[0]), tr[1]))\n", + "\n", + " # Left edge\n", + " for i in range(edge_points):\n", + " t = i / edge_points\n", + " points.append((tl[0], tl[1] - t * (tl[1] - bl[1])))\n", + "\n", + " return points[:num_points]" + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "metadata": {}, + "outputs": [], + "source": [ + "class Circle(Shape):\n", + " def __init__(self, radius, x, y):\n", + " self.__radius = radius\n", + " self.__x = x\n", + " self.__y = y\n", + " def area(self): # compute the area using the formula pi * r^2\n", + " return 3.14 * self.__radius ** 2\n", + " def circumfrence(self): # compute the perimeter using the formula 2 * pi * r\n", + " return round(2 * 3.14 * self.__radius, 2)\n", + " def get_center(self):\n", + " return self.__x, self.__y\n", + " def get_radius(self):\n", + " return self.__radius\n", + " def get_circumfrence(self):\n", + " return self.circumfrence()\n", + " def get_area(self):\n", + " return self.area()\n", + " def return_all(self):\n", + " print(f\"Area: {self.area()}\")\n", + " print(f\"Circumfrence: {self.circumfrence()}\")\n", + " print(f\"Center: {self.get_center()}\")\n", + " print(f\"Radius: {self.get_radius()}\")\n", + " return self.area(), self.circumfrence(), self.get_center(), self.get_radius()\n", + " # Implementing abstract methods from Shape\n", + " def compute_area(self):\n", + " return 3.14 * self.__radius ** 2\n", + "\n", + " def compute_perimeter(self):\n", + " return round(2 * 3.14 * self.__radius, 2)\n", + "\n", + " def get_center(self):\n", + " return (self.__x, self.__y)\n", + "\n", + " def get_corners(self):\n", + " \"\"\"Circles do not have corners, return None.\"\"\"\n", + " return None\n", + "\n", + " def get_radius(self):\n", + " return self.__radius\n", + " ## This is the new method added to the Circle class to return the perimeter points of the circle.\n", + " def get_perimeter_points(self, num_points=16):\n", + " \"\"\"Generate up to 16 points along the perimeter of the circle.\"\"\"\n", + " points = []\n", + " for i in range(num_points):\n", + " angle = 2 * math.pi * (i / num_points)\n", + " x = self.__x + self.__radius * math.cos(angle)\n", + " y = self.__y + self.__radius * math.sin(angle)\n", + " points.append((round(x, 2), round(y, 2)))\n", + " return points\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "metadata": {}, + "outputs": [], + "source": [ + "import math # import the math module to use the sqrt function\n", + "# Simplified Triangle class using Heron's formula\n", + "class Triangle(Shape):\n", + " def __init__(self, a, b, c, x=0, y=0):\n", + " # Validate if the sides can form a triangle\n", + " if not (a + b > c and a + c > b and b + c > a):\n", + " raise ValueError(\"The provided sides do not form a valid triangle.\")\n", + " \n", + " self.__a = a\n", + " self.__b = b\n", + " self.__c = c\n", + " self.__x = x\n", + " self.__y = y\n", + "\n", + " # Implementing abstract methods from Shape\n", + " def compute_area(self):\n", + " \"\"\"Compute the area using Heron's formula.\"\"\"\n", + " s = self.compute_perimeter() / 2 # Semi-perimeter\n", + " area = math.sqrt(s * (s - self.__a) * (s - self.__b) * (s - self.__c))\n", + " return round(area, 2)\n", + "\n", + " def compute_perimeter(self):\n", + " \"\"\"Compute the perimeter as the sum of all sides.\"\"\"\n", + " return round(self.__a + self.__b + self.__c, 2)\n", + "\n", + " def get_center(self):\n", + " \"\"\"Return the centroid of the triangle (average of corners).\"\"\"\n", + " corners = self.get_corners()\n", + " centroid_x = sum([p[0] for p in corners.values()]) / 3\n", + " centroid_y = sum([p[1] for p in corners.values()]) / 3\n", + " return (round(centroid_x, 2), round(centroid_y, 2))\n", + "\n", + " def get_corners(self):\n", + " \"\"\"Assume (x, y) is the bottom-left corner and calculate other corners.\"\"\"\n", + " # Simple placement of the triangle along the base a\n", + " return {\n", + " \"bottom_left\": (self.__x, self.__y),\n", + " \"bottom_right\": (self.__x + self.__a, self.__y),\n", + " \"top_vertex\": (self.__x + self.__a / 2, self.__y + self.get_approximate_height())\n", + " }\n", + " def get_approximate_height(self):\n", + " \"\"\"Compute the approximate height for visualization purposes.\"\"\"\n", + " # Area = 1/2 * base * height => height = (2 * area) / base\n", + " area = self.compute_area()\n", + " return round((2 * area) / self.__a, 2)\n", + " ## This is the new method added to the Triangle class to return the perimeter points of the triangle.\n", + " def get_perimeter_points(self, num_points=16):\n", + " \"\"\"Generate up to 16 points along the perimeter of the triangle.\"\"\"\n", + " corners = self.get_corners()\n", + " points = list(corners.values())\n", + " \n", + " # Generate intermediate points along the edges\n", + " while len(points) < num_points:\n", + " for i in range(len(points) - 1):\n", + " mid_point = (\n", + " (points[i][0] + points[i + 1][0]) / 2,\n", + " (points[i][1] + points[i + 1][1]) / 2\n", + " )\n", + " points.insert(i + 1, mid_point)\n", + " if len(points) >= num_points:\n", + " break\n", + " return points[:num_points]\n" + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Shape: Rectangle\n", + "Perimeter Points: [(2.0, 3), (3.25, 3), (4.5, 3), (5.75, 3), (7, 3.0), (7, 5.5), (7, 8.0), (7, 10.5), (7.0, 13), (5.75, 13), (4.5, 13), (3.25, 13), (2, 13.0), (2, 10.5), (2, 8.0), (2, 5.5)]\n", + "------------------------------\n", + "Shape: Circle\n", + "Perimeter Points: [(7.0, 0.0), (6.47, 2.68), (4.95, 4.95), (2.68, 6.47), (0.0, 7.0), (-2.68, 6.47), (-4.95, 4.95), (-6.47, 2.68), (-7.0, 0.0), (-6.47, -2.68), (-4.95, -4.95), (-2.68, -6.47), (-0.0, -7.0), (2.68, -6.47), (4.95, -4.95), (6.47, -2.68)]\n", + "------------------------------\n", + "Shape: Triangle\n", + "Perimeter Points: [(0, 0), (0.625, 0.0), (0.9375, 0.0), (1.09375, 0.0), (1.171875, 0.0), (1.2109375, 0.0), (1.23046875, 0.0), (1.240234375, 0.0), (1.25, 0.0), (1.875, 0.0), (2.1875, 0.0), (2.34375, 0.0), (2.5, 0.0), (3.75, 0.0), (5, 0), (2.5, 5.88)]\n", + "------------------------------\n" + ] + } + ], + "source": [ + "# Reusing the code from previous exercise with the shapes list and a new for loop to demonstarte the full funcctionality with the other shapes and new exercise 7 method.\n", + "shapes = [\n", + " Rectangle(10, 5, x=2, y=3),\n", + " Circle(7, x=0, y=0),\n", + " Triangle(5, 6, 7, x=0, y=0)\n", + "]\n", + "\n", + "for shape in shapes:\n", + " print(f\"Shape: {shape.__class__.__name__}\")\n", + " print(f\"Perimeter Points: {shape.get_perimeter_points()}\")\n", + " print(\"-\" * 30)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "8. Add a function to the object classes, including the base, that tests if a given set of $x$ and $y$ coordinates are inside of the object. You'll have to think through how to determine if a set of coordinates are inside an object for each object type." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In order to determine if a set of coordinates are inside of an object I added another abstract method to the base class and implemented this method in particular for each specific shape." + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "metadata": {}, + "outputs": [], + "source": [ + "from abc import ABC, abstractmethod # Importing again to avoid potential errors in runnning this cell before previous cells.\n", + "## Reusing the Shape class from the previous exercise\n", + "class Shape(ABC):\n", + " @abstractmethod\n", + " def compute_area(self):\n", + " \"\"\"Compute the area of the shape.\"\"\"\n", + " pass\n", + "\n", + " @abstractmethod\n", + " def compute_perimeter(self):\n", + " \"\"\"Compute the perimeter of the shape.\"\"\"\n", + " pass\n", + "\n", + " @abstractmethod\n", + " def get_center(self):\n", + " \"\"\"Get the center of the shape.\"\"\"\n", + " pass\n", + "\n", + " @abstractmethod\n", + " def get_corners(self):\n", + " \"\"\"Get the corners of the shape, if applicable.\"\"\"\n", + " pass\n", + "## This is the new method addded to the Shape class to return the perimeter points of the the object.\n", + " @abstractmethod\n", + " def get_perimeter_points(self, num_points=16):\n", + " \"\"\"Return up to 16 (x, y) points along the perimeter of the shape.\"\"\"\n", + " pass\n", + "## This is the new method to determine if a set of coordinates are inside of the object. \n", + " @abstractmethod\n", + " def contains_point(self, x, y):\n", + " \"\"\"Check if a given (x, y) point is inside the shape.\"\"\"\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "metadata": {}, + "outputs": [], + "source": [ + "class Rectangle(Shape):\n", + " def __init__(self, length, width, x=0, y=0, length_function=None, width_function=None):\n", + " self.__length = length\n", + " self.__width = width\n", + " self.__x = x\n", + " self.__y = y\n", + " self.__length_function = length_function if length_function else lambda l, w, x, y: l\n", + " self.__width_function = width_function if width_function else lambda l, w, x, y: w\n", + "## The @property decorator is used to define properties in Python. This puts it in a read only property, which is useful for this exercise.\n", + " @property\n", + " def length(self):\n", + " # Dynamically compute length based on the function of x and y\n", + " return self.__length_function(self.__length, self.__width, self.__x, self.__y)\n", + "\n", + " @property\n", + " def width(self):\n", + " # Dynamically compute width based on the function of x and y\n", + " return self.__width_function(self.__length, self.__width, self.__x, self.__y)\n", + "\n", + " @property\n", + " def x(self):\n", + " return self.__x\n", + "\n", + " @x.setter\n", + " def x(self, value):\n", + " if value < 0:\n", + " raise ValueError(\"x cannot be negative\")\n", + " self.__x = value\n", + "\n", + " @property\n", + " def y(self):\n", + " return self.__y\n", + "\n", + " @y.setter\n", + " def y(self, value):\n", + " if value < 0:\n", + " raise ValueError(\"y cannot be negative\")\n", + " self.__y = value\n", + "\n", + " def set_origin(self, origin_x, origin_y):\n", + " \"\"\"Update x and y based on a new origin.\"\"\"\n", + " self.__x = origin_x\n", + " self.__y = origin_y\n", + "\n", + " def compute_area(self):\n", + " return self.length * self.width\n", + "\n", + " def compute_perimeter(self):\n", + " return 2 * (self.length + self.width)\n", + "\n", + " def get_corners(self): # Function to conpute the corners of the rectangle given the length and width and corresponding origin, simplifying the code.\n", + " \"\"\"Returns the coordinates of the rectangle's corners based on (x, y) as the bottom-left corner.\"\"\"\n", + " return {\n", + " \"bottom_left\": (self.__x, self.__y),\n", + " \"bottom_right\": (self.__x + self.width, self.__y),\n", + " \"top_left\": (self.__x, self.__y + self.length),\n", + " \"top_right\": (self.__x + self.width, self.__y + self.length)\n", + " }\n", + " # Implementing abstract methods from Shape\n", + " def compute_area(self):\n", + " return self.length * self.width\n", + "\n", + " def compute_perimeter(self):\n", + " return 2 * (self.length + self.width)\n", + "\n", + " def get_center(self):\n", + " return (self.__x, self.__y)\n", + "\n", + " def get_corners(self):\n", + " return {\n", + " \"bottom_left\": (self.__x, self.__y),\n", + " \"bottom_right\": (self.__x + self.width, self.__y),\n", + " \"top_left\": (self.__x, self.__y + self.length),\n", + " \"top_right\": (self.__x + self.width, self.__y + self.length)\n", + " }\n", + " ## This is the new method added to the Rectangle class to return the perimeter points of the rectangle.\n", + " def get_perimeter_points(self, num_points=16):\n", + " \"\"\"Generate up to 16 points along the perimeter of the rectangle.\"\"\"\n", + " corners = self.get_corners()\n", + " points = []\n", + " \n", + " # Extract corner points\n", + " bl = corners[\"bottom_left\"]\n", + " br = corners[\"bottom_right\"]\n", + " tr = corners[\"top_right\"]\n", + " tl = corners[\"top_left\"]\n", + "\n", + " # Calculate the points along each edge\n", + " edge_points = num_points // 4 # Utilizing integer division (floor division operator //) to get the number of points per edge\n", + " \n", + " # Bottom edge\n", + " for i in range(edge_points):\n", + " t = i / edge_points\n", + " points.append((bl[0] + t * (br[0] - bl[0]), bl[1]))\n", + "\n", + " # Right edge\n", + " for i in range(edge_points):\n", + " t = i / edge_points\n", + " points.append((br[0], br[1] + t * (tr[1] - br[1])))\n", + "\n", + " # Top edge\n", + " for i in range(edge_points):\n", + " t = i / edge_points\n", + " points.append((tr[0] - t * (tr[0] - tl[0]), tr[1]))\n", + "\n", + " # Left edge\n", + " for i in range(edge_points):\n", + " t = i / edge_points\n", + " points.append((tl[0], tl[1] - t * (tl[1] - bl[1])))\n", + "\n", + " return points[:num_points]\n", + " ## This is the new method added to the Rectangle class to determine if a given point is inside the rectangle.\n", + " def contains_point(self, x, y):\n", + " \"\"\"Check if the point (x, y) is inside the rectangle.\"\"\"\n", + " return self.__x <= x <= self.__x + self.width and self.__y <= y <= self.__y + self.length" + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "metadata": {}, + "outputs": [], + "source": [ + "class Circle(Shape):\n", + " def __init__(self, radius, x, y):\n", + " self.__radius = radius\n", + " self.__x = x\n", + " self.__y = y\n", + " def area(self): # compute the area using the formula pi * r^2\n", + " return 3.14 * self.__radius ** 2\n", + " def circumfrence(self): # compute the perimeter using the formula 2 * pi * r\n", + " return round(2 * 3.14 * self.__radius, 2)\n", + " def get_center(self):\n", + " return self.__x, self.__y\n", + " def get_radius(self):\n", + " return self.__radius\n", + " def get_circumfrence(self):\n", + " return self.circumfrence()\n", + " def get_area(self):\n", + " return self.area()\n", + " def return_all(self):\n", + " print(f\"Area: {self.area()}\")\n", + " print(f\"Circumfrence: {self.circumfrence()}\")\n", + " print(f\"Center: {self.get_center()}\")\n", + " print(f\"Radius: {self.get_radius()}\")\n", + " return self.area(), self.circumfrence(), self.get_center(), self.get_radius()\n", + " # Implementing abstract methods from Shape\n", + " def compute_area(self):\n", + " return 3.14 * self.__radius ** 2\n", + "\n", + " def compute_perimeter(self):\n", + " return round(2 * 3.14 * self.__radius, 2)\n", + "\n", + " def get_center(self):\n", + " return (self.__x, self.__y)\n", + "\n", + " def get_corners(self):\n", + " \"\"\"Circles do not have corners, return None.\"\"\"\n", + " return None\n", + "\n", + " def get_radius(self):\n", + " return self.__radius\n", + " ## This is the new method added to the Circle class to return the perimeter points of the circle.\n", + " def get_perimeter_points(self, num_points=16):\n", + " \"\"\"Generate up to 16 points along the perimeter of the circle.\"\"\"\n", + " points = []\n", + " for i in range(num_points):\n", + " angle = 2 * math.pi * (i / num_points)\n", + " x = self.__x + self.__radius * math.cos(angle)\n", + " y = self.__y + self.__radius * math.sin(angle)\n", + " points.append((round(x, 2), round(y, 2)))\n", + " return points\n", + " ## This is the new method added to the Circle class to determine if a given point is inside the circle.\n", + " def contains_point(self, x, y):\n", + " \"\"\"Check if the point (x, y) is inside the circle.\"\"\"\n", + " distance = math.sqrt((x - self.__x) ** 2 + (y - self.__y) ** 2)\n", + " return distance <= self.__radius" + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "metadata": {}, + "outputs": [], + "source": [ + "import math # import the math module to use the sqrt function\n", + "# Simplified Triangle class using Heron's formula\n", + "class Triangle(Shape):\n", + " def __init__(self, a, b, c, x=0, y=0):\n", + " # Validate if the sides can form a triangle\n", + " if not (a + b > c and a + c > b and b + c > a):\n", + " raise ValueError(\"The provided sides do not form a valid triangle.\")\n", + " \n", + " self.__a = a\n", + " self.__b = b\n", + " self.__c = c\n", + " self.__x = x\n", + " self.__y = y\n", + "\n", + " # Implementing abstract methods from Shape\n", + " def compute_area(self):\n", + " \"\"\"Compute the area using Heron's formula.\"\"\"\n", + " s = self.compute_perimeter() / 2 # Semi-perimeter\n", + " area = math.sqrt(s * (s - self.__a) * (s - self.__b) * (s - self.__c))\n", + " return round(area, 2)\n", + "\n", + " def compute_perimeter(self):\n", + " \"\"\"Compute the perimeter as the sum of all sides.\"\"\"\n", + " return round(self.__a + self.__b + self.__c, 2)\n", + "\n", + " def get_center(self):\n", + " \"\"\"Return the centroid of the triangle (average of corners).\"\"\"\n", + " corners = self.get_corners()\n", + " centroid_x = sum([p[0] for p in corners.values()]) / 3\n", + " centroid_y = sum([p[1] for p in corners.values()]) / 3\n", + " return (round(centroid_x, 2), round(centroid_y, 2))\n", + "\n", + " def get_corners(self):\n", + " \"\"\"Assume (x, y) is the bottom-left corner and calculate other corners.\"\"\"\n", + " # Simple placement of the triangle along the base a\n", + " return {\n", + " \"bottom_left\": (self.__x, self.__y),\n", + " \"bottom_right\": (self.__x + self.__a, self.__y),\n", + " \"top_vertex\": (self.__x + self.__a / 2, self.__y + self.get_approximate_height())\n", + " }\n", + " def get_approximate_height(self):\n", + " \"\"\"Compute the approximate height for visualization purposes.\"\"\"\n", + " # Area = 1/2 * base * height => height = (2 * area) / base\n", + " area = self.compute_area()\n", + " return round((2 * area) / self.__a, 2)\n", + " ## This is the new method added to the Triangle class to return the perimeter points of the triangle.\n", + " def get_perimeter_points(self, num_points=16):\n", + " \"\"\"Generate up to 16 points along the perimeter of the triangle.\"\"\"\n", + " corners = self.get_corners()\n", + " points = list(corners.values())\n", + " \n", + " # Generate intermediate points along the edges\n", + " while len(points) < num_points:\n", + " for i in range(len(points) - 1):\n", + " mid_point = (\n", + " (points[i][0] + points[i + 1][0]) / 2,\n", + " (points[i][1] + points[i + 1][1]) / 2\n", + " )\n", + " points.insert(i + 1, mid_point)\n", + " if len(points) >= num_points:\n", + " break\n", + " return points[:num_points]\n", + " ## This is the new method added to the Triangle class to determine if a given point is inside the triangle.\n", + " def contains_point(self, x, y):\n", + " \"\"\"Check if the point (x, y) is inside the triangle using area comparison.\"\"\"\n", + " def area(x1, y1, x2, y2, x3, y3):\n", + " return abs((x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2.0)\n", + "\n", + " corners = self.get_corners()\n", + " x1, y1 = corners[\"bottom_left\"]\n", + " x2, y2 = corners[\"bottom_right\"]\n", + " x3, y3 = corners[\"top_vertex\"]\n", + "\n", + " a = area(x1, y1, x2, y2, x3, y3)\n", + " a1 = area(x, y, x2, y2, x3, y3)\n", + " a2 = area(x1, y1, x, y, x3, y3)\n", + " a3 = area(x1, y1, x2, y2, x, y)\n", + "\n", + " return a == a1 + a2 + a3" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Reusing the code from previous exercise with the shapes list and a new for loop to demonstarte the full funcctionality \n", + "# with the other shapes and new method from exercise 8. I have also added some test points to check if they are inside of the shapes\n", + "# using a similar for loop from previous exercises for continuity in the output." + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Testing points for shape: Rectangle\n", + "Point (5, 5) is inside the Rectangle.\n", + "Point (0, 0) is outside the Rectangle.\n", + "Point (10, 10) is outside the Rectangle.\n", + "Point (3, 4) is inside the Rectangle.\n", + "Point (7, 1) is outside the Rectangle.\n", + "--------------------------------------------------\n", + "Testing points for shape: Circle\n", + "Point (5, 5) is outside the Circle.\n", + "Point (0, 0) is inside the Circle.\n", + "Point (10, 10) is outside the Circle.\n", + "Point (3, 4) is inside the Circle.\n", + "Point (7, 1) is outside the Circle.\n", + "--------------------------------------------------\n", + "Testing points for shape: Triangle\n", + "Point (5, 5) is outside the Triangle.\n", + "Point (0, 0) is inside the Triangle.\n", + "Point (10, 10) is outside the Triangle.\n", + "Point (3, 4) is inside the Triangle.\n", + "Point (7, 1) is outside the Triangle.\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "\n", + "# Create shapes\n", + "shapes = [\n", + " Rectangle(10, 5, x=2, y=3),\n", + " Circle(7, x=0, y=0),\n", + " Triangle(5, 6, 7, x=0, y=0)\n", + "]\n", + "\n", + "# Test points\n", + "test_points = [(5, 5), (0, 0), (10, 10), (3, 4), (7, 1)]\n", + "\n", + "# Check if points are within shapes and print results\n", + "for shape in shapes:\n", + " print(f\"Testing points for shape: {shape.__class__.__name__}\")\n", + " for point in test_points:\n", + " is_inside = shape.contains_point(*point)\n", + " print(f\"Point {point} is {'inside' if is_inside else 'outside'} the {shape.__class__.__name__}.\")\n", + " print(\"-\" * 50)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "9. Add a function in the base class of the object classes that returns true/false testing that the object overlaps with another object." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Rectangle vs Rectangle:\n", + "* Two rectangles overlap if their projected ranges on both x and y axes overlap.\n", + "* Check if one rectangle’s edges are not completely outside the other rectangle’s edges.\n", + "\n", + "## Circle vs Circle:\n", + "* Circles overlap if the distance between their centers is less than the sum of their radii.\n", + "\n", + "## Triangle vs Triangle:\n", + "* Triangles overlap if any edge or vertex of one triangle is inside the other triangle.\n", + "\n", + "## Mixed Shapes:\n", + "* Use perimeter points and containment tests:\n", + "* Test if perimeter points of one shape are inside the other shape.\n", + "* If any point is inside, the shapes overlap." + ] + }, + { + "cell_type": "code", + "execution_count": 87, + "metadata": {}, + "outputs": [], + "source": [ + "from abc import ABC, abstractmethod # Importing again to avoid potential errors in runnning this cell before previous cells.\n", + "## Reusing the Shape class from the previous exercise\n", + "class Shape(ABC):\n", + " @abstractmethod\n", + " def compute_area(self):\n", + " \"\"\"Compute the area of the shape.\"\"\"\n", + " pass\n", + "\n", + " @abstractmethod\n", + " def compute_perimeter(self):\n", + " \"\"\"Compute the perimeter of the shape.\"\"\"\n", + " pass\n", + "\n", + " @abstractmethod\n", + " def get_center(self):\n", + " \"\"\"Get the center of the shape.\"\"\"\n", + " pass\n", + "\n", + " @abstractmethod\n", + " def get_corners(self):\n", + " \"\"\"Get the corners of the shape, if applicable.\"\"\"\n", + " pass\n", + "## This is the new method addded to the Shape class to return the perimeter points of the the object.\n", + " @abstractmethod\n", + " def get_perimeter_points(self, num_points=16):\n", + " \"\"\"Return up to 16 (x, y) points along the perimeter of the shape.\"\"\"\n", + " pass\n", + "## This is the new method to determine if a set of coordinates are inside of the object. \n", + " @abstractmethod\n", + " def contains_point(self, x, y):\n", + " \"\"\"Check if a given (x, y) point is inside the shape.\"\"\"\n", + " pass\n", + "## This is the new method to determine if the object overlaps with another object.\n", + " @abstractmethod\n", + " def overlaps_with(self, other: \"Shape\") -> bool:\n", + " \"\"\"Determine if this shape overlaps with another shape.\"\"\"\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "metadata": {}, + "outputs": [], + "source": [ + "class Rectangle(Shape):\n", + " def __init__(self, length, width, x=0, y=0, length_function=None, width_function=None):\n", + " self.__length = length\n", + " self.__width = width\n", + " self.__x = x\n", + " self.__y = y\n", + " self.__length_function = length_function if length_function else lambda l, w, x, y: l\n", + " self.__width_function = width_function if width_function else lambda l, w, x, y: w\n", + "## The @property decorator is used to define properties in Python. This puts it in a read only property, which is useful for this exercise.\n", + " @property\n", + " def length(self):\n", + " # Dynamically compute length based on the function of x and y\n", + " return self.__length_function(self.__length, self.__width, self.__x, self.__y)\n", + "\n", + " @property\n", + " def width(self):\n", + " # Dynamically compute width based on the function of x and y\n", + " return self.__width_function(self.__length, self.__width, self.__x, self.__y)\n", + "\n", + " @property\n", + " def x(self):\n", + " return self.__x\n", + "\n", + " @x.setter\n", + " def x(self, value):\n", + " if value < 0:\n", + " raise ValueError(\"x cannot be negative\")\n", + " self.__x = value\n", + "\n", + " @property\n", + " def y(self):\n", + " return self.__y\n", + "\n", + " @y.setter\n", + " def y(self, value):\n", + " if value < 0:\n", + " raise ValueError(\"y cannot be negative\")\n", + " self.__y = value\n", + "\n", + " def set_origin(self, origin_x, origin_y):\n", + " \"\"\"Update x and y based on a new origin.\"\"\"\n", + " self.__x = origin_x\n", + " self.__y = origin_y\n", + "\n", + " def compute_area(self):\n", + " return self.length * self.width\n", + "\n", + " def compute_perimeter(self):\n", + " return 2 * (self.length + self.width)\n", + "\n", + " def get_corners(self): # Function to conpute the corners of the rectangle given the length and width and corresponding origin, simplifying the code.\n", + " \"\"\"Returns the coordinates of the rectangle's corners based on (x, y) as the bottom-left corner.\"\"\"\n", + " return {\n", + " \"bottom_left\": (self.__x, self.__y),\n", + " \"bottom_right\": (self.__x + self.width, self.__y),\n", + " \"top_left\": (self.__x, self.__y + self.length),\n", + " \"top_right\": (self.__x + self.width, self.__y + self.length)\n", + " }\n", + " # Implementing abstract methods from Shape\n", + " def compute_area(self):\n", + " return self.length * self.width\n", + "\n", + " def compute_perimeter(self):\n", + " return 2 * (self.length + self.width)\n", + "\n", + " def get_center(self):\n", + " return (self.__x, self.__y)\n", + "\n", + " def get_corners(self):\n", + " return {\n", + " \"bottom_left\": (self.__x, self.__y),\n", + " \"bottom_right\": (self.__x + self.width, self.__y),\n", + " \"top_left\": (self.__x, self.__y + self.length),\n", + " \"top_right\": (self.__x + self.width, self.__y + self.length)\n", + " }\n", + " ## This is the new method added to the Rectangle class to return the perimeter points of the rectangle.\n", + " def get_perimeter_points(self, num_points=16):\n", + " \"\"\"Generate up to 16 points along the perimeter of the rectangle.\"\"\"\n", + " corners = self.get_corners()\n", + " points = []\n", + " \n", + " # Extract corner points\n", + " bl = corners[\"bottom_left\"]\n", + " br = corners[\"bottom_right\"]\n", + " tr = corners[\"top_right\"]\n", + " tl = corners[\"top_left\"]\n", + "\n", + " # Calculate the points along each edge\n", + " edge_points = num_points // 4 # Utilizing integer division (floor division operator //) to get the number of points per edge\n", + " \n", + " # Bottom edge\n", + " for i in range(edge_points):\n", + " t = i / edge_points\n", + " points.append((bl[0] + t * (br[0] - bl[0]), bl[1]))\n", + "\n", + " # Right edge\n", + " for i in range(edge_points):\n", + " t = i / edge_points\n", + " points.append((br[0], br[1] + t * (tr[1] - br[1])))\n", + "\n", + " # Top edge\n", + " for i in range(edge_points):\n", + " t = i / edge_points\n", + " points.append((tr[0] - t * (tr[0] - tl[0]), tr[1]))\n", + "\n", + " # Left edge\n", + " for i in range(edge_points):\n", + " t = i / edge_points\n", + " points.append((tl[0], tl[1] - t * (tl[1] - bl[1])))\n", + "\n", + " return points[:num_points]\n", + " ## This is the new method added to the Rectangle class to determine if a given point is inside the rectangle.\n", + " def contains_point(self, x, y):\n", + " \"\"\"Check if the point (x, y) is inside the rectangle.\"\"\"\n", + " return self.__x <= x <= self.__x + self.width and self.__y <= y <= self.__y + self.length\n", + " ## This is the new method added to the Rectangle class to determine if the rectangle overlaps with another shape.\n", + "\n", + " def overlaps_with(self, other: Shape) -> bool:\n", + " \"\"\"Determine if this rectangle overlaps with another shape.\"\"\"\n", + " if isinstance(other, Rectangle):\n", + " # Check if there is no overlap\n", + " if (self.__x + self.__width < other.__x or\n", + " other.__x + other.__width < self.__x or\n", + " self.__y + self.__length < other.__y or\n", + " other.__y + other.__length < self.__y):\n", + " return False\n", + " return True\n", + " else:\n", + " # Check if any perimeter point of either shape is inside the other\n", + " for point in self.get_perimeter_points():\n", + " if other.contains_point(*point):\n", + " return True\n", + " for point in other.get_perimeter_points():\n", + " if self.contains_point(*point):\n", + " return True\n", + " return False" + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "metadata": {}, + "outputs": [], + "source": [ + "class Circle(Shape):\n", + " def __init__(self, radius, x, y):\n", + " self.__radius = radius\n", + " self.__x = x\n", + " self.__y = y\n", + " def area(self): # compute the area using the formula pi * r^2\n", + " return 3.14 * self.__radius ** 2\n", + " def circumfrence(self): # compute the perimeter using the formula 2 * pi * r\n", + " return round(2 * 3.14 * self.__radius, 2)\n", + " def get_center(self):\n", + " return self.__x, self.__y\n", + " def get_radius(self):\n", + " return self.__radius\n", + " def get_circumfrence(self):\n", + " return self.circumfrence()\n", + " def get_area(self):\n", + " return self.area()\n", + " def return_all(self):\n", + " print(f\"Area: {self.area()}\")\n", + " print(f\"Circumfrence: {self.circumfrence()}\")\n", + " print(f\"Center: {self.get_center()}\")\n", + " print(f\"Radius: {self.get_radius()}\")\n", + " return self.area(), self.circumfrence(), self.get_center(), self.get_radius()\n", + " # Implementing abstract methods from Shape\n", + " def compute_area(self):\n", + " return 3.14 * self.__radius ** 2\n", + "\n", + " def compute_perimeter(self):\n", + " return round(2 * 3.14 * self.__radius, 2)\n", + "\n", + " def get_center(self):\n", + " return (self.__x, self.__y)\n", + "\n", + " def get_corners(self):\n", + " \"\"\"Circles do not have corners, return None.\"\"\"\n", + " return None\n", + "\n", + " def get_radius(self):\n", + " return self.__radius\n", + " ## This is the new method added to the Circle class to return the perimeter points of the circle.\n", + " def get_perimeter_points(self, num_points=16):\n", + " \"\"\"Generate up to 16 points along the perimeter of the circle.\"\"\"\n", + " points = []\n", + " for i in range(num_points):\n", + " angle = 2 * math.pi * (i / num_points)\n", + " x = self.__x + self.__radius * math.cos(angle)\n", + " y = self.__y + self.__radius * math.sin(angle)\n", + " points.append((round(x, 2), round(y, 2)))\n", + " return points\n", + " ## This is the new method added to the Circle class to determine if a given point is inside the circle.\n", + " def contains_point(self, x, y):\n", + " \"\"\"Check if the point (x, y) is inside the circle.\"\"\"\n", + " distance = math.sqrt((x - self.__x) ** 2 + (y - self.__y) ** 2)\n", + " return distance <= self.__radius\n", + " ## This is the new method added to the Circle class to determine if the circle overlaps with another shape.\n", + " def overlaps_with(self, other: Shape) -> bool:\n", + " \"\"\"Determine if this circle overlaps with another shape.\"\"\"\n", + " if isinstance(other, Circle):\n", + " distance = math.sqrt((self.__x - other.__x) ** 2 + (self.__y - other.__y) ** 2)\n", + " return distance < (self.__radius + other.__radius)\n", + " else:\n", + " # Check perimeter points of both shapes for containment\n", + " for point in self.get_perimeter_points():\n", + " if other.contains_point(*point):\n", + " return True\n", + " for point in other.get_perimeter_points():\n", + " if self.contains_point(*point):\n", + " return True\n", + " return False" + ] + }, + { + "cell_type": "code", + "execution_count": 90, + "metadata": {}, + "outputs": [], + "source": [ + "import math # import the math module to use the sqrt function\n", + "# Simplified Triangle class using Heron's formula\n", + "class Triangle(Shape):\n", + " def __init__(self, a, b, c, x=0, y=0):\n", + " # Validate if the sides can form a triangle\n", + " if not (a + b > c and a + c > b and b + c > a):\n", + " raise ValueError(\"The provided sides do not form a valid triangle.\")\n", + " \n", + " self.__a = a\n", + " self.__b = b\n", + " self.__c = c\n", + " self.__x = x\n", + " self.__y = y\n", + "\n", + " # Implementing abstract methods from Shape\n", + " def compute_area(self):\n", + " \"\"\"Compute the area using Heron's formula.\"\"\"\n", + " s = self.compute_perimeter() / 2 # Semi-perimeter\n", + " area = math.sqrt(s * (s - self.__a) * (s - self.__b) * (s - self.__c))\n", + " return round(area, 2)\n", + "\n", + " def compute_perimeter(self):\n", + " \"\"\"Compute the perimeter as the sum of all sides.\"\"\"\n", + " return round(self.__a + self.__b + self.__c, 2)\n", + "\n", + " def get_center(self):\n", + " \"\"\"Return the centroid of the triangle (average of corners).\"\"\"\n", + " corners = self.get_corners()\n", + " centroid_x = sum([p[0] for p in corners.values()]) / 3\n", + " centroid_y = sum([p[1] for p in corners.values()]) / 3\n", + " return (round(centroid_x, 2), round(centroid_y, 2))\n", + "\n", + " def get_corners(self):\n", + " \"\"\"Assume (x, y) is the bottom-left corner and calculate other corners.\"\"\"\n", + " # Simple placement of the triangle along the base a\n", + " return {\n", + " \"bottom_left\": (self.__x, self.__y),\n", + " \"bottom_right\": (self.__x + self.__a, self.__y),\n", + " \"top_vertex\": (self.__x + self.__a / 2, self.__y + self.get_approximate_height())\n", + " }\n", + " def get_approximate_height(self):\n", + " \"\"\"Compute the approximate height for visualization purposes.\"\"\"\n", + " # Area = 1/2 * base * height => height = (2 * area) / base\n", + " area = self.compute_area()\n", + " return round((2 * area) / self.__a, 2)\n", + " ## This is the new method added to the Triangle class to return the perimeter points of the triangle.\n", + " def get_perimeter_points(self, num_points=16):\n", + " \"\"\"Generate up to 16 points along the perimeter of the triangle.\"\"\"\n", + " corners = self.get_corners()\n", + " points = list(corners.values())\n", + " \n", + " # Generate intermediate points along the edges\n", + " while len(points) < num_points:\n", + " for i in range(len(points) - 1):\n", + " mid_point = (\n", + " (points[i][0] + points[i + 1][0]) / 2,\n", + " (points[i][1] + points[i + 1][1]) / 2\n", + " )\n", + " points.insert(i + 1, mid_point)\n", + " if len(points) >= num_points:\n", + " break\n", + " return points[:num_points]\n", + " ## This is the new method added to the Triangle class to determine if a given point is inside the triangle.\n", + " def contains_point(self, x, y):\n", + " \"\"\"Check if the point (x, y) is inside the triangle using area comparison.\"\"\"\n", + " def area(x1, y1, x2, y2, x3, y3):\n", + " return abs((x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2.0)\n", + "\n", + " corners = self.get_corners()\n", + " x1, y1 = corners[\"bottom_left\"]\n", + " x2, y2 = corners[\"bottom_right\"]\n", + " x3, y3 = corners[\"top_vertex\"]\n", + "\n", + " a = area(x1, y1, x2, y2, x3, y3)\n", + " a1 = area(x, y, x2, y2, x3, y3)\n", + " a2 = area(x1, y1, x, y, x3, y3)\n", + " a3 = area(x1, y1, x2, y2, x, y)\n", + "\n", + " return a == a1 + a2 + a3\n", + " ## This is the new method added to the Triangle class to determine if the triangle overlaps with another shape.\n", + " def overlaps_with(self, other: Shape) -> bool:\n", + " \"\"\"Determine if this triangle overlaps with another shape.\"\"\"\n", + " if isinstance(other, Triangle):\n", + " # Check if any perimeter point of either triangle is inside the other\n", + " for point in self.get_perimeter_points():\n", + " if other.contains_point(*point):\n", + " return True\n", + " for point in other.get_perimeter_points():\n", + " if self.contains_point(*point):\n", + " return True\n", + " else:\n", + " # Mixed shape overlap detection\n", + " for point in self.get_perimeter_points():\n", + " if other.contains_point(*point):\n", + " return True\n", + " for point in other.get_perimeter_points():\n", + " if self.contains_point(*point):\n", + " return True\n", + " return False" + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Rectangle overlaps with Circle: True\n", + "Rectangle overlaps with Triangle: True\n", + "Circle overlaps with Triangle: True\n" + ] + } + ], + "source": [ + "## Reusing the code from previous exercise with the shapes list and a new for loop to test for overlap and to demonstarte the full funcctionality \n", + "# with the other shapes using the new exercise 9 method.\n", + "# Create shapes\n", + "shapes = [\n", + " Rectangle(10, 5, x=2, y=3),\n", + " Circle(7, x=5, y=5),\n", + " Triangle(5, 6, 7, x=0, y=0)\n", + "]\n", + "\n", + "# Test for overlaps between all shapes\n", + "for i, shape1 in enumerate(shapes):\n", + " for j, shape2 in enumerate(shapes):\n", + " if i < j:\n", + " overlap = shape1.overlaps_with(shape2)\n", + " print(f\"{shape1.__class__.__name__} overlaps with {shape2.__class__.__name__}: {overlap}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "10. Copy the `Canvas` class from lecture to in a python file creating a `paint` module. Copy your classes from above into the module and implement paint functions. Implement a `CompoundShape` class. Create a simple drawing demonstrating that all of your classes are working." + ] + }, + { + "cell_type": "code", + "execution_count": 103, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The autoreload extension is already loaded. To reload it, use:\n", + " %reload_ext autoreload\n" + ] + } + ], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "\n", + "import importlib\n", + "import Paint\n", + "importlib.reload(Paint)\n", + "\n", + "from Paint import Canvas, Rectangle, Circle, Triangle, CompoundShape" + ] + }, + { + "cell_type": "code", + "execution_count": 104, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " \n", + " \n", + " ##### \n", + " ##### \n", + " ##### \n", + " ##### \n", + " ##### \n", + " ##### @@@@@@ \n", + " ##### @@ @@ \n", + " ##### @ @ \n", + " ##### @ @@ \n", + " ##### @@ @@ \n", + " @@@@@@ \n", + " \n", + " \n", + " ******** \n", + " * \n", + " \n", + " * \n", + " \n" + ] + } + ], + "source": [ + "# Create a canvas with a square aspect ratio\n", + "canvas = Canvas(40, 20)\n", + "\n", + "# Create individual shapes\n", + "rectangle = Rectangle(10, 5, x=5, y=2, char='#')\n", + "circle = Circle(5, x=20, y=10, char='@')\n", + "triangle = Triangle(7, 6, 8, x=10, y=15, char='*')\n", + "\n", + "# Create a compound shape\n", + "compound_shape = CompoundShape(rectangle, circle, triangle)\n", + "\n", + "# Paint shapes on the canvas\n", + "compound_shape.paint(canvas)\n", + "\n", + "# Display the result\n", + "canvas.display()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "11. Create a `RasterDrawing` class. Demonstrate that you can create a drawing made of several shapes, paint the drawing, modify the drawing, and paint it again. " + ] + }, + { + "cell_type": "code", + "execution_count": 105, + "metadata": {}, + "outputs": [], + "source": [ + "from Paint import Canvas, Shape\n", + "\n", + "class RasterDrawing:\n", + " def __init__(self, width, height, bg_char=' '):\n", + " self.canvas = Canvas(width, height)\n", + " self.shapes = []\n", + " self.bg_char = bg_char\n", + "\n", + " def add_shape(self, shape: Shape):\n", + " \"\"\"Add a shape to the drawing.\"\"\"\n", + " self.shapes.append(shape)\n", + "\n", + " def remove_shape(self, shape: Shape):\n", + " \"\"\"Remove a shape from the drawing.\"\"\"\n", + " if shape in self.shapes:\n", + " self.shapes.remove(shape)\n", + "\n", + " def clear_shapes(self):\n", + " \"\"\"Remove all shapes from the drawing.\"\"\"\n", + " self.shapes = []\n", + "\n", + " def paint(self):\n", + " \"\"\"Paint all shapes on the canvas.\"\"\"\n", + " self.canvas.clear_canvas()\n", + " for shape in self.shapes:\n", + " shape.paint(self.canvas)\n", + " self.canvas.display()" + ] + }, + { + "cell_type": "code", + "execution_count": 108, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Initial Drawing:\n", + " \n", + " \n", + " ##### \n", + " ##### \n", + " ##### \n", + " ##### \n", + " ##### \n", + " ##### @@@@@@ \n", + " ##### @@ @@ \n", + " ##### @ @ \n", + " ##### @ @@ \n", + " ##### @@ @@ \n", + " @@@@@@ \n", + " \n", + " \n", + " ******** \n", + " * \n", + " \n", + " * \n", + " \n", + "\n", + "Modified Drawing:\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " @@@@@@ ++++++++ \n", + " @@ @@ \n", + " @ @ + \n", + " @#### @@ \n", + " @@### @@ + \n", + " ##@@@@@@ \n", + " ##### + \n", + " ##### \n", + " ********## \n", + " * ##### \n", + " ##### \n", + " * ##### \n", + " ##### \n" + ] + } + ], + "source": [ + "from Paint import Rectangle, Circle, Triangle, CompoundShape\n", + "\n", + "# Create a new RasterDrawing with a canvas of 40x20\n", + "drawing = RasterDrawing(40, 20)\n", + "\n", + "# Create shapes\n", + "rectangle = Rectangle(10, 5, x=2, y=2, char='#')\n", + "circle = Circle(5, x=20, y=10, char='@')\n", + "triangle = Triangle(7, 6, 8, x=10, y=15, char='*')\n", + "\n", + "# Add shapes to the drawing\n", + "drawing.add_shape(rectangle)\n", + "drawing.add_shape(circle)\n", + "drawing.add_shape(triangle)\n", + "\n", + "# Paint the initial drawing\n", + "print(\"Initial Drawing:\")\n", + "drawing.paint()\n", + "\n", + "# Modify the drawing\n", + "print(\"\\nModified Drawing:\")\n", + "\n", + "# Move the rectangle and add a new shape\n", + "rectangle.x = 15\n", + "rectangle.y = 10\n", + "\n", + "# Add a new triangle\n", + "new_triangle = Triangle(7, 7, 7, x=25, y=7, char='+')\n", + "drawing.add_shape(new_triangle)\n", + "\n", + "# Paint the modified drawing\n", + "drawing.paint()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "12. Implement the ability to load/save raster drawings and demonstate that your method works. One way to implement this ability:\n", + "\n", + " * Overload `__repr__` functions of all objects to return strings of the python code that would construct the object.\n", + " \n", + " * In the save method of raster drawing class, store the representations into the file.\n", + " * Write a loader function that reads the file and uses `eval` to instantiate the object.\n", + "\n", + "For example:" + ] + }, + { + "cell_type": "code", + "execution_count": 109, + "metadata": {}, + "outputs": [], + "source": [ + "class foo:\n", + " def __init__(self,a,b=None):\n", + " self.a=a\n", + " self.b=b\n", + " \n", + " def __repr__(self):\n", + " return \"foo(\"+repr(self.a)+\",\"+repr(self.b)+\")\"\n", + " \n", + " def save(self,filename):\n", + " f=open(filename,\"w\")\n", + " f.write(self.__repr__())\n", + " f.close()\n", + " \n", + " \n", + "def foo_loader(filename):\n", + " f=open(filename,\"r\")\n", + " tmp=eval(f.read())\n", + " f.close()\n", + " return tmp\n" + ] + }, + { + "cell_type": "code", + "execution_count": 110, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "foo(1,'hello')\n" + ] + } + ], + "source": [ + "# Test\n", + "print(repr(foo(1,\"hello\")))" + ] + }, + { + "cell_type": "code", + "execution_count": 111, + "metadata": {}, + "outputs": [], + "source": [ + "# Create an object and save it\n", + "ff=foo(1,\"hello\")\n", + "ff.save(\"Test.foo\")" + ] + }, + { + "cell_type": "code", + "execution_count": 112, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "foo(1,'hello')" + ] + } + ], + "source": [ + "# Check contents of the saved file\n", + "!cat Test.foo" + ] + }, + { + "cell_type": "code", + "execution_count": 114, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "foo(1,'hello')" + ] + }, + "execution_count": 114, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Load the object\n", + "ff_reloaded=foo_loader(\"Test.foo\")\n", + "ff_reloaded" + ] + }, + { + "cell_type": "code", + "execution_count": 120, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The autoreload extension is already loaded. To reload it, use:\n", + " %reload_ext autoreload\n" + ] + } + ], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "\n", + "import importlib\n", + "import Paint\n", + "importlib.reload(Paint)\n", + "\n", + "from Paint import Canvas, Rectangle, Circle, Triangle, CompoundShape, RasterDrawing" + ] + }, + { + "cell_type": "code", + "execution_count": 121, + "metadata": {}, + "outputs": [], + "source": [ + "def raster_drawing_loader(filename: str) -> RasterDrawing:\n", + " \"\"\"Load a RasterDrawing from a file.\"\"\"\n", + " with open(filename, 'r') as f:\n", + " return eval(f.read())" + ] + }, + { + "cell_type": "code", + "execution_count": 122, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Initial Drawing:\n", + " \n", + " \n", + " ##### \n", + " ##### \n", + " ##### \n", + " ##### \n", + " ##### \n", + " ##### @@@@@@ \n", + " ##### @@ @@ \n", + " ##### @ @ \n", + " ##### @ @@ \n", + " ##### @@ @@ \n", + " @@@@@@ \n", + " \n", + " \n", + " ******** \n", + " * \n", + " \n", + " * \n", + " \n", + "\n", + "Contents of Saved File:\n", + "RasterDrawing(40, 20, bg_char=' ').add_shapes([Rectangle(10, 5, x=5, y=2, char='#'), Circle(5, x=20, y=10, char='@'), Triangle(7, 6, 8, x=10, y=15, char='*')])\n", + "Loaded Drawing:\n", + " \n", + " \n", + " ##### \n", + " ##### \n", + " ##### \n", + " ##### \n", + " ##### \n", + " ##### @@@@@@ \n", + " ##### @@ @@ \n", + " ##### @ @ \n", + " ##### @ @@ \n", + " ##### @@ @@ \n", + " @@@@@@ \n", + " \n", + " \n", + " ******** \n", + " * \n", + " \n", + " * \n", + " \n" + ] + } + ], + "source": [ + "# Create a RasterDrawing with shapes\n", + "drawing = RasterDrawing(40, 20)\n", + "\n", + "# Add shapes\n", + "drawing.add_shape(Rectangle(10, 5, x=5, y=2, char='#'))\n", + "drawing.add_shape(Circle(5, x=20, y=10, char='@'))\n", + "drawing.add_shape(Triangle(7, 6, 8, x=10, y=15, char='*'))\n", + "\n", + "# Paint the initial drawing\n", + "print(\"Initial Drawing:\")\n", + "drawing.paint()\n", + "\n", + "# Save the drawing to a file\n", + "drawing.save(\"drawing.raster\")\n", + "\n", + "# Check the contents of the saved file\n", + "print(\"\\nContents of Saved File:\")\n", + "!cat drawing.raster\n", + "\n", + "# Load the drawing from the file\n", + "loaded_drawing = raster_drawing_loader(\"drawing.raster\")\n", + "\n", + "# Paint the loaded drawing\n", + "print(\"\\nLoaded Drawing:\")\n", + "loaded_drawing.paint()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Labs/Lab.4/Paint.py b/Labs/Lab.4/Paint.py new file mode 100644 index 000000000..6824dff6b --- /dev/null +++ b/Labs/Lab.4/Paint.py @@ -0,0 +1,505 @@ +class Canvas: + def __init__(self, width, height): + self.width = width + self.height = height + self.data = [[' '] * width for i in range(height)] + + def set_pixel(self, row, col, char='*'): + if 0 <= row < self.height and 0 <= col < self.width: + self.data[row][col] = char + + def get_pixel(self, row, col): + return self.data[row][col] + + def clear_canvas(self): + self.data = [[' '] * self.width for i in range(self.height)] + + def v_line(self, x, y, w, char='*'): + for i in range(x, x + w): + self.set_pixel(i, y, char) + + def h_line(self, x, y, h, char='*'): + for i in range(y, y + h): + self.set_pixel(x, i, char) + + def line(self, x1, y1, x2, y2, char='*'): + if x2 == x1: + for y in range(y1, y2 + 1): + self.set_pixel(y, x1, char) + else: + slope = (y2 - y1) / (x2 - x1) + for x in range(x1, x2 + 1): + y = int(y1 + slope * (x - x1)) + self.set_pixel(y, x, char) + + def display(self): + print("\n".join(["".join(row) for row in self.data])) + +from abc import ABC, abstractmethod # Importing again to avoid potential errors in runnning this cell before previous cells. +## Reusing the Shape class from the previous exercise +class Shape(ABC): + @abstractmethod + def compute_area(self): + """Compute the area of the shape.""" + pass + + @abstractmethod + def compute_perimeter(self): + """Compute the perimeter of the shape.""" + pass + + @abstractmethod + def get_center(self): + """Get the center of the shape.""" + pass + + @abstractmethod + def get_corners(self): + """Get the corners of the shape, if applicable.""" + pass +## This is the new method addded to the Shape class to return the perimeter points of the the object. + @abstractmethod + def get_perimeter_points(self, num_points=16): + """Return up to 16 (x, y) points along the perimeter of the shape.""" + pass +## This is the new method to determine if a set of coordinates are inside of the object. + @abstractmethod + def contains_point(self, x, y): + """Check if a given (x, y) point is inside the shape.""" + pass +## This is the new method to determine if the object overlaps with another object. + @abstractmethod + def overlaps_with(self, other: "Shape") -> bool: + """Determine if this shape overlaps with another shape.""" + pass + @abstractmethod + def __repr__(self): + """Return a string that can be used to recreate the object.""" + pass + +class Rectangle(Shape): + def __init__(self, length, width, x=0, y=0, char = "#"): + self.__length = length + self.__width = width + self.__x = x + self.__y = y + self.char = char +## The @property decorator is used to define properties in Python. This puts it in a read only property, which is useful for this exercise. + @property + def length(self): + # Dynamically compute length based on the function of x and y + return self.__length_function(self.__length, self.__width, self.__x, self.__y) + + @property + def width(self): + # Dynamically compute width based on the function of x and y + return self.__width_function(self.__length, self.__width, self.__x, self.__y) + + @property + def x(self): + return self.__x + + @x.setter + def x(self, value): + if value < 0: + raise ValueError("x cannot be negative") + self.__x = value + + @property + def y(self): + return self.__y + + @y.setter + def y(self, value): + if value < 0: + raise ValueError("y cannot be negative") + self.__y = value + + def set_origin(self, origin_x, origin_y): + """Update x and y based on a new origin.""" + self.__x = origin_x + self.__y = origin_y + + def compute_area(self): + return self.length * self.width + + def compute_perimeter(self): + return 2 * (self.length + self.width) + + def get_corners(self): # Function to conpute the corners of the rectangle given the length and width and corresponding origin, simplifying the code. + """Returns the coordinates of the rectangle's corners based on (x, y) as the bottom-left corner.""" + return { + "bottom_left": (self.__x, self.__y), + "bottom_right": (self.__x + self.width, self.__y), + "top_left": (self.__x, self.__y + self.length), + "top_right": (self.__x + self.width, self.__y + self.length) + } + # Implementing abstract methods from Shape + def compute_area(self): + return self.length * self.width + + def compute_perimeter(self): + return 2 * (self.length + self.width) + + def get_center(self): + return (self.__x, self.__y) + + def get_corners(self): + return { + "bottom_left": (self.__x, self.__y), + "bottom_right": (self.__x + self.width, self.__y), + "top_left": (self.__x, self.__y + self.length), + "top_right": (self.__x + self.width, self.__y + self.length) + } + ## This is the new method added to the Rectangle class to return the perimeter points of the rectangle. + def get_perimeter_points(self, num_points=16): + """Generate up to 16 points along the perimeter of the rectangle.""" + corners = self.get_corners() + points = [] + + # Extract corner points + bl = corners["bottom_left"] + br = corners["bottom_right"] + tr = corners["top_right"] + tl = corners["top_left"] + + # Calculate the points along each edge + edge_points = num_points // 4 # Utilizing integer division (floor division operator //) to get the number of points per edge + + # Bottom edge + for i in range(edge_points): + t = i / edge_points + points.append((bl[0] + t * (br[0] - bl[0]), bl[1])) + + # Right edge + for i in range(edge_points): + t = i / edge_points + points.append((br[0], br[1] + t * (tr[1] - br[1]))) + + # Top edge + for i in range(edge_points): + t = i / edge_points + points.append((tr[0] - t * (tr[0] - tl[0]), tr[1])) + + # Left edge + for i in range(edge_points): + t = i / edge_points + points.append((tl[0], tl[1] - t * (tl[1] - bl[1]))) + + return points[:num_points] + ## This is the new method added to the Rectangle class to determine if a given point is inside the rectangle. + def contains_point(self, x, y): + """Check if the point (x, y) is inside the rectangle.""" + return self.__x <= x <= self.__x + self.width and self.__y <= y <= self.__y + self.length + ## This is the new method added to the Rectangle class to determine if the rectangle overlaps with another shape. + + def overlaps_with(self, other: Shape) -> bool: + """Determine if this rectangle overlaps with another shape.""" + if isinstance(other, Rectangle): + # Check if there is no overlap + if (self.__x + self.__width < other.__x or + other.__x + other.__width < self.__x or + self.__y + self.__length < other.__y or + other.__y + other.__length < self.__y): + return False + return True + else: + # Check if any perimeter point of either shape is inside the other + for point in self.get_perimeter_points(): + if other.contains_point(*point): + return True + for point in other.get_perimeter_points(): + if self.contains_point(*point): + return True + return False + def paint(self, canvas: Canvas): + for i in range(self.__length): + canvas.h_line(self.__y + i, self.__x, self.__width, char=self.char) + def __repr__(self): + return f"Rectangle({self.__length}, {self.__width}, x={self.__x}, y={self.__y}, char={repr(self.char)})" + +class Circle(Shape): + def __init__(self, radius, x =0, y = 0, char = '@'): + self.__radius = radius + self.__x = x + self.__y = y + self.char = char + def area(self): # compute the area using the formula pi * r^2 + return 3.14 * self.__radius ** 2 + def circumfrence(self): # compute the perimeter using the formula 2 * pi * r + return round(2 * 3.14 * self.__radius, 2) + def get_center(self): + return self.__x, self.__y + def get_radius(self): + return self.__radius + def get_circumfrence(self): + return self.circumfrence() + def get_area(self): + return self.area() + def return_all(self): + print(f"Area: {self.area()}") + print(f"Circumfrence: {self.circumfrence()}") + print(f"Center: {self.get_center()}") + print(f"Radius: {self.get_radius()}") + return self.area(), self.circumfrence(), self.get_center(), self.get_radius() + # Implementing abstract methods from Shape + def compute_area(self): + return 3.14 * self.__radius ** 2 + + def compute_perimeter(self): + return round(2 * 3.14 * self.__radius, 2) + + def get_center(self): + return (self.__x, self.__y) + + def get_corners(self): + """Circles do not have corners, return None.""" + return None + + def get_radius(self): + return self.__radius + ## This is the new method added to the Circle class to return the perimeter points of the circle. + def get_perimeter_points(self, num_points=16): + """Generate up to 16 points along the perimeter of the circle.""" + points = [] + for i in range(num_points): + angle = 2 * math.pi * (i / num_points) + x = self.__x + self.__radius * math.cos(angle) + y = self.__y + self.__radius * math.sin(angle) + points.append((round(x, 2), round(y, 2))) + return points + ## This is the new method added to the Circle class to determine if a given point is inside the circle. + def contains_point(self, x, y): + """Check if the point (x, y) is inside the circle.""" + distance = math.sqrt((x - self.__x) ** 2 + (y - self.__y) ** 2) + return distance <= self.__radius + ## This is the new method added to the Circle class to determine if the circle overlaps with another shape. + def overlaps_with(self, other: Shape) -> bool: + """Determine if this circle overlaps with another shape.""" + if isinstance(other, Circle): + distance = math.sqrt((self.__x - other.__x) ** 2 + (self.__y - other.__y) ** 2) + return distance < (self.__radius + other.__radius) + else: + # Check perimeter points of both shapes for containment + for point in self.get_perimeter_points(): + if other.contains_point(*point): + return True + for point in other.get_perimeter_points(): + if self.contains_point(*point): + return True + return False + def paint(self, canvas: Canvas): + aspect_ratio = canvas.height / canvas.width + for angle in range(0, 360, 10): + rad = math.radians(angle) + px = int(self.__x + self.__radius * math.cos(rad)) + py = int(self.__y + self.__radius * math.sin(rad) * aspect_ratio) + canvas.set_pixel(py, px, char=self.char) + def __repr__(self): + return f"Circle({self.__radius}, x={self.__x}, y={self.__y}, char={repr(self.char)})" + +import math # import the math module to use the sqrt function +# Simplified Triangle class using Heron's formula +class Triangle(Shape): + def __init__(self, a, b, c, x=0, y=0, char = '*'): + # Validate if the sides can form a triangle + if not (a + b > c and a + c > b and b + c > a): + raise ValueError("The provided sides do not form a valid triangle.") + + self.__a = a + self.__b = b + self.__c = c + self.__x = x + self.__y = y + self.char = char + + # Implementing abstract methods from Shape + def compute_area(self): + """Compute the area using Heron's formula.""" + s = self.compute_perimeter() / 2 # Semi-perimeter + area = math.sqrt(s * (s - self.__a) * (s - self.__b) * (s - self.__c)) + return round(area, 2) + + def compute_perimeter(self): + """Compute the perimeter as the sum of all sides.""" + return round(self.__a + self.__b + self.__c, 2) + + def get_center(self): + """Return the centroid of the triangle (average of corners).""" + corners = self.get_corners() + centroid_x = sum([p[0] for p in corners.values()]) / 3 + centroid_y = sum([p[1] for p in corners.values()]) / 3 + return (round(centroid_x, 2), round(centroid_y, 2)) + + def get_corners(self): + """Assume (x, y) is the bottom-left corner and calculate other corners.""" + # Simple placement of the triangle along the base a + return { + "bottom_left": (self.__x, self.__y), + "bottom_right": (self.__x + self.__a, self.__y), + "top_vertex": (self.__x + self.__a / 2, self.__y + self.get_approximate_height()) + } + def get_approximate_height(self): + """Compute the approximate height for visualization purposes.""" + # Area = 1/2 * base * height => height = (2 * area) / base + area = self.compute_area() + return round((2 * area) / self.__a, 2) + ## This is the new method added to the Triangle class to return the perimeter points of the triangle. + def get_perimeter_points(self, num_points=16): + """Generate up to 16 points along the perimeter of the triangle.""" + corners = self.get_corners() + points = list(corners.values()) + + # Generate intermediate points along the edges + while len(points) < num_points: + for i in range(len(points) - 1): + mid_point = ( + (points[i][0] + points[i + 1][0]) / 2, + (points[i][1] + points[i + 1][1]) / 2 + ) + points.insert(i + 1, mid_point) + if len(points) >= num_points: + break + return points[:num_points] + ## This is the new method added to the Triangle class to determine if a given point is inside the triangle. + def contains_point(self, x, y): + """Check if the point (x, y) is inside the triangle using area comparison.""" + def area(x1, y1, x2, y2, x3, y3): + return abs((x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2.0) + + corners = self.get_corners() + x1, y1 = corners["bottom_left"] + x2, y2 = corners["bottom_right"] + x3, y3 = corners["top_vertex"] + + a = area(x1, y1, x2, y2, x3, y3) + a1 = area(x, y, x2, y2, x3, y3) + a2 = area(x1, y1, x, y, x3, y3) + a3 = area(x1, y1, x2, y2, x, y) + + return a == a1 + a2 + a3 + ## This is the new method added to the Triangle class to determine if the triangle overlaps with another shape. + def overlaps_with(self, other: Shape) -> bool: + """Determine if this triangle overlaps with another shape.""" + if isinstance(other, Triangle): + # Check if any perimeter point of either triangle is inside the other + for point in self.get_perimeter_points(): + if other.contains_point(*point): + return True + for point in other.get_perimeter_points(): + if self.contains_point(*point): + return True + else: + # Mixed shape overlap detection + for point in self.get_perimeter_points(): + if other.contains_point(*point): + return True + for point in other.get_perimeter_points(): + if self.contains_point(*point): + return True + return False + def paint(self, canvas: Canvas): + corners = self.get_corners() + bl = corners["bottom_left"] + br = corners["bottom_right"] + tv = corners["top_vertex"] + + # Use Bresenham's line algorithm for all triangle sides + canvas.line(int(bl[0]), int(bl[1]), int(br[0]), int(br[1]), char=self.char) + canvas.line(int(bl[0]), int(bl[1]), int(tv[0]), int(tv[1]), char=self.char) + canvas.line(int(br[0]), int(br[1]), int(tv[0]), int(tv[1]), char=self.char) + def __repr__(self): + return f"Triangle({self.__a}, {self.__b}, {self.__c}, x={self.__x}, y={self.__y}, char={repr(self.char)})" + +class CompoundShape(Shape): + def __init__(self, *shapes, char='%'): + self.shapes = shapes + self.char = char + + def paint(self, canvas: Canvas): + for shape in self.shapes: + shape.paint(canvas) + + def compute_area(self): + """Compute the total area of all contained shapes.""" + return sum(shape.compute_area() for shape in self.shapes) + + def compute_perimeter(self): + """Compute the total perimeter of all contained shapes.""" + return sum(shape.compute_perimeter() for shape in self.shapes) + + def get_center(self): + """Compute the geometric center of all contained shapes.""" + if not self.shapes: + return (0, 0) + centers = [shape.get_center() for shape in self.shapes] + avg_x = sum(x for x, _ in centers) / len(centers) + avg_y = sum(y for _, y in centers) / len(centers) + return (avg_x, avg_y) + + def get_corners(self): + """Returns all corners of the contained shapes.""" + corners = [] + for shape in self.shapes: + shape_corners = shape.get_corners() + if shape_corners: + corners.extend(shape_corners.values()) + return corners + + def get_perimeter_points(self, num_points=16): + """Aggregate perimeter points from all shapes.""" + points = [] + for shape in self.shapes: + points.extend(shape.get_perimeter_points(num_points // len(self.shapes))) + return points[:num_points] + + def contains_point(self, x, y): + """Check if any shape in the compound contains the point.""" + return any(shape.contains_point(x, y) for shape in self.shapes) + + def overlaps_with(self, other: Shape) -> bool: + """Check if any shape in the compound overlaps with the other shape.""" + return any(shape.overlaps_with(other) for shape in self.shapes) + + def __repr__(self): + """Return a string that reconstructs the compound shape with all sub-shapes.""" + shapes_repr = ', '.join(repr(shape) for shape in self.shapes) + return f"CompoundShape({shapes_repr}, char={repr(self.char)})" + +class RasterDrawing: + def __init__(self, width, height, bg_char=' '): + self.canvas = Canvas(width, height) + self.shapes = [] + self.bg_char = bg_char + + def add_shape(self, shape: Shape): + self.shapes.append(shape) + + def remove_shape(self, shape: Shape): + if shape in self.shapes: + self.shapes.remove(shape) + + def clear_shapes(self): + self.shapes = [] + + def paint(self): + """Paint all shapes onto the canvas and display the drawing.""" + self.canvas.clear_canvas() + for shape in self.shapes: + shape.paint(self.canvas) + self.canvas.display() + + def save(self, filename: str): + """Save the drawing to a file as executable Python code.""" + with open(filename, 'w') as f: + f.write(repr(self)) + + def __repr__(self): + """Return a string to recreate the RasterDrawing and all its shapes.""" + shapes_repr = ', '.join(repr(shape) for shape in self.shapes) + return f"RasterDrawing({self.canvas.width}, {self.canvas.height}, bg_char={repr(self.bg_char)}).add_shapes([{shapes_repr}])" + + def add_shapes(self, shapes: list): + """Add multiple shapes to the drawing.""" + self.shapes.extend(shapes) + return self # Return self to support method chaining in __repr__ \ No newline at end of file diff --git a/Labs/Lab.4/drawing.raster b/Labs/Lab.4/drawing.raster new file mode 100644 index 000000000..77c3c55fc --- /dev/null +++ b/Labs/Lab.4/drawing.raster @@ -0,0 +1 @@ +RasterDrawing(40, 20, bg_char=' ').add_shapes([Rectangle(10, 5, x=5, y=2, char='#'), Circle(5, x=20, y=10, char='@'), Triangle(7, 6, 8, x=10, y=15, char='*')]) \ No newline at end of file From b5dca18187d14b7bd0748ad2497eb290347188b2 Mon Sep 17 00:00:00 2001 From: mathlete1618 Date: Fri, 28 Feb 2025 22:49:48 -0600 Subject: [PATCH 4/5] Added Lab.5 solution and output file --- Labs/Lab.5/Lab.5_Output.txt | 157 ++++++++++++++++++++++++++++++++ Labs/Lab.5/Lab.5_Solution.ipynb | 126 +++++++++++++++++++++++++ 2 files changed, 283 insertions(+) create mode 100644 Labs/Lab.5/Lab.5_Output.txt create mode 100644 Labs/Lab.5/Lab.5_Solution.ipynb diff --git a/Labs/Lab.5/Lab.5_Output.txt b/Labs/Lab.5/Lab.5_Output.txt new file mode 100644 index 000000000..6385e97e5 --- /dev/null +++ b/Labs/Lab.5/Lab.5_Output.txt @@ -0,0 +1,157 @@ +Notebook output is being captured... Run all cells to save outputs. + +0 0 0 +0 0 0 +0 0 0 +1 2 3 +4 5 6 +7 8 9 +Error: All rows must have the same length. +1 2 3 +4 5 6 +7 8 9 +(3, 3) +1 4 7 +2 5 8 +3 6 9 +4 5 6 +2 +5 +8 +[[1, 2, 3], [4, 5, 6], [7, 8, 9]] +1 2 +4 5 +Error: Invalid block indices. +3 +5.0 5.0 5.0 +5.0 5.0 5.0 +5.0 5.0 5.0 +0.0 0.0 0.0 +0.0 0.0 0.0 +0.0 0.0 0.0 +1.0 1.0 1.0 +1.0 1.0 1.0 +1.0 1.0 1.0 +1.0 0.0 0.0 +0.0 1.0 0.0 +0.0 0.0 1.0 + +Matrix M: +1 2 +3 4 + +Matrix N: +5 6 +7 8 + +M * 3: +3 6 +9 12 + +M + N: +6 8 +10 12 + +M - N: +-4 -4 +-4 -4 + +Matrix P: +1 2 3 +4 5 6 + +Matrix Q: +7 8 +9 10 +11 12 + +P * Q (Matrix Multiplication): +58 64 +139 154 + +M ⊙ N (Element-wise Multiplication): +5 12 +21 32 + +M == N: False +M == M: True + +Matrix M: +1 2 +3 4 + +Matrix N: +5 6 +7 8 + +M * 2: +2 4 +6 8 + +2 * M: +2 4 +6 8 + +M + N: +6 8 +10 12 + +M - N: +-4 -4 +-4 -4 + +Matrix P: +1 2 3 +4 5 6 + +Matrix Q: +7 8 +9 10 +11 12 + +P * Q (Matrix Multiplication): +58 64 +139 154 + +P @ Q (Using @ Operator): +58 64 +139 154 + +M == N: False +M == M: True + +Associativity of Matrix Multiplication: (AB)C == A(BC) +LHS (AB)C: + 12 10 +24 22 +RHS A(BC): + 12 10 +24 22 +Equality Check: True + +Distributive Property: A(B + C) == AB + AC +LHS A(B + C): + 8 9 +18 19 +RHS AB + AC: + 8 9 +18 19 +Equality Check: True + +Non-Commutativity: AB ≠ BA +AB: + 4 6 +10 12 +BA: + 2 4 +10 14 +Equality Check: False + +Identity Matrix Property: AI = A +AI: + 1.0 2.0 +3.0 4.0 +A: + 1 2 +3 4 +Equality Check: True diff --git a/Labs/Lab.5/Lab.5_Solution.ipynb b/Labs/Lab.5/Lab.5_Solution.ipynb new file mode 100644 index 000000000..b8f082246 --- /dev/null +++ b/Labs/Lab.5/Lab.5_Solution.ipynb @@ -0,0 +1,126 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Lab 5\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Matrix Representation: In this lab you will be creating a simple linear algebra system. In memory, we will represent matrices as nested python lists as we have done in lecture. In the exercises below, you are required to explicitly test every feature you implement, demonstrating it works.\n", + "\n", + "1. Create a `matrix` class with the following properties:\n", + " * It can be initialized in 2 ways:\n", + " 1. with arguments `n` and `m`, the size of the matrix. A newly instanciated matrix will contain all zeros.\n", + " 2. with a list of lists of values. Note that since we are using lists of lists to implement matrices, it is possible that not all rows have the same number of columns. Test explicitly that the matrix is properly specified.\n", + " * Matrix instances `M` can be indexed with `M[i][j]` and `M[i,j]`.\n", + " * Matrix assignment works in 2 ways:\n", + " 1. If `M_1` and `M_2` are `matrix` instances `M_1=M_2` sets the values of `M_1` to those of `M_2`, if they are the same size. Error otherwise.\n", + " 2. In example above `M_2` can be a list of lists of correct size.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "2. Add the following methods:\n", + " * `shape()`: returns a tuple `(n,m)` of the shape of the matrix.\n", + " * `transpose()`: returns a new matrix instance which is the transpose of the matrix.\n", + " * `row(n)` and `column(n)`: that return the nth row or column of the matrix M as a new appropriately shaped matrix object.\n", + " * `to_list()`: which returns the matrix as a list of lists.\n", + " * `block(n_0,n_1,m_0,m_1)` that returns a smaller matrix located at the n_0 to n_1 columns and m_0 to m_1 rows. \n", + " * (Extra credit) Modify `__getitem__` implemented above to support slicing.\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "3. Write functions that create special matrices (note these are standalone functions, not member functions of your `matrix` class):\n", + " * `constant(n,m,c)`: returns a `n` by `m` matrix filled with floats of value `c`.\n", + " * `zeros(n,m)` and `ones(n,m)`: return `n` by `m` matrices filled with floats of value `0` and `1`, respectively.\n", + " * `eye(n)`: returns the n by n identity matrix." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "4. Add the following member functions to your class. Make sure to appropriately test the dimensions of the matrices to make sure the operations are correct.\n", + " * `M.scalarmul(c)`: a matrix that is scalar product $cM$, where every element of $M$ is multiplied by $c$.\n", + " * `M.add(N)`: adds two matrices $M$ and $N$. Don’t forget to test that the sizes of the matrices are compatible for this and all other operations.\n", + " * `M.sub(N)`: subtracts two matrices $M$ and $N$.\n", + " * `M.mat_mult(N)`: returns a matrix that is the matrix product of two matrices $M$ and $N$.\n", + " * `M.element_mult(N)`: returns a matrix that is the element-wise product of two matrices $M$ and $N$.\n", + " * `M.equals(N)`: returns true/false if $M==N$." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "5. Overload python operators to appropriately use your functions in 4 and allow expressions like:\n", + " * 2*M\n", + " * M*2\n", + " * M+N\n", + " * M-N\n", + " * M*N\n", + " * M==N\n", + " * M=N\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "6. Demonstrate the basic properties of matrices with your matrix class by creating two 2 by 2 example matrices using your Matrix class and illustrating the following:\n", + "\n", + "$$\n", + "(AB)C=A(BC)\n", + "$$\n", + "$$\n", + "A(B+C)=AB+AC\n", + "$$\n", + "$$\n", + "AB\\neq BA\n", + "$$\n", + "$$\n", + "AI=A\n", + "$$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.9.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 83148950e6c8319ab8b70e31d5d1867ce2f01091 Mon Sep 17 00:00:00 2001 From: mathlete1618 Date: Thu, 24 Apr 2025 22:59:26 -0500 Subject: [PATCH 5/5] Add solution for Lab 5 --- Labs/Lab.5/Lab.5_Solution.ipynb | 799 +++++++++++++++++++++++++++++++- 1 file changed, 795 insertions(+), 4 deletions(-) diff --git a/Labs/Lab.5/Lab.5_Solution.ipynb b/Labs/Lab.5/Lab.5_Solution.ipynb index b8f082246..0b37c3c26 100644 --- a/Labs/Lab.5/Lab.5_Solution.ipynb +++ b/Labs/Lab.5/Lab.5_Solution.ipynb @@ -94,17 +94,808 @@ "$$" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Exercise 1: For part A and B." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "test_data = [[1,2,3], [4,5,6], [7,8,9]]\n", + "test_data2 = [[1,2], [1,2,3], [1,2,3,4]]" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "class Matrix:\n", + " def __str__(self): \n", + " # String representation for printing the matrix\n", + " return '\\n'.join(['\\t'.join(map(str, row)) for row in self.data])\n", + " \n", + "class Matrix_A(Matrix): # Where n is the number of rows and m is the number of columns\n", + " def __init__(self, n, m):\n", + " self.n = n\n", + " self.m = m\n", + " self.data = [[0 for i in range(m)] for j in range(n)] # List comprehension to create a matrix with zeros for entries\n", + "class Matrix_B(Matrix):\n", + " def __init__(self, data):\n", + " # Check if all rows have the same length\n", + " if not all(len(row) == len(data[0]) for row in data): \n", + " raise ValueError(\"All rows must have the same length.\")\n", + " self.data = data\n", + " self.rows = len(data)\n", + " self.columns = len(data[0])\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "arr_1 = Matrix_A(3, 3)\n", + "print(arr_1)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "arr_2 = Matrix_B(test_data)\n", + "print(arr_2)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " arr_3 = Matrix_B(test_data2)\n", + " print(arr_3)\n", + "except ValueError as e:\n", + " print(f\"Error: {e}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "arr_1 = arr_2\n", + "print(arr_1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Exercise 2" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "class Matrix:\n", + " def __init__(self, data):\n", + " if not all(len(row) == len(data[0]) for row in data):\n", + " raise ValueError(\"All rows must have the same length.\")\n", + " self.data = data\n", + " self.rows = len(data)\n", + " self.columns = len(data[0])\n", + " def __str__(self): \n", + " # String representation for printing the matrix\n", + " return '\\n'.join(['\\t'.join(map(str, row)) for row in self.data])\n", + " def shape(self):\n", + " return (self.rows, self.columns)\n", + "\n", + " def transpose(self):\n", + " # Returns a new Matrix instance which is the transpose of the original matrix\n", + " transposed_data = [[self.data[j][i] for j in range(self.rows)] for i in range(self.columns)]\n", + " return Matrix(transposed_data)\n", + "\n", + " def row(self, n):\n", + " # Returns the nth row as a new Matrix instance.\n", + " if n < 0 or n >= self.rows:\n", + " raise IndexError(\"Row index out of range.\")\n", + " return Matrix([self.data[n]]) # Return as a new 1 row matrix\n", + "\n", + " def column(self, n):\n", + " # Returns the nth column as a new Matrix instance.\n", + " if n < 0 or n >= self.columns:\n", + " raise IndexError(\"Column index out of range.\")\n", + " return Matrix([[self.data[i][n]] for i in range(self.rows)]) # Return as a new 1 column matrix\n", + "\n", + " def to_list(self):\n", + " # Returns a list of lists (copy of the data)\n", + " return [row[:] for row in self.data]\n", + "\n", + " def block(self, n_0, n_1, m_0, m_1):\n", + " # Returns a new Matrix instance which is a copy of the specified block of the matrix\n", + " if not (0 <= n_0 < n_1 <= self.rows) or not (0 <= m_0 < m_1 <= self.columns):\n", + " raise IndexError(\"Invalid block indices.\")\n", + " \n", + " submatrix = [row[m_0:m_1] for row in self.data[n_0:n_1]]\n", + " return Matrix(submatrix)\n", + "\n", + " def __getitem__(self, key):\n", + " # Overloading the [] operator for accessing matrix elements\n", + " if isinstance(key, tuple):\n", + " row_idx, col_idx = key\n", + " if isinstance(row_idx, slice) and isinstance(col_idx, slice):\n", + " # Slice both rows and columns\n", + " sliced_data = [row[col_idx] for row in self.data[row_idx]]\n", + " return Matrix(sliced_data)\n", + " elif isinstance(row_idx, int) and isinstance(col_idx, slice):\n", + " # Single row, multiple columns (returns as 1 row matrix)\n", + " return Matrix([self.data[row_idx][col_idx]])\n", + " elif isinstance(row_idx, slice) and isinstance(col_idx, int):\n", + " # Multiple rows, single column (returns as 1 column matrix)\n", + " return Matrix([[row[col_idx]] for row in self.data[row_idx]])\n", + " elif isinstance(row_idx, int) and isinstance(col_idx, int):\n", + " # Single element access\n", + " return self.data[row_idx][col_idx]\n", + " else:\n", + " raise TypeError(\"Invalid index type for matrix slicing.\")\n", + " elif isinstance(key, int):\n", + " # Access full row\n", + " return Matrix([self.data[key]]) # Return as a 1 row matrix\n", + " else:\n", + " raise TypeError(\"Invalid index type.\")\n", + " \n", + "class Matrix_A(Matrix): # Where n is the number of rows and m is the number of columns\n", + " def __init__(self, n, m):\n", + " self.rows = n\n", + " self.columns = m\n", + " self.data = [[0 for _ in range(m)] for _ in range(n)] # List comprehension to create matrix with zeros\n", + "class Matrix_B(Matrix):\n", + " def __init__(self, data):\n", + " # Check if all rows have the same length\n", + " if not all(len(row) == len(data[0]) for row in data):\n", + " raise ValueError(\"All rows must have the same length.\")\n", + " self.data = data\n", + " self.rows = len(data)\n", + " self.columns = len(data[0])\n" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "matrix_1 = Matrix_B(test_data)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "print(matrix_1.shape())" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "print(matrix_1.transpose())\n" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "print(matrix_1.row(1))\n", + "print(matrix_1.column(1)) " + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [], + "source": [ + "print(matrix_1.to_list())" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ + "print(matrix_1.block(0, 2, 0, 2))" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [], + "source": [ + "try: # Error\n", + " print(matrix_1.block(2, 0, 0, 2))\n", + "except IndexError as e:\n", + " print(f\"Error: {e}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [], + "source": [ + "print(matrix_1.__getitem__((0, 2)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Exercise 3:" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [], + "source": [ + "def constant(n, m, c):\n", + " # Returns an n x m matrix filled with the constant c.\n", + " return Matrix_B([[float(c) for _ in range(m)] for _ in range(n)])\n", + "\n", + "def zeros(n, m):\n", + "# Returns an n x m matrix filled with floats of value 0.\n", + " return Matrix_B([[0.0 for _ in range(m)] for _ in range(n)])\n", + "\n", + "def ones(n, m):\n", + "# Returns an n x m matrix filled with floats of value 1.\n", + " return Matrix_B([[1.0 for _ in range(m)] for _ in range(n)])\n", + "\n", + "def eye(n):\n", + "# Returns an n x n identity matrix.\n", + " return Matrix_B([[1.0 if i == j else 0.0 for j in range(n)] for i in range(n)])" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [], + "source": [ + "con_arr_1 = constant(3, 3, 5)\n", + "print(con_arr_1)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [], + "source": [ + "con_arr_2 = zeros(3, 3)\n", + "print(con_arr_2)" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [], + "source": [ + "con_arr_3 = ones(3, 3)\n", + "print(con_arr_3)" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [], + "source": [ + "con_arr_4 = eye(3)\n", + "print(con_arr_4)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Exercise 4:" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [], + "source": [ + "class Matrix:\n", + " def __init__(self, data):\n", + " if not all(len(row) == len(data[0]) for row in data):\n", + " raise ValueError(\"All rows must have the same length.\")\n", + " self.data = data\n", + " self.rows = len(data)\n", + " self.columns = len(data[0])\n", + " def __str__(self): \n", + " # String representation for printing the matrix\n", + " return '\\n'.join(['\\t'.join(map(str, row)) for row in self.data])\n", + " def shape(self):\n", + " return (self.rows, self.columns)\n", + "\n", + " def transpose(self):\n", + " # Returns a new Matrix instance which is the transpose of the original matrix\n", + " transposed_data = [[self.data[j][i] for j in range(self.rows)] for i in range(self.columns)]\n", + " return Matrix(transposed_data)\n", + "\n", + " def row(self, n):\n", + " # Returns the nth row as a new Matrix instance.\n", + " if n < 0 or n >= self.rows:\n", + " raise IndexError(\"Row index out of range.\")\n", + " return Matrix([self.data[n]]) # Return as a new 1 row matrix\n", + "\n", + " def column(self, n):\n", + " # Returns the nth column as a new Matrix instance.\n", + " if n < 0 or n >= self.columns:\n", + " raise IndexError(\"Column index out of range.\")\n", + " return Matrix([[self.data[i][n]] for i in range(self.rows)]) # Return as a new 1 column matrix\n", + "\n", + " def to_list(self):\n", + " # Returns a list of lists (copy of the data)\n", + " return [row[:] for row in self.data]\n", + "\n", + " def block(self, n_0, n_1, m_0, m_1):\n", + " # Returns a new Matrix instance which is a copy of the specified block of the matrix\n", + " if not (0 <= n_0 < n_1 <= self.rows) or not (0 <= m_0 < m_1 <= self.columns):\n", + " raise IndexError(\"Invalid block indices.\")\n", + " \n", + " submatrix = [row[m_0:m_1] for row in self.data[n_0:n_1]]\n", + " return Matrix(submatrix)\n", + "\n", + " def __getitem__(self, key):\n", + " # Overloading the [] operator for accessing matrix elements\n", + " if isinstance(key, tuple):\n", + " row_idx, col_idx = key\n", + " if isinstance(row_idx, slice) and isinstance(col_idx, slice):\n", + " # Slice both rows and columns\n", + " sliced_data = [row[col_idx] for row in self.data[row_idx]]\n", + " return Matrix(sliced_data)\n", + " elif isinstance(row_idx, int) and isinstance(col_idx, slice):\n", + " # Single row, multiple columns (returns as 1 row matrix)\n", + " return Matrix([self.data[row_idx][col_idx]])\n", + " elif isinstance(row_idx, slice) and isinstance(col_idx, int):\n", + " # Multiple rows, single column (returns as 1 column matrix)\n", + " return Matrix([[row[col_idx]] for row in self.data[row_idx]])\n", + " elif isinstance(row_idx, int) and isinstance(col_idx, int):\n", + " # Single element access\n", + " return self.data[row_idx][col_idx]\n", + " else:\n", + " raise TypeError(\"Invalid index type for matrix slicing.\")\n", + " elif isinstance(key, int):\n", + " # Access full row\n", + " return Matrix([self.data[key]]) # Return as a 1 row matrix\n", + " else:\n", + " raise TypeError(\"Invalid index type.\")\n", + "\n", + " def scalarmul(self, c):\n", + " # Returns a new matrix that is the scalar product c*M\n", + " return Matrix([[c * self.data[i][j] for j in range(self.columns)] for i in range(self.rows)])\n", + "\n", + " def add(self, N):\n", + " # Returns a new matrix that is the sum of M + N. Checks dimensions for compatibility.\n", + " if self.shape() != N.shape():\n", + " raise ValueError(\"Matrices must have the same dimensions for addition.\")\n", + " return Matrix([[self.data[i][j] + N.data[i][j] for j in range(self.columns)] for i in range(self.rows)])\n", + "\n", + " def sub(self, N):\n", + " # Returns a new matrix that is the difference of M - N. Checks dimensions for compatibility.\n", + " if self.shape() != N.shape():\n", + " raise ValueError(\"Matrices must have the same dimensions for subtraction.\")\n", + " return Matrix([[self.data[i][j] - N.data[i][j] for j in range(self.columns)] for i in range(self.rows)])\n", + "\n", + " def mat_mult(self, N):\n", + " # Returns a new matrix that is the matrix product of M * N. Checks dimensions for compatibility.\n", + " if self.columns != N.rows:\n", + " raise ValueError(\"Number of columns of M must equal the number of rows of N for matrix multiplication.\")\n", + " result = [[sum(self.data[i][k] * N.data[k][j] for k in range(self.columns)) for j in range(N.columns)] for i in range(self.rows)]\n", + " return Matrix(result)\n", + "\n", + " def element_mult(self, N):\n", + " # Returns a new matrix that is the element-wise product of M * N. Checks dimensions for compatibility.\n", + " if self.shape() != N.shape():\n", + " raise ValueError(\"Matrices must have the same dimensions for element-wise multiplication.\")\n", + " return Matrix([[self.data[i][j] * N.data[i][j] for j in range(self.columns)] for i in range(self.rows)])\n", + "\n", + " def equals(self, N):\n", + " # Returns True if M and N are equal, False otherwise.\n", + " return self.shape() == N.shape() and all(self.data[i][j] == N.data[i][j] for i in range(self.rows) for j in range(self.columns))\n", + " \n", + "class Matrix_A(Matrix): # Where n is the number of rows and m is the number of columns\n", + " def __init__(self, n, m):\n", + " self.rows = n\n", + " self.columns = m\n", + " self.data = [[0 for _ in range(m)] for _ in range(n)] # List comprehension to create matrix with zeros\n", + "class Matrix_B(Matrix):\n", + " def __init__(self, data):\n", + " # Check if all rows have the same length\n", + " if not all(len(row) == len(data[0]) for row in data):\n", + " raise ValueError(\"All rows must have the same length.\")\n", + " self.data = data\n", + " self.rows = len(data)\n", + " self.columns = len(data[0])\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Testing code for exercise 4." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [], + "source": [ + "# Define two matrices\n", + "M = Matrix([[1, 2], [3, 4]])\n", + "N = Matrix([[5, 6], [7, 8]])\n", + "\n", + "print(\"\\nMatrix M:\")\n", + "print(M)\n", + "\n", + "print(\"\\nMatrix N:\")\n", + "print(N)\n", + "\n", + "# Scalar Multiplication\n", + "print(\"\\nM * 3:\")\n", + "print(M.scalarmul(3))\n", + "\n", + "# Matrix Addition\n", + "print(\"\\nM + N:\")\n", + "print(M.add(N))\n", + "\n", + "# Matrix Subtraction\n", + "print(\"\\nM - N:\")\n", + "print(M.sub(N))\n", + "\n", + "# Matrix Multiplication\n", + "P = Matrix([[1, 2, 3], [4, 5, 6]])\n", + "Q = Matrix([[7, 8], [9, 10], [11, 12]])\n", + "\n", + "print(\"\\nMatrix P:\")\n", + "print(P)\n", + "\n", + "print(\"\\nMatrix Q:\")\n", + "print(Q)\n", + "\n", + "print(\"\\nP * Q (Matrix Multiplication):\")\n", + "print(P.mat_mult(Q))\n", + "\n", + "# Element-wise Multiplication\n", + "print(\"\\nM ⊙ N (Element-wise Multiplication):\")\n", + "print(M.element_mult(N))\n", + "\n", + "# Matrix Equality Check\n", + "print(\"\\nM == N:\", M.equals(N))\n", + "print(\"M == M:\", M.equals(M))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Exercise 5: Copy and paste from exercise 4 and implement the overloading after the getitem method." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [], + "source": [ + "class Matrix:\n", + " def __init__(self, data):\n", + " if not all(len(row) == len(data[0]) for row in data):\n", + " raise ValueError(\"All rows must have the same length.\")\n", + " self.data = data\n", + " self.rows = len(data)\n", + " self.columns = len(data[0])\n", + " def __str__(self): \n", + " # String representation for printing the matrix\n", + " return '\\n'.join(['\\t'.join(map(str, row)) for row in self.data])\n", + " def shape(self):\n", + " return (self.rows, self.columns)\n", + "\n", + " def transpose(self):\n", + " # Returns a new Matrix instance which is the transpose of the original matrix\n", + " transposed_data = [[self.data[j][i] for j in range(self.rows)] for i in range(self.columns)]\n", + " return Matrix(transposed_data)\n", + "\n", + " def row(self, n):\n", + " # Returns the nth row as a new Matrix instance.\n", + " if n < 0 or n >= self.rows:\n", + " raise IndexError(\"Row index out of range.\")\n", + " return Matrix([self.data[n]]) # Return as a new 1 row matrix\n", + "\n", + " def column(self, n):\n", + " # Returns the nth column as a new Matrix instance.\n", + " if n < 0 or n >= self.columns:\n", + " raise IndexError(\"Column index out of range.\")\n", + " return Matrix([[self.data[i][n]] for i in range(self.rows)]) # Return as a new 1 column matrix\n", + "\n", + " def to_list(self):\n", + " # Returns a list of lists (copy of the data)\n", + " return [row[:] for row in self.data]\n", + "\n", + " def block(self, n_0, n_1, m_0, m_1):\n", + " # Returns a new Matrix instance which is a copy of the specified block of the matrix\n", + " if not (0 <= n_0 < n_1 <= self.rows) or not (0 <= m_0 < m_1 <= self.columns):\n", + " raise IndexError(\"Invalid block indices.\")\n", + " \n", + " submatrix = [row[m_0:m_1] for row in self.data[n_0:n_1]]\n", + " return Matrix(submatrix)\n", + "\n", + " def __getitem__(self, key):\n", + " # Overloading the [] operator for accessing matrix elements\n", + " if isinstance(key, tuple):\n", + " row_idx, col_idx = key\n", + " if isinstance(row_idx, slice) and isinstance(col_idx, slice):\n", + " # Slice both rows and columns\n", + " sliced_data = [row[col_idx] for row in self.data[row_idx]]\n", + " return Matrix(sliced_data)\n", + " elif isinstance(row_idx, int) and isinstance(col_idx, slice):\n", + " # Single row, multiple columns (returns as 1 row matrix)\n", + " return Matrix([self.data[row_idx][col_idx]])\n", + " elif isinstance(row_idx, slice) and isinstance(col_idx, int):\n", + " # Multiple rows, single column (returns as 1 column matrix)\n", + " return Matrix([[row[col_idx]] for row in self.data[row_idx]])\n", + " elif isinstance(row_idx, int) and isinstance(col_idx, int):\n", + " # Single element access\n", + " return self.data[row_idx][col_idx]\n", + " else:\n", + " raise TypeError(\"Invalid index type for matrix slicing.\")\n", + " elif isinstance(key, int):\n", + " # Access full row\n", + " return Matrix([self.data[key]]) # Return as a 1 row matrix\n", + " else:\n", + " raise TypeError(\"Invalid index type.\")\n", + "\n", + " # OVERLOADING OPERATORS\n", + " \n", + " def __mul__(self, other):\n", + " # Handles matrix multiplication (M * N)\n", + " if isinstance(other, (int, float)): # Scalar multiplication\n", + " return Matrix([[other * self.data[i][j] for j in range(self.columns)] for i in range(self.rows)])\n", + " elif isinstance(other, Matrix): # Matrix multiplication\n", + " return self.mat_mult(other)\n", + " else:\n", + " raise TypeError(\"Unsupported multiplication. Must be scalar or another Matrix.\")\n", + "\n", + " def __rmul__(self, other):\n", + " # Handles scalar multiplication (scalar * M)\n", + " return self.__mul__(other) # Scalar multiplication is commutative\n", + "\n", + " def __add__(self, other):\n", + " # Handles matrix addition (M + N)\n", + " if not isinstance(other, Matrix):\n", + " raise TypeError(\"Addition is only supported between two matrices.\")\n", + " return self.add(other)\n", + "\n", + " def __sub__(self, other):\n", + " # Handles matrix subtraction (M - N)\n", + " if not isinstance(other, Matrix):\n", + " raise TypeError(\"Subtraction is only supported between two matrices.\")\n", + " return self.sub(other)\n", + "\n", + " def __matmul__(self, other):\n", + " # Matrix multiplication (M @ N)\n", + " if not isinstance(other, Matrix):\n", + " raise TypeError(\"Matrix multiplication requires another Matrix.\")\n", + " return self.mat_mult(other)\n", + "\n", + " def __eq__(self, other):\n", + " # Check if two matrices are equal\n", + " if not isinstance(other, Matrix):\n", + " return False\n", + " return self.equals(other)\n", + "\n", + " def scalarmul(self, c):\n", + " # Returns a new matrix that is the scalar product c*M\n", + " return Matrix([[c * self.data[i][j] for j in range(self.columns)] for i in range(self.rows)])\n", + "\n", + " def add(self, N):\n", + " # Returns a new matrix that is the sum of M + N. Checks dimensions for compatibility.\n", + " if self.shape() != N.shape():\n", + " raise ValueError(\"Matrices must have the same dimensions for addition.\")\n", + " return Matrix([[self.data[i][j] + N.data[i][j] for j in range(self.columns)] for i in range(self.rows)])\n", + "\n", + " def sub(self, N):\n", + " # Returns a new matrix that is the difference of M - N. Checks dimensions for compatibility.\n", + " if self.shape() != N.shape():\n", + " raise ValueError(\"Matrices must have the same dimensions for subtraction.\")\n", + " return Matrix([[self.data[i][j] - N.data[i][j] for j in range(self.columns)] for i in range(self.rows)])\n", + "\n", + " def mat_mult(self, N):\n", + " # Returns a new matrix that is the matrix product of M * N. Checks dimensions for compatibility.\n", + " if self.columns != N.rows:\n", + " raise ValueError(\"Number of columns of M must equal the number of rows of N for matrix multiplication.\")\n", + " result = [[sum(self.data[i][k] * N.data[k][j] for k in range(self.columns)) for j in range(N.columns)] for i in range(self.rows)]\n", + " return Matrix(result)\n", + "\n", + " def element_mult(self, N):\n", + " # Returns a new matrix that is the element-wise product of M * N. Checks dimensions for compatibility.\n", + " if self.shape() != N.shape():\n", + " raise ValueError(\"Matrices must have the same dimensions for element-wise multiplication.\")\n", + " return Matrix([[self.data[i][j] * N.data[i][j] for j in range(self.columns)] for i in range(self.rows)])\n", + "\n", + " def equals(self, N):\n", + " # Returns True if M and N are equal, False otherwise.\n", + " return self.shape() == N.shape() and all(self.data[i][j] == N.data[i][j] for i in range(self.rows) for j in range(self.columns))\n", + " \n", + "class Matrix_A(Matrix): # Where n is the number of rows and m is the number of columns\n", + " def __init__(self, n, m):\n", + " self.rows = n\n", + " self.columns = m\n", + " self.data = [[0 for _ in range(m)] for _ in range(n)] # List comprehension to create matrix with zeros\n", + "class Matrix_B(Matrix):\n", + " def __init__(self, data):\n", + " # Check if all rows have the same length\n", + " if not all(len(row) == len(data[0]) for row in data):\n", + " raise ValueError(\"All rows must have the same length.\")\n", + " self.data = data\n", + " self.rows = len(data)\n", + " self.columns = len(data[0])\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Testing the code from exercise 5." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [], + "source": [ + "# Define two matrices\n", + "M = Matrix([[1, 2], [3, 4]])\n", + "N = Matrix([[5, 6], [7, 8]])\n", + "\n", + "print(\"\\nMatrix M:\")\n", + "print(M)\n", + "\n", + "print(\"\\nMatrix N:\")\n", + "print(N)\n", + "\n", + "# Scalar Multiplication (Both Orders)\n", + "print(\"\\nM * 2:\")\n", + "print(M * 2)\n", + "\n", + "print(\"\\n2 * M:\")\n", + "print(2 * M)\n", + "\n", + "# Matrix Addition\n", + "print(\"\\nM + N:\")\n", + "print(M + N)\n", + "\n", + "# Matrix Subtraction\n", + "print(\"\\nM - N:\")\n", + "print(M - N)\n", + "\n", + "# Matrix Multiplication (Dot Product)\n", + "P = Matrix([[1, 2, 3], [4, 5, 6]])\n", + "Q = Matrix([[7, 8], [9, 10], [11, 12]])\n", + "\n", + "print(\"\\nMatrix P:\")\n", + "print(P)\n", + "\n", + "print(\"\\nMatrix Q:\")\n", + "print(Q)\n", + "\n", + "print(\"\\nP * Q (Matrix Multiplication):\")\n", + "print(P * Q)\n", + "\n", + "# Matrix Multiplication using '@' (Alternative Syntax)\n", + "print(\"\\nP @ Q (Using @ Operator):\")\n", + "print(P @ Q)\n", + "\n", + "# Matrix Equality\n", + "print(\"\\nM == N:\", M == N)\n", + "print(\"M == M:\", M == M)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Exercise 6: Final testing of code on fundamental properties of matricies." + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 34, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "# Define 2x2 matrices\n", + "A = Matrix([[1, 2], [3, 4]])\n", + "B = Matrix([[2, 0], [1, 3]])\n", + "C = Matrix([[0, 1], [2, 1]])\n", + "\n", + "# Define the 2x2 Identity Matrix\n", + "I = eye(2)\n", + "\n", + "# Associativity of Matrix Multiplication: (AB)C == A(BC)\n", + "AB = A * B\n", + "BC = B * C\n", + "assoc_lhs = AB * C # (AB)C\n", + "assoc_rhs = A * BC # A(BC)\n", + "\n", + "print(\"\\nAssociativity of Matrix Multiplication: (AB)C == A(BC)\")\n", + "print(\"LHS (AB)C:\\n\", assoc_lhs)\n", + "print(\"RHS A(BC):\\n\", assoc_rhs)\n", + "print(\"Equality Check:\", assoc_lhs == assoc_rhs)\n", + "\n", + "# Distributive Property: A(B + C) == AB + AC\n", + "B_plus_C = B + C\n", + "AB_plus_AC = A * B + A * C\n", + "distrib_lhs = A * B_plus_C # A(B + C)\n", + "distrib_rhs = AB_plus_AC # AB + AC\n", + "\n", + "print(\"\\nDistributive Property: A(B + C) == AB + AC\")\n", + "print(\"LHS A(B + C):\\n\", distrib_lhs)\n", + "print(\"RHS AB + AC:\\n\", distrib_rhs)\n", + "print(\"Equality Check:\", distrib_lhs == distrib_rhs)\n", + "\n", + "# Non-Commutativity of Matrix Multiplication: AB ≠ BA\n", + "BA = B * A\n", + "print(\"\\nNon-Commutativity: AB ≠ BA\")\n", + "print(\"AB:\\n\", AB)\n", + "print(\"BA:\\n\", BA)\n", + "print(\"Equality Check:\", AB == BA)\n", + "\n", + "# Identity Matrix Property: AI = A\n", + "A_identity = A * I\n", + "\n", + "print(\"\\nIdentity Matrix Property: AI = A\")\n", + "print(\"AI:\\n\", A_identity)\n", + "print(\"A:\\n\", A)\n", + "print(\"Equality Check:\", A_identity == A)" + ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -118,7 +909,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.6" + "version": "3.12.2" } }, "nbformat": 4,