{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# <font color='blue'>Data Science Academy - Python Fundamentos - Capítulo 7</font>\n",
    "\n",
    "## Download: http://github.com/dsacademybr"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Missão: Implementar um algoritmo para mover um robô do canto superior esquerdo para o canto inferior direito de uma grade."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Nível de Dificuldade: Médio"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Premissas\n",
    "\n",
    "* Existem restrições de como o robô se move?\n",
    "     * O robô só pode se mover para a direita e para baixo\n",
    "* Algumas células são inválidas (fora dos limites)?\n",
    "     * Sim\n",
    "* Podemos supor que as células inicial e final são células válidas?\n",
    "     * Sim\n",
    "* Isso é uma grade retangular? ou seja, a grade não é irregular?\n",
    "     * Sim\n",
    "* Haverá sempre uma maneira válida para o robô chegar ao canto inferior direito?\n",
    "     * Não, retorno None\n",
    "* Podemos assumir que as entradas são válidas?\n",
    "     * Não\n",
    "* Podemos supor que isso se encaixa na memória?\n",
    "     * Sim"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Teste Cases\n",
    "\n",
    "<pre>\n",
    "o = célula válida\n",
    "x = célula inválida\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",
    "</pre>\n",
    "\n",
    "* Caso geral\n",
    "\n",
    "```\n",
    "Saída esperada = [(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",
    "* Nenhum caminho válido, por exemplo, linha 7, col 2 é inválido\n",
    "* Nenhuma entrada\n",
    "* Matriz vazia"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Algoritmo\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": [
    "## Solução"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "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": [
    "## Teste da Solução"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Writing missao3.py\n"
     ]
    }
   ],
   "source": [
    "%%writefile missao3.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('Sua solução foi executada com sucesso! Parabéns!')\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": [
      "Sua solução foi executada com sucesso! Parabéns!\n"
     ]
    }
   ],
   "source": [
    "%run -i missao3.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Fim"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Obrigado - Data Science Academy - <a href=\"http://facebook.com/dsacademybr\">facebook.com/dsacademybr</a>"
   ]
  }
 ],
 "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.6.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
