From a0afa5c9d9148b2c4846afb78422811f0f908be4 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Mon, 27 Mar 2017 05:19:37 -0400 Subject: [PATCH] Add steps challenge --- recursion_dynamic/steps/__init__.py | 0 recursion_dynamic/steps/steps_challenge.ipynb | 172 +++++++++++++ recursion_dynamic/steps/steps_solution.ipynb | 228 ++++++++++++++++++ recursion_dynamic/steps/test_steps.py | 25 ++ 4 files changed, 425 insertions(+) create mode 100644 recursion_dynamic/steps/__init__.py create mode 100644 recursion_dynamic/steps/steps_challenge.ipynb create mode 100644 recursion_dynamic/steps/steps_solution.ipynb create mode 100644 recursion_dynamic/steps/test_steps.py diff --git a/recursion_dynamic/steps/__init__.py b/recursion_dynamic/steps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/recursion_dynamic/steps/steps_challenge.ipynb b/recursion_dynamic/steps/steps_challenge.ipynb new file mode 100644 index 0000000..b1eb3d2 --- /dev/null +++ b/recursion_dynamic/steps/steps_challenge.ipynb @@ -0,0 +1,172 @@ +{ + "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: You are running up n steps. If you can take a single, double, or triple step, how many possible ways are there to run up to the nth step?\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", + "* If n == 0, what should the result be?\n", + " * Go with 1, but discuss different approaches\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", + "* None or negative input -> Exception\n", + "* n == 0 -> 1\n", + "* n == 1 -> 1\n", + "* n == 2 -> 2\n", + "* n == 3 -> 4\n", + "* n == 4 -> 7\n", + "* n == 10 -> 274" + ] + }, + { + "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 Steps(object):\n", + "\n", + " def count_ways(self, num_steps):\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_steps.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSteps(object):\n", + "\n", + " def test_steps(self):\n", + " steps = Steps()\n", + " assert_raises(TypeError, steps.count_ways, None)\n", + " assert_raises(TypeError, steps.count_ways, -1)\n", + " assert_equal(steps.count_ways(0), 1)\n", + " assert_equal(steps.count_ways(1), 1)\n", + " assert_equal(steps.count_ways(2), 2)\n", + " assert_equal(steps.count_ways(3), 4)\n", + " assert_equal(steps.count_ways(4), 7)\n", + " assert_equal(steps.count_ways(10), 274)\n", + " print('Success: test_steps')\n", + "\n", + "\n", + "def main():\n", + " test = TestSteps()\n", + " test.test_steps()\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/steps/steps_solution.ipynb b/recursion_dynamic/steps/steps_solution.ipynb new file mode 100644 index 0000000..be65d1c --- /dev/null +++ b/recursion_dynamic/steps/steps_solution.ipynb @@ -0,0 +1,228 @@ +{ + "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: You are running up n steps. If you can take a single, double, or triple step, how many possible ways are there to run up to the nth step?\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", + "* If n == 0, what should the result be?\n", + " * Go with 1, but discuss different approaches\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", + "* None or negative input -> Exception\n", + "* n == 0 -> 1\n", + "* n == 1 -> 1\n", + "* n == 2 -> 2\n", + "* n == 3 -> 4\n", + "* n == 4 -> 7\n", + "* n == 10 -> 274" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "To get to step n, we will need to have gone:\n", + "\n", + "* One step from n-1\n", + "* Two steps from n-2\n", + "* Three steps from n-3\n", + "\n", + "If we go the one step route above, we'll be at n-1 before taking the last step. To get to step n-1, we will need to have gone:\n", + "\n", + "* One step from n-1-1\n", + "* Two steps from n-1-2\n", + "* Three steps from n-1-2\n", + "\n", + "Continue this process until we reach the start.\n", + "\n", + "Base case:\n", + "\n", + "* If n < 0: return 0\n", + "* If n == 0: return 1\n", + "\n", + "Note, if we had chosen n == 0 to return 0 instead, we would need to add additional base cases. Otherwise we'd be adding multiple 0's once we hit the base cases and not get any result > 0.\n", + "\n", + "Recursive case:\n", + "\n", + "We'll memoize the solution to improve performance.\n", + "\n", + "* Use the memo if we've already processed the current step.\n", + "* Update the memo by adding the recursive calls to step(n-1), step(n-2), step(n-3)\n", + "\n", + "Complexity:\n", + "* Time: O(n), if using memoization\n", + "* Space: O(n), where n is the recursion depth\n", + "\n", + "Note: The number of ways will quickly overflow the bounds of an integer." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Steps(object):\n", + "\n", + " def count_ways(self, num_steps):\n", + " if num_steps is None or num_steps < 0:\n", + " raise TypeError('num_steps cannot be None or negative')\n", + " cache = {}\n", + " return self._count_ways(num_steps, cache)\n", + "\n", + " def _count_ways(self, num_steps, cache):\n", + " if num_steps < 0:\n", + " return 0\n", + " if num_steps == 0:\n", + " return 1\n", + " if num_steps in cache:\n", + " return cache[num_steps]\n", + " cache[num_steps] = (self._count_ways(num_steps-1, cache) +\n", + " self._count_ways(num_steps-2, cache) +\n", + " self._count_ways(num_steps-3, cache))\n", + " return cache[num_steps]" + ] + }, + { + "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_steps.py\n" + ] + } + ], + "source": [ + "%%writefile test_steps.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSteps(object):\n", + "\n", + " def test_steps(self):\n", + " steps = Steps()\n", + " assert_raises(TypeError, steps.count_ways, None)\n", + " assert_raises(TypeError, steps.count_ways, -1)\n", + " assert_equal(steps.count_ways(0), 1)\n", + " assert_equal(steps.count_ways(1), 1)\n", + " assert_equal(steps.count_ways(2), 2)\n", + " assert_equal(steps.count_ways(3), 4)\n", + " assert_equal(steps.count_ways(4), 7)\n", + " assert_equal(steps.count_ways(10), 274)\n", + " print('Success: test_steps')\n", + "\n", + "\n", + "def main():\n", + " test = TestSteps()\n", + " test.test_steps()\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_steps\n" + ] + } + ], + "source": [ + "%run -i test_steps.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/steps/test_steps.py b/recursion_dynamic/steps/test_steps.py new file mode 100644 index 0000000..d506287 --- /dev/null +++ b/recursion_dynamic/steps/test_steps.py @@ -0,0 +1,25 @@ +from nose.tools import assert_equal, assert_raises + + +class TestSteps(object): + + def test_steps(self): + steps = Steps() + assert_raises(TypeError, steps.count_ways, None) + assert_raises(TypeError, steps.count_ways, -1) + assert_equal(steps.count_ways(0), 1) + assert_equal(steps.count_ways(1), 1) + assert_equal(steps.count_ways(2), 2) + assert_equal(steps.count_ways(3), 4) + assert_equal(steps.count_ways(4), 7) + assert_equal(steps.count_ways(10), 274) + print('Success: test_steps') + + +def main(): + test = TestSteps() + test.test_steps() + + +if __name__ == '__main__': + main() \ No newline at end of file