From 918d1727994111206dd53829c22de7a7d85acacb Mon Sep 17 00:00:00 2001 From: Godwinh19 Date: Sun, 11 Apr 2021 11:43:33 +0200 Subject: [PATCH] a star algorithm --- graphs_trees/a_star/__init__.py | 0 graphs_trees/a_star/astar_challenge.ipynb | 319 ++++++++++++++ graphs_trees/a_star/astar_solution.ipynb | 494 ++++++++++++++++++++++ graphs_trees/a_star/test_astar.py | 108 +++++ 4 files changed, 921 insertions(+) create mode 100644 graphs_trees/a_star/__init__.py create mode 100644 graphs_trees/a_star/astar_challenge.ipynb create mode 100644 graphs_trees/a_star/astar_solution.ipynb create mode 100644 graphs_trees/a_star/test_astar.py diff --git a/graphs_trees/a_star/__init__.py b/graphs_trees/a_star/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/graphs_trees/a_star/astar_challenge.ipynb b/graphs_trees/a_star/astar_challenge.ipynb new file mode 100644 index 0000000..3c250b4 --- /dev/null +++ b/graphs_trees/a_star/astar_challenge.ipynb @@ -0,0 +1,319 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook was prepared by [Author](https://github.com/). Source and license info is on [GitHub](https://github.com/donnemartin/interactive-coding-challenges)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Challenge Notebook" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Problem: Implement A* algorithm\n", + "\n", + "* [Constraints](#Constraints)\n", + "* [Test Cases](#Test-Cases)\n", + "* [Algorithm](#Algorithm)\n", + "* [Code](#Code)\n", + "* [Unit Test](#Unit-Test)\n", + "* [Solution Notebook](#Solution-Notebook)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Constraints\n", + "\n", + "* Can we assume we have a Node Class?\n", + " * No\n", + "* Can we assume the inputs are valid?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
boardstartendcostoutput
[][0, 0][3, 5]1None
[[0, 1, 0, 0, 0, 0],\n",
+    "[0, 0, 0, 0, 0, 1],\n",
+    "[0, 1, 0, 1, 0, 0],\n",
+    "[0, 1, 0, 0, 1, 0],\n",
+    "[0, 0, 0, 0, 1, 0]]
[0, 0][3, 5]1
[[0, -1, -1, -1, -1, -1],\n",
+    "[1,  2,  3,  4,  5, -1],\n",
+    "[-1, -1, -1, -1,  6,  7],\n",
+    "[-1, -1, -1, -1, -1,  8],\n",
+    "[-1, -1, -1, -1, -1, -1]]
[[0, 1, 0, 0, 0, 0],\n",
+    "[0, 0, 0, 0, 0, 1],\n",
+    "[0, 1, 0, 1, 0, 0],\n",
+    "[0, 1, 0, 0, 1, 0],\n",
+    "[0, 0, 0, 0, 1, 0]]
[0, 5][4, 0]1
[[-1, -1, -1, -1,  1,  0],\n",
+    "[-1, -1,  4,  3,  2, -1],\n",
+    "[-1, -1,  5, -1, -1, -1],\n",
+    "[-1, -1,  6, -1, -1, -1],\n",
+    "[ 9,  8,  7, -1, -1, -1]]
[[0, 1, 0, 0, 0, 0],\n",
+    "[0, 0, 0, 0, 0, 1],\n",
+    "[0, 1, 0, 1, 0, 0],\n",
+    "[0, 1, 0, 0, 1, 0],\n",
+    "[0, 0, 0, 0, 1, 0]]
[0, 1][3, 5]1None
[[0, 1, 0, 0, 0, 0],\n",
+    "[0, 0, 0, 0, 0, 1],\n",
+    "[0, 1, 0, 1, 0, 0],\n",
+    "[0, 1, 0, 0, 1, 0],\n",
+    "[0, 0, 0, 0, 1, 0]]
[0, 0][2, 3]1None
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "Refer to the [Solution Notebook](). If you are stuck and need a hint, the solution notebook's algorithm discussion might be a good place to start." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from typing import List\n", + "class Astar(object):\n", + " def search(self, board, cost: int, start: List[int], end: List[int]) -> List[List[int]]:\n", + " # TODO: Implement me\n", + " pass " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Unit Test" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**The following unit test is expected to fail until you solve the challenge.**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# %load test_astar.py\n", + "\n", + "from unittest import TestCase\n", + "\n", + "\n", + "class TestAstar(TestCase):\n", + " def test_astar_with_empty_board(self):\n", + " board = []\n", + " start = [0, 1]\n", + " end = [3, 5]\n", + " cost = 1 # cost is 1 per movement\n", + "\n", + " a_start = Astar()\n", + " self.assertEqual(a_start.search(board=board, cost=cost, start=start, end=end), None)\n", + " print('Success: test_astar_with_empty_board')\n", + "\n", + " def test_astar_with_valid_board(self):\n", + " board = [\n", + " [0, 1, 0, 0, 0, 0],\n", + " [0, 0, 0, 0, 0, 1],\n", + " [0, 1, 0, 1, 0, 0],\n", + " [0, 1, 0, 0, 1, 0],\n", + " [0, 0, 0, 0, 1, 0],\n", + " ]\n", + " start = [0, 0]\n", + " end = [3, 5]\n", + " cost = 1 # cost is 1 per movement\n", + " expected = [\n", + " [0, -1, -1, -1, -1, -1],\n", + " [1, 2, 3, 4, 5, -1],\n", + " [-1, -1, -1, -1, 6, 7],\n", + " [-1, -1, -1, -1, -1, 8],\n", + " [-1, -1, -1, -1, -1, -1]\n", + " ]\n", + "\n", + " a_start = Astar()\n", + " self.assertEqual(a_start.search(board=board, cost=cost, start=start, end=end), expected)\n", + " print('Success: test_astar_with_valid_board')\n", + "\n", + " def test_astar_with_another_valid_board(self):\n", + " board = [\n", + " [0, 1, 0, 0, 0, 0],\n", + " [0, 0, 0, 0, 0, 1],\n", + " [0, 1, 0, 1, 0, 0],\n", + " [0, 1, 0, 0, 1, 0],\n", + " [0, 0, 0, 0, 1, 0],\n", + " ]\n", + " start = [0, 5]\n", + " end = [4, 0]\n", + " cost = 1 # cost is 1 per movement\n", + " expected = [\n", + " [-1, -1, -1, -1, 1, 0],\n", + " [-1, -1, 4, 3, 2, -1],\n", + " [-1, -1, 5, -1, -1, -1],\n", + " [-1, -1, 6, -1, -1, -1],\n", + " [9, 8, 7, -1, -1, -1]\n", + " ]\n", + "\n", + " a_start = Astar()\n", + " self.assertEqual(a_start.search(board=board, cost=cost, start=start, end=end), expected)\n", + " print('Success: test_astar_with_another_valid_board')\n", + "\n", + "\n", + " def test_astar_start_from_danger_zone(self):\n", + " board = [\n", + " [0, 1, 0, 0, 0, 0],\n", + " [0, 0, 0, 0, 0, 1],\n", + " [0, 1, 0, 1, 0, 0],\n", + " [0, 1, 0, 0, 1, 0],\n", + " [0, 0, 0, 0, 1, 0],\n", + " ]\n", + " start = [0, 1]\n", + " end = [3, 5]\n", + " cost = 1 # cost is 1 per movement\n", + "\n", + " a_start = Astar()\n", + " self.assertEqual(a_start.search(board=board, cost=cost, start=start, end=end), None)\n", + " print('Success: test_astar_start_from_danger_zone')\n", + "\n", + "\n", + " def test_astar_with_danger_zone_as_end(self):\n", + " board = [\n", + " [0, 1, 0, 0, 0, 0],\n", + " [0, 0, 0, 0, 0, 1],\n", + " [0, 1, 0, 1, 0, 0],\n", + " [0, 1, 0, 0, 1, 0],\n", + " [0, 0, 0, 0, 1, 0],\n", + " ]\n", + " start = [0, 0]\n", + " end = [2, 3]\n", + " cost = 1\n", + " expected = None\n", + "\n", + " a_start = Astar()\n", + " self.assertEqual(a_start.search(board=board, cost=cost, start=start, end=end), expected)\n", + " print('Success: test_astar_with_danger_zone_as_end')\n", + "\n", + "\n", + "def main():\n", + " test = TestAstar()\n", + " test.test_astar_with_empty_board()\n", + " test.test_astar_with_valid_board()\n", + " test.test_astar_with_another_valid_board()\n", + " test.test_astar_start_from_danger_zone()\n", + " test.test_astar_with_danger_zone_as_end()\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " main()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Solution Notebook\n", + "\n", + "Review the [Solution Notebook]() for a discussion on algorithms and code solutions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/graphs_trees/a_star/astar_solution.ipynb b/graphs_trees/a_star/astar_solution.ipynb new file mode 100644 index 0000000..d90e2ce --- /dev/null +++ b/graphs_trees/a_star/astar_solution.ipynb @@ -0,0 +1,494 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook was prepared by [Author](https://github.com/). Source and license info is on [GitHub](https://github.com/donnemartin/interactive-coding-challenges)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Solution Notebook" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Problem: Implement foo(val), which returns val\n", + "\n", + "* [Constraints](#Constraints)\n", + "* [Test Cases](#Test-Cases)\n", + "* [Algorithm](#Algorithm)\n", + "* [Code](#Code)\n", + "* [Unit Test](#Unit-Test)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Constraints\n", + "\n", + "* Can we assume we have a Node Class?\n", + " * No\n", + "* Can we assume the inputs are valid?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
boardstartendcostexpected
[][0, 0][3, 5]1None
[[0, 1, 0, 0, 0, 0],\n",
+    "[0, 0, 0, 0, 0, 1],\n",
+    "[0, 1, 0, 1, 0, 0],\n",
+    "[0, 1, 0, 0, 1, 0],\n",
+    "[0, 0, 0, 0, 1, 0]]
[0, 0][3, 5]1
[[0, -1, -1, -1, -1, -1],\n",
+    "[1,  2,  3,  4,  5, -1],\n",
+    "[-1, -1, -1, -1,  6,  7],\n",
+    "[-1, -1, -1, -1, -1,  8],\n",
+    "[-1, -1, -1, -1, -1, -1]]
[[0, 1, 0, 0, 0, 0],\n",
+    "[0, 0, 0, 0, 0, 1],\n",
+    "[0, 1, 0, 1, 0, 0],\n",
+    "[0, 1, 0, 0, 1, 0],\n",
+    "[0, 0, 0, 0, 1, 0]]
[0, 5][4, 0]1
[[-1, -1, -1, -1,  1,  0],\n",
+    "[-1, -1,  4,  3,  2, -1],\n",
+    "[-1, -1,  5, -1, -1, -1],\n",
+    "[-1, -1,  6, -1, -1, -1],\n",
+    "[ 9,  8,  7, -1, -1, -1]]
[[0, 1, 0, 0, 0, 0],\n",
+    "[0, 0, 0, 0, 0, 1],\n",
+    "[0, 1, 0, 1, 0, 0],\n",
+    "[0, 1, 0, 0, 1, 0],\n",
+    "[0, 0, 0, 0, 1, 0]]
[0, 1][3, 5]1None
[[0, 1, 0, 0, 0, 0],\n",
+    "[0, 0, 0, 0, 0, 1],\n",
+    "[0, 1, 0, 1, 0, 0],\n",
+    "[0, 1, 0, 0, 1, 0],\n",
+    "[0, 0, 0, 0, 1, 0]]
[0, 0][2, 3]1None
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "A* node have some characteristics :\n", + " - parent: the previous node (called parent) of the current node\n", + " - position: the current position of the node in the board\n", + " - g : cost from start to current node\n", + " - h: heuristic (estimated cost) from current node to the goal\n", + " - f : total cost of current node equal to g+h\n", + "To find the path from start to an end:\n", + "* We add the start node to not_visited_nodes list\n", + "* while the not_visited_nodes is not empty:\n", + " * if this node have a lowest f then it become the current_node so we add it to visited_nodes list and remove it from not_visited_nodes list so as we can go further with it children\n", + " * We assume that the max_iteration isn't hit and the node is not the end node\n", + " * if it is the end node, we return the path from start to this node, otherwise\n", + " * we get all children and the current node become the parent of this nodes\n", + " * we will use euclidean distance for the heuristic\n", + " * if the child is already in not_visited_nodes list or have a highest g than others we ignore it and try the next child else we add it to not_visited_nodes list\n", + " * We also ignore the child if its in danger zone\n", + "\n", + " \n", + "Complexity:\n", + "* Time: 2*O(log n)\n", + "* Space: O(b**d) b: average of successors per state, d the depth of the solution" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from typing import List\n", + "\n", + "import numpy as np\n", + "\n", + "\n", + "class Node:\n", + " \"\"\"\n", + " A Node class for A* algorithm\n", + " the previous node (called parent) of the current node \n", + " position: the current position of the node in the board\n", + " g : cost from start to current node\n", + " h: heuristic (estimated cost) from current node to the goal\n", + " f : total cost of current node equal to g+h\n", + " \"\"\"\n", + "\n", + " def __init__(self, parent=None, position=None):\n", + " self.parent = parent\n", + " self.position = position\n", + "\n", + " self.g = 0\n", + " self.h = 0\n", + " self.f = 0\n", + "\n", + " def __eq__(self, other):\n", + " return self.position == other.position\n", + "\n", + "\n", + "class Astar(object):\n", + "\n", + " @staticmethod\n", + " def get_path(current_node: Node, board):\n", + " path = []\n", + " board_rows, board_columns = np.shape(board)\n", + " output = [[-1 for _ in range(board_columns)] for _ in range(board_rows)]\n", + " while current_node is not None:\n", + " path.append(current_node.position)\n", + " current_node = current_node.parent # node3 - node2 - node1 with node1 the parent of node2 and so on\n", + "\n", + " path = path[::-1] # we reverse to get the logic path: start to end\n", + " for i in range(len(path)):\n", + " output[path[i][0]][path[i][1]] = i\n", + " return output\n", + "\n", + " def search(self, board, cost: int, start: List[int], end: List[int]) -> List[List[int]]:\n", + " \"\"\"\n", + " Return given the start node and the end one, the path for the given board\n", + " :param board:\n", + " :param cost:\n", + " :param start:\n", + " :param end:\n", + " :return:\n", + " \"\"\"\n", + " if len(board) == 0:\n", + " return None\n", + "\n", + " if board[start[0]][start[1]] != 0:\n", + " return None\n", + "\n", + " # INITIALISATION\n", + " start_node, end_node = Node(None, tuple(start)), Node(None, tuple(end))\n", + " start_node.g = start_node.h = start_node.f = 0\n", + " end_node.g = end_node.h = end_node.f = 0\n", + "\n", + " visited_nodes, not_visited_nodes = [], []\n", + " not_visited_nodes.append(start_node) # Here we start\n", + "\n", + " # stop criteria to avoid an infinite search/loop\n", + " _iterations = 0\n", + " max_iteration = (len(board) // 2) ** 12\n", + "\n", + " # take the current node and compare all the f cost and selecting the lowest one. check\n", + " # also if max_iteration is hit to stop searching\n", + "\n", + " while any(not_visited_nodes):\n", + " _iterations += 1\n", + "\n", + " current_node = not_visited_nodes[0]\n", + " current_index = 0\n", + "\n", + " for index, node in enumerate(not_visited_nodes):\n", + " if node.f < current_node.f:\n", + " current_node = node\n", + " current_index = index\n", + "\n", + " if _iterations > max_iteration:\n", + " print(\"Stopping searching...Too much iterations\")\n", + " return self.get_path(current_node, board)\n", + "\n", + " # Then we remove this node from not_visited_nodes, and add it to visited_nodes\n", + " not_visited_nodes.pop(current_index)\n", + " visited_nodes.append(current_node)\n", + "\n", + " if current_node == end_node:\n", + " print(f\"Goal reaches in {_iterations} iterations...\")\n", + " return self.get_path(current_node, board)\n", + "\n", + " # Until here the node is not the goal, so we go further with its children\n", + " children = self.get_children(node=current_node, board=board)\n", + "\n", + " # We have children; let take one by one\n", + " # if it's in visited_nodes we ignore it and try the next one\n", + " # if it's in not_visited_nodes we ignore it, else we move it to that nodes list\n", + "\n", + " for child in children:\n", + " if any([visited_child for visited_child in visited_nodes if visited_child == child]):\n", + " continue\n", + "\n", + " # f,g and h of the child\n", + " child.g = current_node.g + cost\n", + " # we use euclidean distance for the heuristic\n", + " child.h = (((child.position[0] - end_node.position[0]) ** 2) + (\n", + " (child.position[1] - end_node.position[1]) ** 2))\n", + " child.f = child.g + child.h\n", + "\n", + " if any([item for item in not_visited_nodes if item == child and child.g > item.g]):\n", + " continue\n", + "\n", + " not_visited_nodes.append(child)\n", + "\n", + " @staticmethod\n", + " def get_children(node: Node, board) -> List[Node]:\n", + " children = []\n", + " moves = [\n", + " [-1, 0], # UP\n", + " [1, 0], # DOWN\n", + " [0, 1], # RIGHT\n", + " [0, -1] # LEFT\n", + " ]\n", + " board_rows, board_columns = np.shape(board)\n", + " for move in moves:\n", + " node_position = (node.position[0] + move[0],\n", + " node.position[1] + move[1])\n", + "\n", + " # check for board constraints\n", + " if (node_position[0] > (board_rows - 1) or\n", + " node_position[0] < 0 or\n", + " node_position[1] > (board_columns - 1) or\n", + " node_position[1] < 0):\n", + " continue\n", + "\n", + " # can't move to an danger zone\n", + " # Here the value of danger zone in the given board is set to 1\n", + " if board[node_position[0]][node_position[1]] != 0:\n", + " continue\n", + " child = Node(node, node_position)\n", + " children.append(child)\n", + " return children" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Unit Test" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting test_astar.py\n" + ] + } + ], + "source": [ + "%%writefile test_astar.py\n", + "\n", + "from unittest import TestCase\n", + "\n", + "\n", + "class TestAstar(TestCase):\n", + " def test_astar_with_empty_board(self):\n", + " board = []\n", + " start = [0, 1]\n", + " end = [3, 5]\n", + " cost = 1 # cost is 1 per movement\n", + "\n", + " a_start = Astar()\n", + " self.assertEqual(a_start.search(board=board, cost=cost, start=start, end=end), None)\n", + " print('Success: test_astar_with_empty_board')\n", + "\n", + " def test_astar_with_valid_board(self):\n", + " board = [\n", + " [0, 1, 0, 0, 0, 0],\n", + " [0, 0, 0, 0, 0, 1],\n", + " [0, 1, 0, 1, 0, 0],\n", + " [0, 1, 0, 0, 1, 0],\n", + " [0, 0, 0, 0, 1, 0],\n", + " ]\n", + " start = [0, 0]\n", + " end = [3, 5]\n", + " cost = 1 # cost is 1 per movement\n", + " expected = [\n", + " [0, -1, -1, -1, -1, -1],\n", + " [1, 2, 3, 4, 5, -1],\n", + " [-1, -1, -1, -1, 6, 7],\n", + " [-1, -1, -1, -1, -1, 8],\n", + " [-1, -1, -1, -1, -1, -1]\n", + " ]\n", + "\n", + " a_start = Astar()\n", + " self.assertEqual(a_start.search(board=board, cost=cost, start=start, end=end), expected)\n", + " print('Success: test_astar_with_valid_board')\n", + "\n", + " def test_astar_with_another_valid_board(self):\n", + " board = [\n", + " [0, 1, 0, 0, 0, 0],\n", + " [0, 0, 0, 0, 0, 1],\n", + " [0, 1, 0, 1, 0, 0],\n", + " [0, 1, 0, 0, 1, 0],\n", + " [0, 0, 0, 0, 1, 0],\n", + " ]\n", + " start = [0, 5]\n", + " end = [4, 0]\n", + " cost = 1 # cost is 1 per movement\n", + " expected = [\n", + " [-1, -1, -1, -1, 1, 0],\n", + " [-1, -1, 4, 3, 2, -1],\n", + " [-1, -1, 5, -1, -1, -1],\n", + " [-1, -1, 6, -1, -1, -1],\n", + " [9, 8, 7, -1, -1, -1]\n", + " ]\n", + "\n", + " a_start = Astar()\n", + " self.assertEqual(a_start.search(board=board, cost=cost, start=start, end=end), expected)\n", + " print('Success: test_astar_with_another_valid_board')\n", + "\n", + "\n", + " def test_astar_start_from_danger_zone(self):\n", + " board = [\n", + " [0, 1, 0, 0, 0, 0],\n", + " [0, 0, 0, 0, 0, 1],\n", + " [0, 1, 0, 1, 0, 0],\n", + " [0, 1, 0, 0, 1, 0],\n", + " [0, 0, 0, 0, 1, 0],\n", + " ]\n", + " start = [0, 1]\n", + " end = [3, 5]\n", + " cost = 1 # cost is 1 per movement\n", + "\n", + " a_start = Astar()\n", + " self.assertEqual(a_start.search(board=board, cost=cost, start=start, end=end), None)\n", + " print('Success: test_astar_start_from_danger_zone')\n", + "\n", + "\n", + " def test_astar_with_danger_zone_as_end(self):\n", + " board = [\n", + " [0, 1, 0, 0, 0, 0],\n", + " [0, 0, 0, 0, 0, 1],\n", + " [0, 1, 0, 1, 0, 0],\n", + " [0, 1, 0, 0, 1, 0],\n", + " [0, 0, 0, 0, 1, 0],\n", + " ]\n", + " start = [0, 0]\n", + " end = [2, 3]\n", + " cost = 1\n", + " expected = None\n", + "\n", + " a_start = Astar()\n", + " self.assertEqual(a_start.search(board=board, cost=cost, start=start, end=end), expected)\n", + " print('Success: test_astar_with_danger_zone_as_end')\n", + "\n", + "\n", + "def main():\n", + " test = TestAstar()\n", + " test.test_astar_with_empty_board()\n", + " test.test_astar_with_valid_board()\n", + " test.test_astar_with_another_valid_board()\n", + " test.test_astar_start_from_danger_zone()\n", + " test.test_astar_with_danger_zone_as_end()\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " main()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Success: test_astar_with_empty_board\n", + "Goal reaches in 9 iterations...\n", + "Success: test_astar_with_valid_board\n", + "Goal reaches in 10 iterations...\n", + "Success: test_astar_with_another_valid_board\n", + "Success: test_astar_start_from_danger_zone\n", + "Success: test_astar_with_danger_zone_as_end\n" + ] + } + ], + "source": [ + "%run -i test_astar.py" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/graphs_trees/a_star/test_astar.py b/graphs_trees/a_star/test_astar.py new file mode 100644 index 0000000..2d98120 --- /dev/null +++ b/graphs_trees/a_star/test_astar.py @@ -0,0 +1,108 @@ + +from unittest import TestCase + + +class TestAstar(TestCase): + def test_astar_with_empty_board(self): + board = [] + start = [0, 1] + end = [3, 5] + cost = 1 # cost is 1 per movement + + a_start = Astar() + self.assertEqual(a_start.search(board=board, cost=cost, start=start, end=end), None) + print('Success: test_astar_with_empty_board') + + def test_astar_with_valid_board(self): + board = [ + [0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1], + [0, 1, 0, 1, 0, 0], + [0, 1, 0, 0, 1, 0], + [0, 0, 0, 0, 1, 0], + ] + start = [0, 0] + end = [3, 5] + cost = 1 # cost is 1 per movement + expected = [ + [0, -1, -1, -1, -1, -1], + [1, 2, 3, 4, 5, -1], + [-1, -1, -1, -1, 6, 7], + [-1, -1, -1, -1, -1, 8], + [-1, -1, -1, -1, -1, -1] + ] + + a_start = Astar() + self.assertEqual(a_start.search(board=board, cost=cost, start=start, end=end), expected) + print('Success: test_astar_with_valid_board') + + def test_astar_with_another_valid_board(self): + board = [ + [0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1], + [0, 1, 0, 1, 0, 0], + [0, 1, 0, 0, 1, 0], + [0, 0, 0, 0, 1, 0], + ] + start = [0, 5] + end = [4, 0] + cost = 1 # cost is 1 per movement + expected = [ + [-1, -1, -1, -1, 1, 0], + [-1, -1, 4, 3, 2, -1], + [-1, -1, 5, -1, -1, -1], + [-1, -1, 6, -1, -1, -1], + [9, 8, 7, -1, -1, -1] + ] + + a_start = Astar() + self.assertEqual(a_start.search(board=board, cost=cost, start=start, end=end), expected) + print('Success: test_astar_with_another_valid_board') + + + def test_astar_start_from_danger_zone(self): + board = [ + [0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1], + [0, 1, 0, 1, 0, 0], + [0, 1, 0, 0, 1, 0], + [0, 0, 0, 0, 1, 0], + ] + start = [0, 1] + end = [3, 5] + cost = 1 # cost is 1 per movement + + a_start = Astar() + self.assertEqual(a_start.search(board=board, cost=cost, start=start, end=end), None) + print('Success: test_astar_start_from_danger_zone') + + + def test_astar_with_danger_zone_as_end(self): + board = [ + [0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1], + [0, 1, 0, 1, 0, 0], + [0, 1, 0, 0, 1, 0], + [0, 0, 0, 0, 1, 0], + ] + start = [0, 0] + end = [2, 3] + cost = 1 + expected = None + + a_start = Astar() + self.assertEqual(a_start.search(board=board, cost=cost, start=start, end=end), expected) + print('Success: test_astar_with_danger_zone_as_end') + + +def main(): + test = TestAstar() + test.test_astar_with_empty_board() + test.test_astar_with_valid_board() + test.test_astar_with_another_valid_board() + test.test_astar_start_from_danger_zone() + test.test_astar_with_danger_zone_as_end() + + +if __name__ == '__main__': + main()