{ "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": {}, "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": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Overwriting test_grid_path.py\n" ] } ], "source": [ "%%writefile test_grid_path.py\n", "import unittest\n", "\n", "\n", "class TestGridPath(unittest.TestCase):\n", "\n", " def test_grid_path(self):\n", " grid = Grid()\n", " self.assertEqual(grid.find_path(None), None)\n", " self.assertEqual(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", " self.assertEqual(result, expected)\n", " matrix[7][2] = 0\n", " result = grid.find_path(matrix)\n", " self.assertEqual(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": {}, "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.7.2" } }, "nbformat": 4, "nbformat_minor": 1 }