diff --git a/recursion_dynamic/knapsack_unbounded/__init__.py b/recursion_dynamic/knapsack_unbounded/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/recursion_dynamic/knapsack_unbounded/knapsack_unbounded_challenge.ipynb b/recursion_dynamic/knapsack_unbounded/knapsack_unbounded_challenge.ipynb new file mode 100644 index 0000000..b651724 --- /dev/null +++ b/recursion_dynamic/knapsack_unbounded/knapsack_unbounded_challenge.ipynb @@ -0,0 +1,211 @@ +{ + "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: Given a knapsack with a total weight capacity and a list of items with weight w(i) and value v(i), determine the max total value you can carry.\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 replace the items once they are placed in the knapsack?\n", + " * Yes, this is the unbounded knapsack problem\n", + "* Can we split an item?\n", + " * No\n", + "* Can we get an input item with weight of 0 or value of 0?\n", + " * No\n", + "* Do we need to return the items that make up the max total value?\n", + " * No, just the total value\n", + "* Can we assume the inputs are valid?\n", + " * No\n", + "* Are the inputs in sorted order by val/weight?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* items or total weight is None -> Exception\n", + "* items or total weight is 0 -> 0\n", + "* General case\n", + "\n", + "
\n",
+    "total_weight = 8\n",
+    "items\n",
+    "  v | w\n",
+    "  0 | 0\n",
+    "a 1 | 1\n",
+    "b 3 | 2\n",
+    "c 7 | 4\n",
+    "\n",
+    "max value = 14 \n",
+    "
" + ] + }, + { + "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": true + }, + "outputs": [], + "source": [ + "class Item(object):\n", + "\n", + " def __init__(self, label, value, weight):\n", + " self.label = label\n", + " self.value = value\n", + " self.weight = weight\n", + "\n", + " def __repr__(self):\n", + " return self.label + ' v:' + str(self.value) + ' w:' + str(self.weight)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Knapsack(object):\n", + "\n", + " def fill_knapsack(self, input_items, total_weight):\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_knapsack_unbounded.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestKnapsack(object):\n", + "\n", + " def test_knapsack(self):\n", + " knapsack = Knapsack()\n", + " assert_raises(TypeError, knapsack.fill_knapsack, None, None)\n", + " assert_equal(knapsack.fill_knapsack(0, 0), 0)\n", + " items = []\n", + " items.append(Item(label='a', value=1, weight=1))\n", + " items.append(Item(label='b', value=3, weight=2))\n", + " items.append(Item(label='c', value=7, weight=4))\n", + " total_weight = 8\n", + " expected_value = 14\n", + " results = knapsack.fill_knapsack(items, total_weight)\n", + " total_weight = 7\n", + " expected_value = 11\n", + " results = knapsack.fill_knapsack(items, total_weight)\n", + " assert_equal(results, expected_value)\n", + " print('Success: test_knapsack')\n", + "\n", + "def main():\n", + " test = TestKnapsack()\n", + " test.test_knapsack()\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/knapsack_unbounded/knapsack_unbounded_solution.ipynb b/recursion_dynamic/knapsack_unbounded/knapsack_unbounded_solution.ipynb new file mode 100644 index 0000000..867a00c --- /dev/null +++ b/recursion_dynamic/knapsack_unbounded/knapsack_unbounded_solution.ipynb @@ -0,0 +1,298 @@ +{ + "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: Given a knapsack with a total weight capacity and a list of items with weight w(i) and value v(i), determine the max total value you can carry.\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 replace the items once they are placed in the knapsack?\n", + " * Yes, this is the unbounded knapsack problem\n", + "* Can we split an item?\n", + " * No\n", + "* Can we get an input item with weight of 0 or value of 0?\n", + " * No\n", + "* Do we need to return the items that make up the max total value?\n", + " * No, just the total value\n", + "* Can we assume the inputs are valid?\n", + " * No\n", + "* Are the inputs in sorted order by val/weight?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* items or total weight is None -> Exception\n", + "* items or total weight is 0 -> 0\n", + "* General case\n", + "\n", + "
\n",
+    "total_weight = 8\n",
+    "items\n",
+    "  v | w\n",
+    "  0 | 0\n",
+    "a 1 | 1\n",
+    "b 3 | 2\n",
+    "c 7 | 4\n",
+    "\n",
+    "max value = 14 \n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "We'll use bottom up dynamic programming to build a table. \n", + "\n", + "Taking what we learned with the 0/1 knapsack problem, we could solve the problem like the following:\n", + "\n", + "
\n",
+    "\n",
+    "v = value\n",
+    "w = weight\n",
+    "\n",
+    "               j              \n",
+    "    -------------------------------------------------\n",
+    "    | v | w || 0 | 1 | 2 | 3 | 4 | 5 |  6 |  7 |  8  |\n",
+    "    -------------------------------------------------\n",
+    "    | 0 | 0 || 0 | 0 | 0 | 0 | 0 | 0 |  0 |  0 |  0  |\n",
+    "  a | 1 | 1 || 0 | 1 | 2 | 3 | 4 | 5 |  6 |  7 |  8  |\n",
+    "i b | 3 | 2 || 0 | 1 | 3 | 4 | 6 | 7 |  9 | 10 | 12  |\n",
+    "  c | 7 | 4 || 0 | 1 | 3 | 4 | 7 | 8 | 10 | 11 | 14  |\n",
+    "    -------------------------------------------------\n",
+    "\n",
+    "i = row\n",
+    "j = col\n",
+    "\n",
+    "
\n", + "\n", + "However, unlike the 0/1 knapsack variant, we don't actually need to keep space of O(n * w), where n is the number of items and w is the total weight. We just need a single array that we update after we process each item:\n", + "\n", + "
\n",
+    "\n",
+    "    -------------------------------------------------\n",
+    "    | v | w || 0 | 1 | 2 | 3 | 4 | 5 |  6 |  7 |  8  |\n",
+    "    -------------------------------------------------\n",
+    "\n",
+    "    -------------------------------------------------\n",
+    "  a | 1 | 1 || 0 | 1 | 2 | 3 | 4 | 5 |  6 |  7 |  8  |\n",
+    "    -------------------------------------------------\n",
+    "\n",
+    "    -------------------------------------------------\n",
+    "  b | 3 | 2 || 0 | 1 | 3 | 4 | 6 | 7 |  9 | 10 | 12  |\n",
+    "    -------------------------------------------------\n",
+    "\n",
+    "    -------------------------------------------------\n",
+    "  c | 7 | 4 || 0 | 1 | 3 | 4 | 7 | 8 | 10 | 11 | 14  |\n",
+    "    -------------------------------------------------\n",
+    "\n",
+    "if j >= items[i].weight:\n",
+    "    T[j] = max(items[i].value + T[j - items[i].weight],\n",
+    "               T[j])\n",
+    "\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(n * w), where n is the number of items and w is the total weight\n", + "* Space: O(w), where w is the total weight" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Item Class" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class Item(object):\n", + "\n", + " def __init__(self, label, value, weight):\n", + " self.label = label\n", + " self.value = value\n", + " self.weight = weight\n", + "\n", + " def __repr__(self):\n", + " return self.label + ' v:' + str(self.value) + ' w:' + str(self.weight)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Knapsack Bottom Up" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Knapsack(object):\n", + "\n", + " def fill_knapsack(self, items, total_weight):\n", + " if items is None or total_weight is None:\n", + " raise TypeError('items or total_weight cannot be None')\n", + " if not items or total_weight == 0:\n", + " return 0\n", + " num_rows = len(items)\n", + " num_cols = total_weight + 1\n", + " T = [0] * (num_cols)\n", + " for i in range(num_rows):\n", + " for j in range(num_cols):\n", + " if j >= items[i].weight:\n", + " T[j] = max(items[i].value + T[j - items[i].weight],\n", + " T[j])\n", + " return T[-1]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Unit Test" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting test_knapsack_unbounded.py\n" + ] + } + ], + "source": [ + "%%writefile test_knapsack_unbounded.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestKnapsack(object):\n", + "\n", + " def test_knapsack(self):\n", + " knapsack = Knapsack()\n", + " assert_raises(TypeError, knapsack.fill_knapsack, None, None)\n", + " assert_equal(knapsack.fill_knapsack(0, 0), 0)\n", + " items = []\n", + " items.append(Item(label='a', value=1, weight=1))\n", + " items.append(Item(label='b', value=3, weight=2))\n", + " items.append(Item(label='c', value=7, weight=4))\n", + " total_weight = 8\n", + " expected_value = 14\n", + " results = knapsack.fill_knapsack(items, total_weight)\n", + " total_weight = 7\n", + " expected_value = 11\n", + " results = knapsack.fill_knapsack(items, total_weight)\n", + " assert_equal(results, expected_value)\n", + " print('Success: test_knapsack')\n", + "\n", + "def main():\n", + " test = TestKnapsack()\n", + " test.test_knapsack()\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " main()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Success: test_knapsack\n" + ] + } + ], + "source": [ + "%run -i test_knapsack_unbounded.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.4.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/recursion_dynamic/knapsack_unbounded/test_knapsack_unbounded.py b/recursion_dynamic/knapsack_unbounded/test_knapsack_unbounded.py new file mode 100644 index 0000000..141c7c8 --- /dev/null +++ b/recursion_dynamic/knapsack_unbounded/test_knapsack_unbounded.py @@ -0,0 +1,29 @@ +from nose.tools import assert_equal, assert_raises + + +class TestKnapsack(object): + + def test_knapsack(self): + knapsack = Knapsack() + assert_raises(TypeError, knapsack.fill_knapsack, None, None) + assert_equal(knapsack.fill_knapsack(0, 0), 0) + items = [] + items.append(Item(label='a', value=1, weight=1)) + items.append(Item(label='b', value=3, weight=2)) + items.append(Item(label='c', value=7, weight=4)) + total_weight = 8 + expected_value = 14 + results = knapsack.fill_knapsack(items, total_weight) + total_weight = 7 + expected_value = 11 + results = knapsack.fill_knapsack(items, total_weight) + assert_equal(results, expected_value) + print('Success: test_knapsack') + +def main(): + test = TestKnapsack() + test.test_knapsack() + + +if __name__ == '__main__': + main() \ No newline at end of file