mirror of
https://github.com/donnemartin/interactive-coding-challenges.git
synced 2024-03-22 13:11:13 +08:00
a star algorithm
This commit is contained in:
parent
358f2cc604
commit
918d172799
0
graphs_trees/a_star/__init__.py
Normal file
0
graphs_trees/a_star/__init__.py
Normal file
319
graphs_trees/a_star/astar_challenge.ipynb
Normal file
319
graphs_trees/a_star/astar_challenge.ipynb
Normal file
|
@ -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",
|
||||
"<table>\n",
|
||||
"<tr>\n",
|
||||
"<td>board</td>\n",
|
||||
"<td>start</td>\n",
|
||||
"<td>end</td>\n",
|
||||
"<td>cost</td>\n",
|
||||
"<td>output</td>\n",
|
||||
"</tr>\n",
|
||||
"<tr>\n",
|
||||
"<td>[]</td>\n",
|
||||
"<td>[0, 0]</td>\n",
|
||||
"<td>[3, 5]</td>\n",
|
||||
"<td>1</td>\n",
|
||||
"<td>None</td>\n",
|
||||
"</tr>\n",
|
||||
"<tr>\n",
|
||||
"<td><pre>[[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]]</pre></td>\n",
|
||||
"<td>[0, 0]</td>\n",
|
||||
"<td>[3, 5]</td>\n",
|
||||
"<td>1</td>\n",
|
||||
"<td><pre>[[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]]</pre></td>\n",
|
||||
"</tr>\n",
|
||||
" <tr>\n",
|
||||
"<td><pre>[[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]]</pre></td>\n",
|
||||
"<td>[0, 5]</td>\n",
|
||||
"<td>[4, 0]</td>\n",
|
||||
"<td>1</td>\n",
|
||||
"<td><pre>[[-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]]</pre></td>\n",
|
||||
"</tr>\n",
|
||||
"<tr>\n",
|
||||
"<td><pre>[[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]]</pre></td>\n",
|
||||
"<td>[0, 1]</td>\n",
|
||||
"<td>[3, 5]</td>\n",
|
||||
"<td>1</td>\n",
|
||||
"<td>None</td>\n",
|
||||
"</tr>\n",
|
||||
"<tr>\n",
|
||||
"<td><pre>[[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]]</pre></td>\n",
|
||||
"<td>[0, 0]</td>\n",
|
||||
"<td>[2, 3]</td>\n",
|
||||
"<td>1</td>\n",
|
||||
"<td>None</td>\n",
|
||||
"</tr>\n",
|
||||
"</table>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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
|
||||
}
|
494
graphs_trees/a_star/astar_solution.ipynb
Normal file
494
graphs_trees/a_star/astar_solution.ipynb
Normal file
|
@ -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",
|
||||
"<table>\n",
|
||||
"<tr>\n",
|
||||
"<td>board</td>\n",
|
||||
"<td>start</td>\n",
|
||||
"<td>end</td>\n",
|
||||
"<td>cost</td>\n",
|
||||
"<td>expected</td>\n",
|
||||
"</tr>\n",
|
||||
"<tr>\n",
|
||||
"<td>[]</td>\n",
|
||||
"<td>[0, 0]</td>\n",
|
||||
"<td>[3, 5]</td>\n",
|
||||
"<td>1</td>\n",
|
||||
"<td>None</td>\n",
|
||||
"</tr>\n",
|
||||
"<tr>\n",
|
||||
"<td><pre>[[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]]</pre></td>\n",
|
||||
"<td>[0, 0]</td>\n",
|
||||
"<td>[3, 5]</td>\n",
|
||||
"<td>1</td>\n",
|
||||
"<td><pre>[[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]]</pre></td>\n",
|
||||
"</tr>\n",
|
||||
" <tr>\n",
|
||||
"<td><pre>[[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]]</pre></td>\n",
|
||||
"<td>[0, 5]</td>\n",
|
||||
"<td>[4, 0]</td>\n",
|
||||
"<td>1</td>\n",
|
||||
"<td><pre>[[-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]]</pre></td>\n",
|
||||
"</tr>\n",
|
||||
"<tr>\n",
|
||||
"<td><pre>[[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]]</pre></td>\n",
|
||||
"<td>[0, 1]</td>\n",
|
||||
"<td>[3, 5]</td>\n",
|
||||
"<td>1</td>\n",
|
||||
"<td>None</td>\n",
|
||||
"</tr>\n",
|
||||
"<tr>\n",
|
||||
"<td><pre>[[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]]</pre></td>\n",
|
||||
"<td>[0, 0]</td>\n",
|
||||
"<td>[2, 3]</td>\n",
|
||||
"<td>1</td>\n",
|
||||
"<td>None</td>\n",
|
||||
"</tr>\n",
|
||||
"</table>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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
|
||||
}
|
108
graphs_trees/a_star/test_astar.py
Normal file
108
graphs_trees/a_star/test_astar.py
Normal file
|
@ -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()
|
Loading…
Reference in New Issue
Block a user