diff --git a/recursion_dynamic/grid_path/__init__.py b/recursion_dynamic/grid_path/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/recursion_dynamic/grid_path/grid_path_challenge.ipynb b/recursion_dynamic/grid_path/grid_path_challenge.ipynb new file mode 100644 index 0000000..d9139a5 --- /dev/null +++ b/recursion_dynamic/grid_path/grid_path_challenge.ipynb @@ -0,0 +1,212 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook was prepared by [Donne Martin](https://github.com/donnemartin). 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 an algorithm to have a robot move from the upper left corner to the bottom right corner of a grid.\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", + "* Are there restrictions to how the robot moves?\n", + " * The robot can only move right and down\n", + "* Are some cells off limits?\n", + " * Yes\n", + "* Is this a rectangular grid? i.e. the grid is not jagged?\n", + " * Yes\n", + "* Will there always be a valid way for the robot to get to the bottom right?\n", + " * No, return None\n", + "* Can we assume the inputs are valid?\n", + " * No\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "
\n", + "o = valid cell\n", + "x = invalid cell\n", + "\n", + " 0 1 2 3\n", + "0 o o o o\n", + "1 o x o o\n", + "2 o o x o\n", + "3 x o o o\n", + "4 o o x o\n", + "5 o o o x\n", + "6 o x o x\n", + "7 o x o o\n", + "\n", + "\n", + "* General case\n", + "\n", + "```\n", + "expected = [(0, 0), (1, 0), (2, 0),\n", + " (2, 1), (3, 1), (4, 1),\n", + " (5, 1), (5, 2), (6, 2), \n", + " (7, 2), (7, 3)]\n", + "```\n", + "\n", + "* No valid path: In above example, row 7 col 2 is also invalid -> None\n", + "* None input -> None\n", + "* Empty matrix -> None" + ] + }, + { + "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": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Grid(object):\n", + "\n", + " def find_path(self, matrix):\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": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# %load test_grid_path.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestGridPath(object):\n", + "\n", + " def test_grid_path(self):\n", + " grid = Grid()\n", + " assert_equal(grid.find_path(None), None)\n", + " assert_equal(grid.find_path([[]]), None)\n", + " max_rows = 8\n", + " max_cols = 4\n", + " matrix = [[1] * max_cols for _ in range(max_rows)]\n", + " matrix[1][1] = 0\n", + " matrix[2][2] = 0\n", + " matrix[3][0] = 0\n", + " matrix[4][2] = 0\n", + " matrix[5][3] = 0\n", + " matrix[6][1] = 0\n", + " matrix[6][3] = 0\n", + " matrix[7][1] = 0\n", + " result = grid.find_path(matrix)\n", + " expected = [(0, 0), (1, 0), (2, 0),\n", + " (2, 1), (3, 1), (4, 1),\n", + " (5, 1), (5, 2), (6, 2), \n", + " (7, 2), (7, 3)]\n", + " assert_equal(result, expected)\n", + " matrix[7][2] = 0\n", + " result = grid.find_path(matrix)\n", + " assert_equal(result, None)\n", + " print('Success: test_grid_path')\n", + "\n", + "\n", + "def main():\n", + " test = TestGridPath()\n", + " test.test_grid_path()\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " main()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Solution Notebook\n", + "\n", + "Review the [Solution Notebook]() for a discussion on algorithms and code solutions." + ] + } + ], + "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.5.0" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/recursion_dynamic/grid_path/grid_path_solution.ipynb b/recursion_dynamic/grid_path/grid_path_solution.ipynb new file mode 100644 index 0000000..18fc1f8 --- /dev/null +++ b/recursion_dynamic/grid_path/grid_path_solution.ipynb @@ -0,0 +1,276 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook was prepared by [Donne Martin](https://github.com/donnemartin). 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 an algorithm to have a robot move from the upper left corner to the bottom right corner of a grid.\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", + "* Are there restrictions to how the robot moves?\n", + " * The robot can only move right and down\n", + "* Are some cells invalid (off limits)?\n", + " * Yes\n", + "* Can we assume the starting and ending cells are valid cells?\n", + " * Yes\n", + "* Is this a rectangular grid? i.e. the grid is not jagged?\n", + " * Yes\n", + "* Will there always be a valid way for the robot to get to the bottom right?\n", + " * No, return None\n", + "* Can we assume the inputs are valid?\n", + " * No\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "
\n", + "o = valid cell\n", + "x = invalid cell\n", + "\n", + " 0 1 2 3\n", + "0 o o o o\n", + "1 o x o o\n", + "2 o o x o\n", + "3 x o o o\n", + "4 o o x o\n", + "5 o o o x\n", + "6 o x o x\n", + "7 o x o o\n", + "\n", + "\n", + "* General case\n", + "\n", + "```\n", + "expected = [(0, 0), (1, 0), (2, 0),\n", + " (2, 1), (3, 1), (4, 1),\n", + " (5, 1), (5, 2), (6, 2), \n", + " (7, 2), (7, 3)]\n", + "```\n", + "\n", + "* No valid path, say row 7, col 2 is invalid\n", + "* None input\n", + "* Empty matrix" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "To get to row r and column c [r, c], we will need to have gone:\n", + "\n", + "* Right from [r, c-1] if this is a valid cell - [Path 1] \n", + "* Down from [r-1, c] if this is a valid cell - [Path 2]\n", + "\n", + "If we look at [Path 1], to get to [r, c-1], we will need to have gone:\n", + "\n", + "* Right from [r, c-2] if this is a valid cell\n", + "* Down from [r-1, c-1] if this is a valid cell\n", + "\n", + "Continue this process until we reach the start cell or until we find that there is no path.\n", + "\n", + "Base case:\n", + "\n", + "* If the input row or col are < 0, or if [row, col] is not a valid cell\n", + " * Return False\n", + "\n", + "Recursive case:\n", + "\n", + "We'll memoize the solution to improve performance.\n", + "\n", + "* Use the memo to see if we've already processed the current cell\n", + "* If any of the following is True, append the current cell to the path and set our result to True:\n", + " * We are at the start cell\n", + " * We get a True result from a recursive call on:\n", + " * [row, col-1]\n", + " * [row-1, col]\n", + "* Update the memo\n", + "* Return the result\n", + "\n", + "Complexity:\n", + "* Time: O(row * col)\n", + "* Space: O(row * col) for the recursion depth" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Grid(object):\n", + "\n", + " def find_path(self, matrix):\n", + " if matrix is None or not matrix:\n", + " return None\n", + " cache = {}\n", + " path = []\n", + " if self._find_path(matrix, len(matrix) - 1, \n", + " len(matrix[0]) - 1, cache, path):\n", + " return path\n", + " else:\n", + " return None\n", + "\n", + " def _find_path(self, matrix, row, col, cache, path):\n", + " if row < 0 or col < 0 or not matrix[row][col]:\n", + " return False\n", + " cell = (row, col)\n", + " if cell in cache:\n", + " return cache[cell]\n", + " cache[cell] = (row == 0 and col == 0 or\n", + " self._find_path(matrix, row, col - 1, cache, path) or\n", + " self._find_path(matrix, row - 1, col, cache, path))\n", + " if cache[cell]:\n", + " path.append(cell)\n", + " return cache[cell]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Unit Test" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting test_grid_path.py\n" + ] + } + ], + "source": [ + "%%writefile test_grid_path.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestGridPath(object):\n", + "\n", + " def test_grid_path(self):\n", + " grid = Grid()\n", + " assert_equal(grid.find_path(None), None)\n", + " assert_equal(grid.find_path([[]]), None)\n", + " max_rows = 8\n", + " max_cols = 4\n", + " matrix = [[1] * max_cols for _ in range(max_rows)]\n", + " matrix[1][1] = 0\n", + " matrix[2][2] = 0\n", + " matrix[3][0] = 0\n", + " matrix[4][2] = 0\n", + " matrix[5][3] = 0\n", + " matrix[6][1] = 0\n", + " matrix[6][3] = 0\n", + " matrix[7][1] = 0\n", + " result = grid.find_path(matrix)\n", + " expected = [(0, 0), (1, 0), (2, 0),\n", + " (2, 1), (3, 1), (4, 1),\n", + " (5, 1), (5, 2), (6, 2), \n", + " (7, 2), (7, 3)]\n", + " assert_equal(result, expected)\n", + " matrix[7][2] = 0\n", + " result = grid.find_path(matrix)\n", + " assert_equal(result, None)\n", + " print('Success: test_grid_path')\n", + "\n", + "\n", + "def main():\n", + " test = TestGridPath()\n", + " test.test_grid_path()\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " main()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Success: test_grid_path\n" + ] + } + ], + "source": [ + "%run -i test_grid_path.py" + ] + } + ], + "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.5.0" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/recursion_dynamic/grid_path/test_grid_path.py b/recursion_dynamic/grid_path/test_grid_path.py new file mode 100644 index 0000000..9249cda --- /dev/null +++ b/recursion_dynamic/grid_path/test_grid_path.py @@ -0,0 +1,39 @@ +from nose.tools import assert_equal + + +class TestGridPath(object): + + def test_grid_path(self): + grid = Grid() + assert_equal(grid.find_path(None), None) + assert_equal(grid.find_path([[]]), None) + max_rows = 8 + max_cols = 4 + matrix = [[1] * max_cols for _ in range(max_rows)] + matrix[1][1] = 0 + matrix[2][2] = 0 + matrix[3][0] = 0 + matrix[4][2] = 0 + matrix[5][3] = 0 + matrix[6][1] = 0 + matrix[6][3] = 0 + matrix[7][1] = 0 + result = grid.find_path(matrix) + expected = [(0, 0), (1, 0), (2, 0), + (2, 1), (3, 1), (4, 1), + (5, 1), (5, 2), (6, 2), + (7, 2), (7, 3)] + assert_equal(result, expected) + matrix[7][2] = 0 + result = grid.find_path(matrix) + assert_equal(result, None) + print('Success: test_grid_path') + + +def main(): + test = TestGridPath() + test.test_grid_path() + + +if __name__ == '__main__': + main() \ No newline at end of file