From 3e67ffea39fce21e0cdbec5446bc22ee238657fe Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Mon, 27 Mar 2017 05:13:54 -0400 Subject: [PATCH 01/90] Add anagrams challenge --- sorting_searching/anagrams/__init__.py | 0 .../anagrams/anagrams_challenge.ipynb | 170 ++++++++++++++ .../anagrams/anagrams_solution.ipynb | 220 ++++++++++++++++++ sorting_searching/anagrams/test_anagrams.py | 22 ++ 4 files changed, 412 insertions(+) create mode 100644 sorting_searching/anagrams/__init__.py create mode 100644 sorting_searching/anagrams/anagrams_challenge.ipynb create mode 100644 sorting_searching/anagrams/anagrams_solution.ipynb create mode 100644 sorting_searching/anagrams/test_anagrams.py diff --git a/sorting_searching/anagrams/__init__.py b/sorting_searching/anagrams/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sorting_searching/anagrams/anagrams_challenge.ipynb b/sorting_searching/anagrams/anagrams_challenge.ipynb new file mode 100644 index 0000000..afdb9e7 --- /dev/null +++ b/sorting_searching/anagrams/anagrams_challenge.ipynb @@ -0,0 +1,170 @@ +{ + "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: Sort an array of strings so all anagrams are next to each other.\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 any other sorting requirements other than the grouping of anagrams?\n", + " * No\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 -> Exception\n", + "* [] -> []\n", + "* General case\n", + " * Input: ['ram', 'act', 'arm', 'bat', 'cat', 'tab']\n", + " * Result: ['arm', 'ram', 'act', 'cat', 'bat', 'tab']" + ] + }, + { + "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": [ + "from collections import OrderedDict\n", + "\n", + "\n", + "class Anagram(object):\n", + "\n", + " def group_anagrams(self, items):\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_anagrams.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestAnagrams(object):\n", + "\n", + " def test_group_anagrams(self):\n", + " anagram = Anagram()\n", + " assert_raises(TypeError, anagram.group_anagrams, None)\n", + " data = ['ram', 'act', 'arm', 'bat', 'cat', 'tab']\n", + " expected = ['ram', 'arm', 'act', 'cat', 'bat', 'tab']\n", + " assert_equal(anagram.group_anagrams(data), expected)\n", + "\n", + " print('Success: test_group_anagrams')\n", + "\n", + "\n", + "def main():\n", + " test = TestAnagrams()\n", + " test.test_group_anagrams()\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/sorting_searching/anagrams/anagrams_solution.ipynb b/sorting_searching/anagrams/anagrams_solution.ipynb new file mode 100644 index 0000000..da04897 --- /dev/null +++ b/sorting_searching/anagrams/anagrams_solution.ipynb @@ -0,0 +1,220 @@ +{ + "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: Sort an array of strings so all anagrams are next to each other.\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 any other sorting requirements other than the grouping of anagrams?\n", + " * No\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 -> Exception\n", + "* [] -> []\n", + "* General case\n", + " * Input: ['ram', 'act', 'arm', 'bat', 'cat', 'tab']\n", + " * Result: ['arm', 'ram', 'act', 'cat', 'bat', 'tab']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "
\n",
+    "Input: ['ram', 'act', 'arm', 'bat', 'cat', 'tab']\n",
+    "\n",
+    "Sort the chars for each item:\n",
+    "\n",
+    "'ram' -> 'amr'\n",
+    "'act' -> 'act'\n",
+    "'arm' -> 'amr'\n",
+    "'abt' -> 'bat'\n",
+    "'cat' -> 'act'\n",
+    "'abt' -> 'tab'\n",
+    "\n",
+    "Use a map of sorted chars to each item to group anagrams:\n",
+    "\n",
+    "{\n",
+    "    'amr': ['ram', 'arm'], \n",
+    "    'act': ['act', 'cat'], \n",
+    "    'abt': ['bat', 'tab']\n",
+    "}\n",
+    "\n",
+    "Result: ['arm', 'ram', 'act', 'cat', 'bat', 'tab']\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(k * n), due to the modified bucket sort\n", + "* Space: O(n)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from collections import OrderedDict\n", + "\n", + "\n", + "class Anagram(object):\n", + "\n", + " def group_anagrams(self, items):\n", + " if items is None:\n", + " raise TypeError('items cannot be None')\n", + " if not items:\n", + " return items\n", + " anagram_map = OrderedDict()\n", + " for item in items:\n", + " # Use a tuple, which is hashable and\n", + " # serves as the key in anagram_map\n", + " sorted_chars = tuple(sorted(item))\n", + " if sorted_chars in anagram_map:\n", + " anagram_map[sorted_chars].append(item)\n", + " else:\n", + " anagram_map[sorted_chars] = [item]\n", + " result = []\n", + " for value in anagram_map.values():\n", + " result.extend(value)\n", + " return result" + ] + }, + { + "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_anagrams.py\n" + ] + } + ], + "source": [ + "%%writefile test_anagrams.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestAnagrams(object):\n", + "\n", + " def test_group_anagrams(self):\n", + " anagram = Anagram()\n", + " assert_raises(TypeError, anagram.group_anagrams, None)\n", + " data = ['ram', 'act', 'arm', 'bat', 'cat', 'tab']\n", + " expected = ['ram', 'arm', 'act', 'cat', 'bat', 'tab']\n", + " assert_equal(anagram.group_anagrams(data), expected)\n", + "\n", + " print('Success: test_group_anagrams')\n", + "\n", + "\n", + "def main():\n", + " test = TestAnagrams()\n", + " test.test_group_anagrams()\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_group_anagrams\n" + ] + } + ], + "source": [ + "%run -i test_anagrams.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/sorting_searching/anagrams/test_anagrams.py b/sorting_searching/anagrams/test_anagrams.py new file mode 100644 index 0000000..6395b0d --- /dev/null +++ b/sorting_searching/anagrams/test_anagrams.py @@ -0,0 +1,22 @@ +from nose.tools import assert_equal, assert_raises + + +class TestAnagrams(object): + + def test_group_anagrams(self): + anagram = Anagram() + assert_raises(TypeError, anagram.group_anagrams, None) + data = ['ram', 'act', 'arm', 'bat', 'cat', 'tab'] + expected = ['ram', 'arm', 'act', 'cat', 'bat', 'tab'] + assert_equal(anagram.group_anagrams(data), expected) + + print('Success: test_group_anagrams') + + +def main(): + test = TestAnagrams() + test.test_group_anagrams() + + +if __name__ == '__main__': + main() \ No newline at end of file From 1208e1110fa6ccf8455730329745bfaa80f53be8 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Mon, 27 Mar 2017 05:14:26 -0400 Subject: [PATCH 02/90] Add search sorted matrix challenge --- .../search_sorted_matrix/__init__.py | 0 .../search_sorted_matrix_challenge.ipynb | 180 ++++++++++++++ .../search_sorted_matrix_solution.ipynb | 223 ++++++++++++++++++ .../test_search_sorted_matrix.py | 24 ++ 4 files changed, 427 insertions(+) create mode 100644 sorting_searching/search_sorted_matrix/__init__.py create mode 100644 sorting_searching/search_sorted_matrix/search_sorted_matrix_challenge.ipynb create mode 100644 sorting_searching/search_sorted_matrix/search_sorted_matrix_solution.ipynb create mode 100644 sorting_searching/search_sorted_matrix/test_search_sorted_matrix.py diff --git a/sorting_searching/search_sorted_matrix/__init__.py b/sorting_searching/search_sorted_matrix/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sorting_searching/search_sorted_matrix/search_sorted_matrix_challenge.ipynb b/sorting_searching/search_sorted_matrix/search_sorted_matrix_challenge.ipynb new file mode 100644 index 0000000..1c264ce --- /dev/null +++ b/sorting_searching/search_sorted_matrix/search_sorted_matrix_challenge.ipynb @@ -0,0 +1,180 @@ +{ + "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: Search a sorted matrix for an item.\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 items in each row sorted?\n", + " * Yes\n", + "* Are items in each column sorted?\n", + " * Yes\n", + "* Is the sorting in ascending or descending order?\n", + " * Ascending\n", + "* Is the matrix a rectangle? Not jagged?\n", + " * Yes\n", + "* Is the matrix square?\n", + " * Not necessarily\n", + "* Is the output a tuple (row, col)?\n", + " * Yes\n", + "* Is the item you are searching for always in the matrix?\n", + " * No\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 -> Exception\n", + "* General case\n", + " * Item found -> (row, col)\n", + " * Item not found -> 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 SortedMatrix(object):\n", + "\n", + " def find_val(self, matrix, val):\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_search_sorted_matrix.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSortedMatrix(object):\n", + "\n", + " def test_find_val(self):\n", + " matrix = [[20, 40, 63, 80],\n", + " [30, 50, 80, 90],\n", + " [40, 60, 110, 110],\n", + " [50, 65, 105, 150]]\n", + " sorted_matrix = SortedMatrix()\n", + " assert_raises(TypeError, sorted_matrix.find_val, None, None)\n", + " assert_equal(sorted_matrix.find_val(matrix, 1000), None)\n", + " assert_equal(sorted_matrix.find_val(matrix, 60), (2, 1))\n", + " print('Success: test_find_val')\n", + "\n", + "\n", + "def main():\n", + " test = TestSortedMatrix()\n", + " test.test_find_val()\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/sorting_searching/search_sorted_matrix/search_sorted_matrix_solution.ipynb b/sorting_searching/search_sorted_matrix/search_sorted_matrix_solution.ipynb new file mode 100644 index 0000000..4f25b30 --- /dev/null +++ b/sorting_searching/search_sorted_matrix/search_sorted_matrix_solution.ipynb @@ -0,0 +1,223 @@ +{ + "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: Search a sorted matrix for an item.\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 items in each row sorted?\n", + " * Yes\n", + "* Are items in each column sorted?\n", + " * Yes\n", + "* Is the sorting in ascending or descending order?\n", + " * Ascending\n", + "* Is the matrix a rectangle? Not jagged?\n", + " * Yes\n", + "* Is the matrix square?\n", + " * Not necessarily\n", + "* Is the output a tuple (row, col)?\n", + " * Yes\n", + "* Is the item you are searching for always in the matrix?\n", + " * No\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 -> Exception\n", + "* General case\n", + " * Item found -> (row, col)\n", + " * Item not found -> None" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "
\n",
+    "\n",
+    "Find 60 (val = 60)\n",
+    "\n",
+    " 20  40  63   80\n",
+    " 30  50  80   90\n",
+    " 40  60  100 110\n",
+    " 50  65  105 150\n",
+    "\n",
+    "* If the start of a col > val, look left\n",
+    "* If the end of a col < val, look right\n",
+    "* If the start of row > val, look up\n",
+    "* If the end of a row < val, look down\n",
+    "\n",
+    "If we start at the upper right corner, we just need to use these cases:\n",
+    "\n",
+    "* If the start of a col > val, look left\n",
+    "* If the end of a row < val, look down\n",
+    "\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(n + m), where n and m are the matrix dimensions\n", + "* Space: O(1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class SortedMatrix(object):\n", + "\n", + " def find_val(self, matrix, val):\n", + " if matrix is None or val is None:\n", + " raise TypeError('matrix and val cannot be None')\n", + " row = 0\n", + " col = len(matrix[0]) - 1\n", + " while row < len(matrix) and col >= 0:\n", + " if matrix[row][col] == val:\n", + " return (row, col)\n", + " elif matrix[row][col] > val:\n", + " col -= 1\n", + " else:\n", + " row += 1\n", + " return None" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Unit Test" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Writing test_search_sorted_matrix.py\n" + ] + } + ], + "source": [ + "%%writefile test_search_sorted_matrix.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSortedMatrix(object):\n", + "\n", + " def test_find_val(self):\n", + " matrix = [[20, 40, 63, 80],\n", + " [30, 50, 80, 90],\n", + " [40, 60, 110, 110],\n", + " [50, 65, 105, 150]]\n", + " sorted_matrix = SortedMatrix()\n", + " assert_raises(TypeError, sorted_matrix.find_val, None, None)\n", + " assert_equal(sorted_matrix.find_val(matrix, 1000), None)\n", + " assert_equal(sorted_matrix.find_val(matrix, 60), (2, 1))\n", + " print('Success: test_find_val')\n", + "\n", + "\n", + "def main():\n", + " test = TestSortedMatrix()\n", + " test.test_find_val()\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_find_val\n" + ] + } + ], + "source": [ + "%run -i test_search_sorted_matrix.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/sorting_searching/search_sorted_matrix/test_search_sorted_matrix.py b/sorting_searching/search_sorted_matrix/test_search_sorted_matrix.py new file mode 100644 index 0000000..be1678c --- /dev/null +++ b/sorting_searching/search_sorted_matrix/test_search_sorted_matrix.py @@ -0,0 +1,24 @@ +from nose.tools import assert_equal, assert_raises + + +class TestSortedMatrix(object): + + def test_find_val(self): + matrix = [[20, 40, 63, 80], + [30, 50, 80, 90], + [40, 60, 110, 110], + [50, 65, 105, 150]] + sorted_matrix = SortedMatrix() + assert_raises(TypeError, sorted_matrix.find_val, None, None) + assert_equal(sorted_matrix.find_val(matrix, 1000), None) + assert_equal(sorted_matrix.find_val(matrix, 60), (2, 1)) + print('Success: test_find_val') + + +def main(): + test = TestSortedMatrix() + test.test_find_val() + + +if __name__ == '__main__': + main() \ No newline at end of file From 256755a9628b61069426915f78f98449dc8415ab Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Mon, 27 Mar 2017 05:14:53 -0400 Subject: [PATCH 03/90] Add rotated array search challenge --- .../rotated_array_search/__init__.py | 0 .../rotated_array_search_challenge.ipynb | 175 ++++++++++++ .../rotated_array_search_solution.ipynb | 268 ++++++++++++++++++ .../test_search_sorted_array.py | 24 ++ 4 files changed, 467 insertions(+) create mode 100644 sorting_searching/rotated_array_search/__init__.py create mode 100644 sorting_searching/rotated_array_search/rotated_array_search_challenge.ipynb create mode 100644 sorting_searching/rotated_array_search/rotated_array_search_solution.ipynb create mode 100644 sorting_searching/rotated_array_search/test_search_sorted_array.py diff --git a/sorting_searching/rotated_array_search/__init__.py b/sorting_searching/rotated_array_search/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sorting_searching/rotated_array_search/rotated_array_search_challenge.ipynb b/sorting_searching/rotated_array_search/rotated_array_search_challenge.ipynb new file mode 100644 index 0000000..2ea3ae2 --- /dev/null +++ b/sorting_searching/rotated_array_search/rotated_array_search_challenge.ipynb @@ -0,0 +1,175 @@ +{ + "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: Find an element in a sorted array that has been rotated a number of times.\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", + "* Is the input an array of ints?\n", + " * Yes\n", + "* Do we know how many times the array was rotated?\n", + " * No\n", + "* Was the array originally sorted in increasing or decreasing order?\n", + " * Increasing\n", + "* For the output, do we return the index?\n", + " * Yes\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 -> Exception\n", + "* [] -> None\n", + "* Not found -> None\n", + "* General case with duplicates\n", + "* General case without duplicates" + ] + }, + { + "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 Array(object):\n", + "\n", + " def search_sorted_array(self, array, val):\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_search_sorted_array.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestArray(object):\n", + "\n", + " def test_search_sorted_array(self):\n", + " array = Array()\n", + " assert_raises(TypeError, array.search_sorted_array, None)\n", + " assert_equal(array.search_sorted_array([3, 1, 2], 0), None)\n", + " assert_equal(array.search_sorted_array([3, 1, 2], 0), None)\n", + " data = [10, 12, 14, 1, 3, 5, 6, 7, 8, 9]\n", + " assert_equal(array.search_sorted_array(data, val=1), 3)\n", + " data = [ 1, 1, 2, 1, 1, 1, 1, 1, 1, 1]\n", + " assert_equal(array.search_sorted_array(data, val=2), 2)\n", + " print('Success: test_search_sorted_array')\n", + "\n", + "\n", + "def main():\n", + " test = TestArray()\n", + " test.test_search_sorted_array()\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/sorting_searching/rotated_array_search/rotated_array_search_solution.ipynb b/sorting_searching/rotated_array_search/rotated_array_search_solution.ipynb new file mode 100644 index 0000000..6167b72 --- /dev/null +++ b/sorting_searching/rotated_array_search/rotated_array_search_solution.ipynb @@ -0,0 +1,268 @@ +{ + "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: Find an element in a sorted array that has been rotated a number of times.\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", + "* Is the input an array of ints?\n", + " * Yes\n", + "* Can the input have duplicates?\n", + " * Yes\n", + "* Do we know how many times the array was rotated?\n", + " * No\n", + "* Was the array originally sorted in increasing or decreasing order?\n", + " * Increasing\n", + "* For the output, do we return the index?\n", + " * Yes\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 -> Exception\n", + "* [] -> None\n", + "* Not found -> None\n", + "* General case with duplicates\n", + "* General case without duplicates" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "### General case without dupes\n", + "\n", + "
\n",
+    "\n",
+    "index                   0   1   2   3   4   5   6   7   8   9\n",
+    "input                 [ 1,  3,  5,  6,  7,  8,  9, 10, 12, 14]\n",
+    "input rotated 1x      [10, 12, 14,  1,  3,  5,  6,  7,  8,  9]\n",
+    "input rotated 2x      [ 5,  6,  7,  8,  9, 10, 12, 14,  1,  3]\n",
+    "input rotated 3x      [10, 12, 14,  1,  3,  5,  6,  7,  8,  9]\n",
+    "\n",
+    "find 1\n",
+    "len = 10\n",
+    "mid = 10 // 2 = 5\n",
+    "                        s                   m               e\n",
+    "index                   0   1   2   3   4   5   6   7   8   9\n",
+    "input                 [10, 12, 14,  1,  3,  5,  6,  7,  8,  9]\n",
+    "\n",
+    "input[start] > input[mid]: Left half is rotated\n",
+    "input[end] >= input[mid]: Right half is sorted\n",
+    "1 is not within input[mid+1] to input[end] on the right side, go left\n",
+    "\n",
+    "                        s       m       e\n",
+    "index                   0   1   2   3   4   5   6   7   8   9\n",
+    "input                 [10, 12, 14,  1,  3,  5,  6,  7,  8,  9]\n",
+    "\n",
+    "input[start] <= input[mid]: Right half is rotated\n",
+    "input[end] >= input[mid]: Left half is sorted\n",
+    "1 is not within input[left] to input[mid-1] on the left side, go right\n",
+    "\n",
+    "
\n", + "\n", + "### General case with dupes\n", + "\n", + "
\n",
+    "\n",
+    "                        s                   m               e\n",
+    "index                   0   1   2   3   4   5   6   7   8   9\n",
+    "input                 [ 1,  1,  1,  1,  1,  1,  1,  1,  1,  2]\n",
+    "\n",
+    "input[start] == input[mid], input[mid] != input[end], go right\n",
+    "\n",
+    "input rotated 1x      [ 1,  1,  2,  1,  1,  1,  1,  1,  1,  1]\n",
+    "\n",
+    "input[start] == input[mid] == input[end], search both sides\n",
+    "\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(log n) if there are no duplicates, else O(n)\n", + "* Space: O(m), where m is the recursion depth" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Array(object):\n", + "\n", + " def search_sorted_array(self, array, val):\n", + " if array is None or val is None:\n", + " raise TypeError('array or val cannot be None')\n", + " if not array:\n", + " return None\n", + " return self._search_sorted_array(array, val, start=0, end=len(array) - 1)\n", + "\n", + " def _search_sorted_array(self, array, val, start, end):\n", + " if end < start:\n", + " return None\n", + " mid = (start + end) // 2\n", + " if array[mid] == val:\n", + " return mid\n", + " # Left side is sorted\n", + " if array[start] < array[mid]:\n", + " if array[start] <= val < array[mid]:\n", + " return self._search_sorted_array(array, val, start, mid - 1)\n", + " else:\n", + " return self._search_sorted_array(array, val, mid + 1, end)\n", + " # Right side is sorted\n", + " elif array[start] > array[mid]:\n", + " if array[mid] < val <= array[end]:\n", + " return self._search_sorted_array(array, val, mid + 1, end)\n", + " else:\n", + " return self._search_sorted_array(array, val, start, mid - 1)\n", + " # Duplicates\n", + " else:\n", + " if array[mid] != array[end]:\n", + " return self._search_sorted_array(array, val, mid + 1, end)\n", + " else:\n", + " result = self._search_sorted_array(array, val, start, mid - 1)\n", + " if result != None:\n", + " return result\n", + " else:\n", + " return self._search_sorted_array(array, val, mid + 1, end)" + ] + }, + { + "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_search_sorted_array.py\n" + ] + } + ], + "source": [ + "%%writefile test_search_sorted_array.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestArray(object):\n", + "\n", + " def test_search_sorted_array(self):\n", + " array = Array()\n", + " assert_raises(TypeError, array.search_sorted_array, None)\n", + " assert_equal(array.search_sorted_array([3, 1, 2], 0), None)\n", + " assert_equal(array.search_sorted_array([3, 1, 2], 0), None)\n", + " data = [10, 12, 14, 1, 3, 5, 6, 7, 8, 9]\n", + " assert_equal(array.search_sorted_array(data, val=1), 3)\n", + " data = [ 1, 1, 2, 1, 1, 1, 1, 1, 1, 1]\n", + " assert_equal(array.search_sorted_array(data, val=2), 2)\n", + " print('Success: test_search_sorted_array')\n", + "\n", + "\n", + "def main():\n", + " test = TestArray()\n", + " test.test_search_sorted_array()\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_search_sorted_array\n" + ] + } + ], + "source": [ + "%run -i test_search_sorted_array.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/sorting_searching/rotated_array_search/test_search_sorted_array.py b/sorting_searching/rotated_array_search/test_search_sorted_array.py new file mode 100644 index 0000000..cf5229a --- /dev/null +++ b/sorting_searching/rotated_array_search/test_search_sorted_array.py @@ -0,0 +1,24 @@ +from nose.tools import assert_equal, assert_raises + + +class TestArray(object): + + def test_search_sorted_array(self): + array = Array() + assert_raises(TypeError, array.search_sorted_array, None) + assert_equal(array.search_sorted_array([3, 1, 2], 0), None) + assert_equal(array.search_sorted_array([3, 1, 2], 0), None) + data = [10, 12, 14, 1, 3, 5, 6, 7, 8, 9] + assert_equal(array.search_sorted_array(data, val=1), 3) + data = [ 1, 1, 2, 1, 1, 1, 1, 1, 1, 1] + assert_equal(array.search_sorted_array(data, val=2), 2) + print('Success: test_search_sorted_array') + + +def main(): + test = TestArray() + test.test_search_sorted_array() + + +if __name__ == '__main__': + main() \ No newline at end of file From c915ea69f55ec9a9aa5f0638704c93a26ad8ad5d Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Mon, 27 Mar 2017 05:15:24 -0400 Subject: [PATCH 04/90] Add radix sort challenge --- sorting_searching/radix_sort/__init__.py | 0 .../radix_sort/radix_sort_challenge.ipynb | 170 +++++++++++++ .../radix_sort/radix_sort_solution.ipynb | 232 ++++++++++++++++++ .../radix_sort/test_radix_sort.py | 22 ++ 4 files changed, 424 insertions(+) create mode 100644 sorting_searching/radix_sort/__init__.py create mode 100644 sorting_searching/radix_sort/radix_sort_challenge.ipynb create mode 100644 sorting_searching/radix_sort/radix_sort_solution.ipynb create mode 100644 sorting_searching/radix_sort/test_radix_sort.py diff --git a/sorting_searching/radix_sort/__init__.py b/sorting_searching/radix_sort/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sorting_searching/radix_sort/radix_sort_challenge.ipynb b/sorting_searching/radix_sort/radix_sort_challenge.ipynb new file mode 100644 index 0000000..be3b216 --- /dev/null +++ b/sorting_searching/radix_sort/radix_sort_challenge.ipynb @@ -0,0 +1,170 @@ +{ + "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 radix sort.\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", + "* Is the input a list?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * Check for None in place of an array\n", + " * Assume array elements are ints\n", + "* Do we know the max digits to handle?\n", + " * No\n", + "* Are the digits base 10?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> Exception\n", + "* [] -> []\n", + "* [128, 256, 164, 8, 2, 148, 212, 242, 244] -> [2, 8, 128, 148, 164, 212, 242, 244, 256]" + ] + }, + { + "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 RadixSort(object):\n", + "\n", + " def sort(self, array, base=10):\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_radix_sort.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestRadixSort(object):\n", + "\n", + " def test_sort(self):\n", + " radix_sort = RadixSort()\n", + " assert_raises(TypeError, radix_sort.sort, None)\n", + " assert_equal(radix_sort.sort([]), [])\n", + " array = [128, 256, 164, 8, 2, 148, 212, 242, 244]\n", + " expected = [2, 8, 128, 148, 164, 212, 242, 244, 256]\n", + " assert_equal(radix_sort.sort(array), expected)\n", + " print('Success: test_sort')\n", + "\n", + "\n", + "def main():\n", + " test = TestRadixSort()\n", + " test.test_sort()\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/sorting_searching/radix_sort/radix_sort_solution.ipynb b/sorting_searching/radix_sort/radix_sort_solution.ipynb new file mode 100644 index 0000000..96d8827 --- /dev/null +++ b/sorting_searching/radix_sort/radix_sort_solution.ipynb @@ -0,0 +1,232 @@ +{ + "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 radix sort.\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", + "* Is the input a list?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * Check for None in place of an array\n", + " * Assume array elements are ints\n", + "* Do we know the max digits to handle?\n", + " * No\n", + "* Are the digits base 10?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> Exception\n", + "* [] -> []\n", + "* [128, 256, 164, 8, 2, 148, 212, 242, 244] -> [2, 8, 128, 148, 164, 212, 242, 244, 256]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "Sample input: [1, 220, 122, 112]\n", + "\n", + "* We'll evaluate each digit starting with the ones position\n", + " * [**1**, 22**0**, 12**2**, 11**2**]\n", + " * Bucket 0: 220\n", + " * Bucket 1: 1\n", + " * Bucket 2: 122, 112\n", + " * Result: [220, 1, 122, 112]\n", + " * [2**2**0, 1, 1**2**2, 1**1**2]\n", + " * Bucket 0: 1\n", + " * Bucket 1: 112\n", + " * Bucket 2: 220, 122\n", + " * Result: [1, 112, 220, 122]\n", + " * [1, **1**12, **2**20, **1**22]\n", + " * Bucket 0: 1\n", + " * Bucket 1: 112, 122\n", + " * Bucket 2: 220\n", + " * Result: [1, 112, 122, 220]\n", + "\n", + "Bucketing example: 123\n", + "\n", + "* Ones\n", + " * 12**3** // 10^0 = 123\n", + " * 123 % 10 = 3\n", + "* Tens\n", + " * 1**2**3 // 10^1 = 12\n", + " * 12 % 10 = 2\n", + "* Hundreds\n", + " * **1**23 // 10^2 = 1\n", + " * 1 % 10 = 1\n", + "\n", + "Complexity:\n", + "* Time: O(k*n), where n is the number of items and k is the number of digits in the largest item\n", + "* Space: O(k+n)\n", + "\n", + "Misc:\n", + "* Not in-place\n", + "* Most implementations are stable\n", + "\n", + "If k (the number of digits) is less than log(n), radix sort can be faster than algorithms such as quicksort." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class RadixSort(object):\n", + "\n", + " def sort(self, array, base=10):\n", + " if array is None:\n", + " raise TypeError('array cannot be None')\n", + " if not array:\n", + " return []\n", + " max_element = max(array)\n", + " max_digits = len(str(abs(max_element)))\n", + " curr_array = array\n", + " for digit in range(max_digits):\n", + " buckets = [[] for _ in range(base)]\n", + " for item in curr_array:\n", + " buckets[(item//(base**digit))%base].append(item)\n", + " curr_array = []\n", + " for bucket in buckets:\n", + " curr_array.extend(bucket)\n", + " return curr_array" + ] + }, + { + "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_radix_sort.py\n" + ] + } + ], + "source": [ + "%%writefile test_radix_sort.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestRadixSort(object):\n", + "\n", + " def test_sort(self):\n", + " radix_sort = RadixSort()\n", + " assert_raises(TypeError, radix_sort.sort, None)\n", + " assert_equal(radix_sort.sort([]), [])\n", + " array = [128, 256, 164, 8, 2, 148, 212, 242, 244]\n", + " expected = [2, 8, 128, 148, 164, 212, 242, 244, 256]\n", + " assert_equal(radix_sort.sort(array), expected)\n", + " print('Success: test_sort')\n", + "\n", + "\n", + "def main():\n", + " test = TestRadixSort()\n", + " test.test_sort()\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_sort\n" + ] + } + ], + "source": [ + "%run -i test_radix_sort.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/sorting_searching/radix_sort/test_radix_sort.py b/sorting_searching/radix_sort/test_radix_sort.py new file mode 100644 index 0000000..0dbbed2 --- /dev/null +++ b/sorting_searching/radix_sort/test_radix_sort.py @@ -0,0 +1,22 @@ +from nose.tools import assert_equal, assert_raises + + +class TestRadixSort(object): + + def test_sort(self): + radix_sort = RadixSort() + assert_raises(TypeError, radix_sort.sort, None) + assert_equal(radix_sort.sort([]), []) + array = [128, 256, 164, 8, 2, 148, 212, 242, 244] + expected = [2, 8, 128, 148, 164, 212, 242, 244, 256] + assert_equal(radix_sort.sort(array), expected) + print('Success: test_sort') + + +def main(): + test = TestRadixSort() + test.test_sort() + + +if __name__ == '__main__': + main() \ No newline at end of file From d9edf2fa1507dd0687c3c004dd3e056bfc894c69 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Mon, 27 Mar 2017 05:15:40 -0400 Subject: [PATCH 05/90] Add new int challenge --- sorting_searching/new_int/__init__.py | 0 .../new_int/new_int_challenge.ipynb | 174 ++++++++++++++ .../new_int/new_int_solution.ipynb | 214 ++++++++++++++++++ sorting_searching/new_int/test_new_int.py | 25 ++ 4 files changed, 413 insertions(+) create mode 100644 sorting_searching/new_int/__init__.py create mode 100644 sorting_searching/new_int/new_int_challenge.ipynb create mode 100644 sorting_searching/new_int/new_int_solution.ipynb create mode 100644 sorting_searching/new_int/test_new_int.py diff --git a/sorting_searching/new_int/__init__.py b/sorting_searching/new_int/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sorting_searching/new_int/new_int_challenge.ipynb b/sorting_searching/new_int/new_int_challenge.ipynb new file mode 100644 index 0000000..31fb42f --- /dev/null +++ b/sorting_searching/new_int/new_int_challenge.ipynb @@ -0,0 +1,174 @@ +{ + "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 an array of 32 integers, find an int not in the input. Use a minimal amount of memory.\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 we working with non-negative ints?\n", + " * Yes\n", + "* What is the range of the integers?\n", + " * Discuss the approach for 4 billion integers\n", + " * Implement for 32 integers\n", + "* Can we assume the inputs are valid?\n", + " * No" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> Exception\n", + "* [] -> Exception\n", + "* General case\n", + " * There is an int excluded from the input -> int\n", + " * There isn't an int excluded from the input -> 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": [ + "from bitstring import BitArray # run pip install bitstring\n", + "\n", + "\n", + "class Bits(object):\n", + "\n", + " def new_int(self, array, max_size):\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_new_int.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestBits(object):\n", + "\n", + " def test_new_int(self):\n", + " bits = Bits()\n", + " max_size = 32\n", + " assert_raises(TypeError, bits.new_int, None, max_size)\n", + " assert_raises(TypeError, bits.new_int, [], max_size)\n", + " data = [item for item in range(30)]\n", + " data.append(31)\n", + " assert_equal(bits.new_int(data, max_size), 30)\n", + " data = [item for item in range(32)]\n", + " assert_equal(bits.new_int(data, max_size), None)\n", + " print('Success: test_find_int_excluded_from_input')\n", + "\n", + "\n", + "def main():\n", + " test = TestBits()\n", + " test.test_new_int()\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.4.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/sorting_searching/new_int/new_int_solution.ipynb b/sorting_searching/new_int/new_int_solution.ipynb new file mode 100644 index 0000000..0832aa1 --- /dev/null +++ b/sorting_searching/new_int/new_int_solution.ipynb @@ -0,0 +1,214 @@ +{ + "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 an array of n integers, find an int not in the input. Use a minimal amount of memory.\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 we working with non-negative ints?\n", + " * Yes\n", + "* What is the range of the integers?\n", + " * Discuss the approach for 4 billion integers\n", + " * Implement for 32 integers\n", + "* Can we assume the inputs are valid?\n", + " * No" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> Exception\n", + "* [] -> Exception\n", + "* General case\n", + " * There is an int excluded from the input -> int\n", + " * There isn't an int excluded from the input -> None" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "The problem states to use a minimal amount of memory. We'll use a bit vector to keep track of the inputs.\n", + "\n", + "Say we are given 4 billion integers, which is 2^32 integers. The number of non-negative integers would be 2^31. With a bit vector, we'll need 4 billion bits to map each integer to a bit. Say we had only 1 GB of memory or 2^32 bytes. This would leave us with 8 billion bits.\n", + "\n", + "To simplify this exercise, we'll work with an input of up to 32 ints that we'll map to a bit vector of 32 bits.\n", + "\n", + "
\n",
+    "\n",
+    "input = [0, 1, 2, 3, 4...28, 29, 31]\n",
+    "\n",
+    "bytes          [         1          ]  [          2         ] [          3          ] [          4          ]\n",
+    "index       =  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31\n",
+    "bit_vector  =  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  0  1\n",
+    "\n",
+    "result = 30\n",
+    "\n",
+    "* Loop through each item in the input, setting bit_vector[item] = True.\n",
+    "* Loop through the bit_vector, return the first index where bit_vector[item] == False.\n",
+    "\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(b), where b is the number of bits\n", + "* Space: O(b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from bitstring import BitArray # Run pip install bitstring\n", + "\n", + "\n", + "class Bits(object):\n", + "\n", + " def new_int(self, array, max_size):\n", + " if not array:\n", + " raise TypeError('array cannot be None or empty')\n", + " bit_vector = BitArray(max_size)\n", + " for item in array:\n", + " bit_vector[item] = True\n", + " for index, item in enumerate(bit_vector):\n", + " if not item:\n", + " return index\n", + " return None" + ] + }, + { + "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_new_int.py\n" + ] + } + ], + "source": [ + "%%writefile test_new_int.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestBits(object):\n", + "\n", + " def test_new_int(self):\n", + " bits = Bits()\n", + " max_size = 32\n", + " assert_raises(TypeError, bits.new_int, None, max_size)\n", + " assert_raises(TypeError, bits.new_int, [], max_size)\n", + " data = [item for item in range(30)]\n", + " data.append(31)\n", + " assert_equal(bits.new_int(data, max_size), 30)\n", + " data = [item for item in range(32)]\n", + " assert_equal(bits.new_int(data, max_size), None)\n", + " print('Success: test_find_int_excluded_from_input')\n", + "\n", + "\n", + "def main():\n", + " test = TestBits()\n", + " test.test_new_int()\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_find_int_excluded_from_input\n" + ] + } + ], + "source": [ + "%run -i test_new_int.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/sorting_searching/new_int/test_new_int.py b/sorting_searching/new_int/test_new_int.py new file mode 100644 index 0000000..39c0eda --- /dev/null +++ b/sorting_searching/new_int/test_new_int.py @@ -0,0 +1,25 @@ +from nose.tools import assert_equal, assert_raises + + +class TestBits(object): + + def test_new_int(self): + bits = Bits() + max_size = 32 + assert_raises(TypeError, bits.new_int, None, max_size) + assert_raises(TypeError, bits.new_int, [], max_size) + data = [item for item in range(30)] + data.append(31) + assert_equal(bits.new_int(data, max_size), 30) + data = [item for item in range(32)] + assert_equal(bits.new_int(data, max_size), None) + print('Success: test_find_int_excluded_from_input') + + +def main(): + test = TestBits() + test.test_new_int() + + +if __name__ == '__main__': + main() \ No newline at end of file From abc982d360fef3672b84bad908f1300fa63c119a Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Mon, 27 Mar 2017 05:15:51 -0400 Subject: [PATCH 06/90] Add merge into challenge --- sorting_searching/merge_into/__init__.py | 0 .../merge_into/merge_into_challenge.ipynb | 178 +++++++++++ .../merge_into/merge_into_solution.ipynb | 276 ++++++++++++++++++ .../merge_into/test_merge_into.py | 27 ++ 4 files changed, 481 insertions(+) create mode 100644 sorting_searching/merge_into/__init__.py create mode 100644 sorting_searching/merge_into/merge_into_challenge.ipynb create mode 100644 sorting_searching/merge_into/merge_into_solution.ipynb create mode 100644 sorting_searching/merge_into/test_merge_into.py diff --git a/sorting_searching/merge_into/__init__.py b/sorting_searching/merge_into/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sorting_searching/merge_into/merge_into_challenge.ipynb b/sorting_searching/merge_into/merge_into_challenge.ipynb new file mode 100644 index 0000000..63fdd95 --- /dev/null +++ b/sorting_searching/merge_into/merge_into_challenge.ipynb @@ -0,0 +1,178 @@ +{ + "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 sorted arrays A, B, merge B into A in sorted order.\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", + "* Does A have enough space for B?\n", + " * Yes\n", + "* Can the inputs have duplicate array items?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * No\n", + "* Does the inputs also include the actual size of A and B?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* A or B is None -> Exception\n", + "* index of last A or B < 0 -> Exception\n", + "* A or B is empty\n", + "* General case\n", + " * A = [1, 3, 5, 7, 9, None, None, None]\n", + " * B = [4, 5, 6]\n", + " * A = [1, 3, 4, 5, 5, 6, 7, 9]" + ] + }, + { + "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 Array(object):\n", + "\n", + " def merge_into(self, source, dest, source_end_index, dest_end_index):\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_merge_into.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestArray(object):\n", + "\n", + " def test_merge_into(self):\n", + " array = Array()\n", + " assert_raises(TypeError, array.merge_into, None, None, None, None)\n", + " assert_raises(ValueError, array.merge_into, [1], [2], -1, -1)\n", + " a = [1, 2, 3]\n", + " assert_equal(array.merge_into(a, [], len(a), 0), [1, 2, 3])\n", + " a = [1, 2, 3]\n", + " assert_equal(array.merge_into(a, [], len(a), 0), [1, 2, 3])\n", + " a = [1, 3, 5, 7, 9, None, None, None]\n", + " b = [4, 5, 6]\n", + " expected = [1, 3, 4, 5, 5, 6, 7, 9]\n", + " assert_equal(array.merge_into(a, b, 5, len(b)), expected)\n", + " print('Success: test_merge_into')\n", + "\n", + "\n", + "def main():\n", + " test = TestArray()\n", + " test.test_merge_into()\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/sorting_searching/merge_into/merge_into_solution.ipynb b/sorting_searching/merge_into/merge_into_solution.ipynb new file mode 100644 index 0000000..2df970e --- /dev/null +++ b/sorting_searching/merge_into/merge_into_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: Given sorted arrays A, B, merge B into A in sorted order.\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", + "* Does A have enough space for B?\n", + " * Yes\n", + "* Can the inputs have duplicate array items?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * No\n", + "* Does the inputs also include the actual size of A and B?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* A or B is None -> Exception\n", + "* index of last A or B < 0 -> Exception\n", + "* A or B is empty\n", + "* General case\n", + " * A = [1, 3, 5, 7, 9, None, None, None]\n", + " * B = [4, 5, 6]\n", + " * A = [1, 3, 4, 5, 5, 6, 7, 9]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "
\n",
+    "                     i                 k\n",
+    "A = [1,  3,  5,  7,  9,  None,  None,  None]\n",
+    "             j\n",
+    "B = [4,  5,  6]\n",
+    "\n",
+    "---\n",
+    "\n",
+    "A[k] = max(A[i], B[j])\n",
+    "                     i                 k\n",
+    "A = [1,  3,  5,  7,  9,  None,  None,  9]\n",
+    "             j\n",
+    "B = [4,  5,  6]\n",
+    "\n",
+    "---\n",
+    "\n",
+    "A[k] = max(A[i], B[j])\n",
+    "                 i              k       \n",
+    "A = [1,  3,  5,  7,  9,  None,  7,  9]\n",
+    "             j\n",
+    "B = [4,  5,  6]\n",
+    "\n",
+    "---\n",
+    "\n",
+    "A[k] = max(A[i], B[j])\n",
+    "             i           k              \n",
+    "A = [1,  3,  5,  7,  9,  6,  7,  9]\n",
+    "             j\n",
+    "B = [4,  5,  6]\n",
+    "\n",
+    "---\n",
+    "\n",
+    "A[k] = max(A[i], B[j])\n",
+    "             i       k                  \n",
+    "A = [1,  3,  5,  7,  5,  6,  7,  9]\n",
+    "         j    \n",
+    "B = [4,  5,  6]\n",
+    "\n",
+    "---\n",
+    "\n",
+    "A[k] = max(A[i], B[j])\n",
+    "         i       k                      \n",
+    "A = [1,  3,  5,  5,  5,  6,  7,  9]\n",
+    "         j    \n",
+    "B = [4,  5,  6]\n",
+    "\n",
+    "---\n",
+    "\n",
+    "A[k] = max(A[i], B[j])\n",
+    "         i   k                          \n",
+    "A = [1,  3,  4,  5,  5,  6,  7,  9]\n",
+    "     j        \n",
+    "B = [4,  5,  6]\n",
+    "\n",
+    "---\n",
+    "\n",
+    "A[k] = max(A[i], B[j])\n",
+    "        ik                              \n",
+    "A = [1,  3,  4,  5,  5,  6,  7,  9]\n",
+    "             \n",
+    "B = [4,  5,  6]\n",
+    "\n",
+    "---\n",
+    "\n",
+    "A = [1, 3, 4, 5, 5, 6, 7, 9]\n",
+    "\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(m + n)\n", + "* Space: O(1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Array(object):\n", + "\n", + " def merge_into(self, source, dest, source_end_index, dest_end_index):\n", + " if source is None or dest is None:\n", + " raise TypeError('source or dest cannot be None')\n", + " if source_end_index < 0 or dest_end_index < 0:\n", + " raise ValueError('end indices must be >= 0')\n", + " if not source:\n", + " return dest\n", + " if not dest:\n", + " return source\n", + " source_index = source_end_index - 1\n", + " dest_index = dest_end_index - 1\n", + " insert_index = source_end_index + dest_end_index - 1\n", + " while dest_index >= 0:\n", + " if source[source_index] > dest[dest_index]:\n", + " source[insert_index] = source[source_index]\n", + " source_index -= 1\n", + " else:\n", + " source[insert_index] = dest[dest_index]\n", + " dest_index -= 1\n", + " insert_index -= 1\n", + " return source" + ] + }, + { + "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_merge_into.py\n" + ] + } + ], + "source": [ + "%%writefile test_merge_into.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestArray(object):\n", + "\n", + " def test_merge_into(self):\n", + " array = Array()\n", + " assert_raises(TypeError, array.merge_into, None, None, None, None)\n", + " assert_raises(ValueError, array.merge_into, [1], [2], -1, -1)\n", + " a = [1, 2, 3]\n", + " assert_equal(array.merge_into(a, [], len(a), 0), [1, 2, 3])\n", + " a = [1, 2, 3]\n", + " assert_equal(array.merge_into(a, [], len(a), 0), [1, 2, 3])\n", + " a = [1, 3, 5, 7, 9, None, None, None]\n", + " b = [4, 5, 6]\n", + " expected = [1, 3, 4, 5, 5, 6, 7, 9]\n", + " assert_equal(array.merge_into(a, b, 5, len(b)), expected)\n", + " print('Success: test_merge_into')\n", + "\n", + "\n", + "def main():\n", + " test = TestArray()\n", + " test.test_merge_into()\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_merge_into\n" + ] + } + ], + "source": [ + "%run -i test_merge_into.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/sorting_searching/merge_into/test_merge_into.py b/sorting_searching/merge_into/test_merge_into.py new file mode 100644 index 0000000..b0e7d84 --- /dev/null +++ b/sorting_searching/merge_into/test_merge_into.py @@ -0,0 +1,27 @@ +from nose.tools import assert_equal, assert_raises + + +class TestArray(object): + + def test_merge_into(self): + array = Array() + assert_raises(TypeError, array.merge_into, None, None, None, None) + assert_raises(ValueError, array.merge_into, [1], [2], -1, -1) + a = [1, 2, 3] + assert_equal(array.merge_into(a, [], len(a), 0), [1, 2, 3]) + a = [1, 2, 3] + assert_equal(array.merge_into(a, [], len(a), 0), [1, 2, 3]) + a = [1, 3, 5, 7, 9, None, None, None] + b = [4, 5, 6] + expected = [1, 3, 4, 5, 5, 6, 7, 9] + assert_equal(array.merge_into(a, b, 5, len(b)), expected) + print('Success: test_merge_into') + + +def main(): + test = TestArray() + test.test_merge_into() + + +if __name__ == '__main__': + main() \ No newline at end of file From a0afa5c9d9148b2c4846afb78422811f0f908be4 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Mon, 27 Mar 2017 05:19:37 -0400 Subject: [PATCH 07/90] 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 From ba8325329aea4a1a29d2a1b4edf6ece13112bceb Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Mon, 27 Mar 2017 05:20:27 -0400 Subject: [PATCH 08/90] Add power set challenge --- recursion_dynamic/power_set/__init__.py | 0 .../power_set/power_set_challenge.ipynb | 202 +++++++++++++ .../power_set/power_set_solution.ipynb | 270 ++++++++++++++++++ recursion_dynamic/power_set/test_power_set.py | 39 +++ 4 files changed, 511 insertions(+) create mode 100644 recursion_dynamic/power_set/__init__.py create mode 100644 recursion_dynamic/power_set/power_set_challenge.ipynb create mode 100644 recursion_dynamic/power_set/power_set_solution.ipynb create mode 100644 recursion_dynamic/power_set/test_power_set.py diff --git a/recursion_dynamic/power_set/__init__.py b/recursion_dynamic/power_set/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/recursion_dynamic/power_set/power_set_challenge.ipynb b/recursion_dynamic/power_set/power_set_challenge.ipynb new file mode 100644 index 0000000..907b56c --- /dev/null +++ b/recursion_dynamic/power_set/power_set_challenge.ipynb @@ -0,0 +1,202 @@ +{ + "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: Return all subsets of a set.\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", + "* Should the resulting subsets be unique?\n", + " * Yes, treat 'ab' and 'bc' as the same\n", + "* Is the empty set included as a subset?\n", + " * Yes\n", + "* Are the inputs unique?\n", + " * No\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",
+    "* None -> None\n",
+    "* [] -> [[]]\n",
+    "* ['a'] -> [[], \n",
+    "            ['a']]\n",
+    "* ['a', 'b'] -> [[], \n",
+    "                 ['a'], \n",
+    "                 ['b'], \n",
+    "                 ['a', 'b']]\n",
+    "* ['a', 'b', 'c'] -> [[], \n",
+    "                      ['a'], \n",
+    "                      ['b'], \n",
+    "                      ['c'],\n",
+    "                      ['a', 'b'], \n",
+    "                      ['a', 'c'], \n",
+    "                      ['b', 'c'],\n",
+    "                      ['a', 'b', 'c']]\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": false + }, + "outputs": [], + "source": [ + "class Sets(object):\n", + "\n", + " def find_power_set_recursive(self, input_set):\n", + " # TODO: Implement me\n", + " pass\n", + "\n", + " def find_power_set_iterative(self, input_set):\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_power_set.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestPowerSet(object):\n", + "\n", + " def test_power_set(self):\n", + " input_set = []\n", + " expected = [[]]\n", + " self.run_test(input_set, expected)\n", + " input_set = ['a']\n", + " expected = [['a'], []]\n", + " self.run_test(input_set, expected)\n", + " input_set = ['a', 'b']\n", + " expected = [['a'], ['a', 'b'], ['b'], []]\n", + " self.run_test(input_set, expected)\n", + " input_set = ['a', 'b', 'c']\n", + " expected = [['a'], ['a', 'b'], ['b'], ['a', 'c'], \n", + " ['a', 'b', 'c'], ['b', 'c'], ['c'], []]\n", + " self.run_test(input_set, expected)\n", + " print('Success: test_power_set')\n", + "\n", + " def run_test(self, input_set, expected):\n", + " combinatoric = Combinatoric()\n", + " result = combinatoric.find_power_set_recursive(input_set)\n", + " assert_equal(result, expected)\n", + " result = combinatoric.find_power_set_iterative(input_set)\n", + " assert_equal(result, expected)\n", + "\n", + "\n", + "def main():\n", + " test = TestPowerSet()\n", + " test.test_power_set()\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/power_set/power_set_solution.ipynb b/recursion_dynamic/power_set/power_set_solution.ipynb new file mode 100644 index 0000000..451ce1b --- /dev/null +++ b/recursion_dynamic/power_set/power_set_solution.ipynb @@ -0,0 +1,270 @@ +{ + "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: Return all subsets of a set.\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", + "* Should the resulting subsets be unique?\n", + " * Yes, treat 'ab' and 'bc' as the same\n", + "* Is the empty set included as a subset?\n", + " * Yes\n", + "* Are the inputs unique?\n", + " * No\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",
+    "* None -> None\n",
+    "* '' -> ['']\n",
+    "* 'a' -> ['a', '']\n",
+    "* 'ab' -> ['a', 'ab', 'b', '']\n",
+    "* 'abc' -> ['a', 'ab', 'abc', 'ac',\n",
+    "            'b', 'bc', 'c', '']\n",
+    "* 'aabc' -> ['a', 'aa', 'aab', 'aabc', \n",
+    "             'aac', 'ab', 'abc', 'ac', \n",
+    "             'b', 'bc', 'c', '']\n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "* Build a dictionary of {chars: counts} where counts is the number of times each char is found in the input\n", + "* Loop through each item in the dictionary\n", + " * Keep track of the current index (first item will have current index 0)\n", + " * If the char's count is 0, continue\n", + " * Decrement the current char's count in the dictionary\n", + " * Add the current char to the current results\n", + " * Add the current result to the results\n", + " * Recurse, passing in the current index as the new starting point index\n", + " * When we recurse, we'll check if current index < starting point index, and if so, continue\n", + " * This avoids duplicate results such as 'ab' and 'bc'\n", + " * Backtrack by:\n", + " * Removing the just added current char from the current results\n", + " * Incrementing the current char's acount in the dictionary\n", + "\n", + "Complexity:\n", + "* Time: O(2^n)\n", + "* Space: O(2^n) if we are saving each result, or O(n) if we are just printing each result\n", + "\n", + "We are doubling the number of operations every time we add an element to the results: O(2^n).\n", + "\n", + "Note, you could also use the following method to solve this problem:\n", + "\n", + "
\n",
+    "number binary  subset\n",
+    "0      000      {}\n",
+    "1      001      {c}\n",
+    "2      010      {b}\n",
+    "3      011      {b,c}\n",
+    "4      100      {a}\n",
+    "5      101      {a,c}\n",
+    "6      110      {a,b}\n",
+    "7      111      {a,b,c}\n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from collections import OrderedDict\n", + "\n", + "\n", + "class Combinatoric(object):\n", + "\n", + " def _build_counts_map(self, string):\n", + " counts_map = OrderedDict()\n", + " for char in string:\n", + " if char in counts_map:\n", + " counts_map[char] += 1\n", + " else:\n", + " counts_map[char] = 1\n", + " return counts_map\n", + "\n", + " def find_power_set(self, string):\n", + " if string is None:\n", + " return string\n", + " if string == '':\n", + " return ['']\n", + " counts_map = self._build_counts_map(string)\n", + " curr_results = []\n", + " results = []\n", + " self._find_power_set(counts_map, curr_results,\n", + " results, index=0)\n", + " results.append('')\n", + " return results\n", + "\n", + " def _find_power_set(self, counts_map, curr_result,\n", + " results, index):\n", + " for curr_index, char in enumerate(counts_map):\n", + " if curr_index < index or counts_map[char] == 0:\n", + " continue\n", + " curr_result.append(char)\n", + " counts_map[char] -= 1\n", + " results.append(''.join(curr_result))\n", + " self._find_power_set(counts_map, curr_result,\n", + " results, curr_index)\n", + " counts_map[char] += 1\n", + " curr_result.pop()" + ] + }, + { + "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_power_set.py\n" + ] + } + ], + "source": [ + "%%writefile test_power_set.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestPowerSet(object):\n", + "\n", + " def test_power_set(self):\n", + " input_set = ''\n", + " expected = ['']\n", + " self.run_test(input_set, expected)\n", + " input_set = 'a'\n", + " expected = ['a', '']\n", + " self.run_test(input_set, expected)\n", + " input_set = 'ab'\n", + " expected = ['a', 'ab', 'b', '']\n", + " self.run_test(input_set, expected)\n", + " input_set = 'abc'\n", + " expected = ['a', 'ab', 'abc', 'ac',\n", + " 'b', 'bc', 'c', '']\n", + " self.run_test(input_set, expected)\n", + " input_set = 'aabc'\n", + " expected = ['a', 'aa', 'aab', 'aabc', \n", + " 'aac', 'ab', 'abc', 'ac', \n", + " 'b', 'bc', 'c', '']\n", + " self.run_test(input_set, expected)\n", + " print('Success: test_power_set')\n", + "\n", + " def run_test(self, input_set, expected):\n", + " combinatoric = Combinatoric()\n", + " result = combinatoric.find_power_set(input_set)\n", + " assert_equal(result, expected)\n", + "\n", + "\n", + "def main():\n", + " test = TestPowerSet()\n", + " test.test_power_set()\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_power_set\n" + ] + } + ], + "source": [ + "%run -i test_power_set.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/power_set/test_power_set.py b/recursion_dynamic/power_set/test_power_set.py new file mode 100644 index 0000000..e64b59e --- /dev/null +++ b/recursion_dynamic/power_set/test_power_set.py @@ -0,0 +1,39 @@ +from nose.tools import assert_equal + + +class TestPowerSet(object): + + def test_power_set(self): + input_set = '' + expected = [''] + self.run_test(input_set, expected) + input_set = 'a' + expected = ['a', ''] + self.run_test(input_set, expected) + input_set = 'ab' + expected = ['a', 'ab', 'b', ''] + self.run_test(input_set, expected) + input_set = 'abc' + expected = ['a', 'ab', 'abc', 'ac', + 'b', 'bc', 'c', ''] + self.run_test(input_set, expected) + input_set = 'aabc' + expected = ['a', 'aa', 'aab', 'aabc', + 'aac', 'ab', 'abc', 'ac', + 'b', 'bc', 'c', ''] + self.run_test(input_set, expected) + print('Success: test_power_set') + + def run_test(self, input_set, expected): + combinatoric = Combinatoric() + result = combinatoric.find_power_set(input_set) + assert_equal(result, expected) + + +def main(): + test = TestPowerSet() + test.test_power_set() + + +if __name__ == '__main__': + main() \ No newline at end of file From 9c0035f330dc716959dbe8a29ff5786cd7d4f177 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Mon, 27 Mar 2017 05:21:02 -0400 Subject: [PATCH 09/90] Add permutations challenge --- recursion_dynamic/permutations/__init__.py | 0 .../permutations/permutations_challenge.ipynb | 179 ++++++++++++++ .../permutations/permutations_solution.ipynb | 234 ++++++++++++++++++ .../permutations/test_permutations.py | 26 ++ 4 files changed, 439 insertions(+) create mode 100644 recursion_dynamic/permutations/__init__.py create mode 100644 recursion_dynamic/permutations/permutations_challenge.ipynb create mode 100644 recursion_dynamic/permutations/permutations_solution.ipynb create mode 100644 recursion_dynamic/permutations/test_permutations.py diff --git a/recursion_dynamic/permutations/__init__.py b/recursion_dynamic/permutations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/recursion_dynamic/permutations/permutations_challenge.ipynb b/recursion_dynamic/permutations/permutations_challenge.ipynb new file mode 100644 index 0000000..696ccdb --- /dev/null +++ b/recursion_dynamic/permutations/permutations_challenge.ipynb @@ -0,0 +1,179 @@ +{ + "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: Find all permutations of an input string.\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 the input have duplicates?\n", + " * Yes\n", + "* Can the output have duplicates?\n", + " * No\n", + "* Is the output a list of strings?\n", + " * Yes\n", + "* Do we have to output the results in sorted order?\n", + " * No\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",
+    "* None -> None\n",
+    "* '' -> ''\n",
+    "* 'AABC' -> ['AABC', 'AACB', 'ABAC', 'ABCA',\n",
+    "             'ACAB', 'ACBA', 'BAAC', 'BACA',\n",
+    "             'BCAA', 'CAAB', 'CABA', 'CBAA']\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": false + }, + "outputs": [], + "source": [ + "class Permutations(object):\n", + "\n", + " def find_permutations(self, string):\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_permutations.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestPermutations(object):\n", + "\n", + " def test_permutations(self):\n", + " permutations = Permutations()\n", + " assert_equal(permutations.find_permutations(None), None)\n", + " assert_equal(permutations.find_permutations(''), '')\n", + " string = 'AABC'\n", + " expected = [\n", + " 'AABC', 'AACB', 'ABAC', 'ABCA',\n", + " 'ACAB', 'ACBA', 'BAAC', 'BACA',\n", + " 'BCAA', 'CAAB', 'CABA', 'CBAA'\n", + " ]\n", + " assert_equal(permutations.find_permutations(string), expected)\n", + " print('Success: test_permutations')\n", + "\n", + "\n", + "def main():\n", + " test = TestPermutations()\n", + " test.test_permutations()\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/permutations/permutations_solution.ipynb b/recursion_dynamic/permutations/permutations_solution.ipynb new file mode 100644 index 0000000..4fe81eb --- /dev/null +++ b/recursion_dynamic/permutations/permutations_solution.ipynb @@ -0,0 +1,234 @@ +{ + "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: Find all permutations of an input string.\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 the input have duplicates?\n", + " * Yes\n", + "* Can the output have duplicates?\n", + " * No\n", + "* Is the output a list of strings?\n", + " * Yes\n", + "* Do we have to output the results in sorted order?\n", + " * No\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",
+    "* None -> None\n",
+    "* '' -> ''\n",
+    "* 'AABC' -> ['AABC', 'AACB', 'ABAC', 'ABCA',\n",
+    "             'ACAB', 'ACBA', 'BAAC', 'BACA',\n",
+    "             'BCAA', 'CAAB', 'CABA', 'CBAA']\n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "* Build a dictionary of {chars: counts} where counts is the number of times each char is found in the input\n", + "* Loop through each item in the dictionary\n", + " * If the counts is 0, continue\n", + " * Decrement the current char's count in the dictionary\n", + " * Add the current char to the current results\n", + " * If the current result is the same length as the input, add it to the results\n", + " * Else, recurse\n", + " * Backtrack by:\n", + " * Removing the just added current char from the current results\n", + " * Incrementing the current char's acount in the dictionary\n", + "\n", + "Complexity:\n", + "* Time: O(n!)\n", + "* Space: O(n!) since we are storing the results in an array, or O(n) if we are just printing each result" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from collections import OrderedDict\n", + "\n", + "\n", + "class Permutations(object):\n", + "\n", + " def _build_counts_map(self, string):\n", + " counts_map = OrderedDict()\n", + " for char in string:\n", + " if char in counts_map:\n", + " counts_map[char] += 1\n", + " else:\n", + " counts_map[char] = 1\n", + " return counts_map\n", + "\n", + " def find_permutations(self, string):\n", + " if string is None or string == '':\n", + " return string\n", + " counts_map = self._build_counts_map(string)\n", + " curr_results = []\n", + " results = []\n", + " self._find_permutations(counts_map, curr_results, results, len(string))\n", + " return results\n", + "\n", + " def _find_permutations(self, counts_map, curr_result,\n", + " results, input_length):\n", + " for char in counts_map:\n", + " if counts_map[char] == 0:\n", + " continue\n", + " curr_result.append(char)\n", + " counts_map[char] -= 1\n", + " if len(curr_result) == input_length:\n", + " results.append(''.join(curr_result))\n", + " else:\n", + " self._find_permutations(counts_map, curr_result,\n", + " results, input_length)\n", + " counts_map[char] += 1\n", + " curr_result.pop()" + ] + }, + { + "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_permutations.py\n" + ] + } + ], + "source": [ + "%%writefile test_permutations.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestPermutations(object):\n", + "\n", + " def test_permutations(self):\n", + " permutations = Permutations()\n", + " assert_equal(permutations.find_permutations(None), None)\n", + " assert_equal(permutations.find_permutations(''), '')\n", + " string = 'AABC'\n", + " expected = [\n", + " 'AABC', 'AACB', 'ABAC', 'ABCA',\n", + " 'ACAB', 'ACBA', 'BAAC', 'BACA',\n", + " 'BCAA', 'CAAB', 'CABA', 'CBAA'\n", + " ]\n", + " assert_equal(permutations.find_permutations(string), expected)\n", + " print('Success: test_permutations')\n", + "\n", + "\n", + "def main():\n", + " test = TestPermutations()\n", + " test.test_permutations()\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_permutations\n" + ] + } + ], + "source": [ + "%run -i test_permutations.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/permutations/test_permutations.py b/recursion_dynamic/permutations/test_permutations.py new file mode 100644 index 0000000..b319ac5 --- /dev/null +++ b/recursion_dynamic/permutations/test_permutations.py @@ -0,0 +1,26 @@ +from nose.tools import assert_equal + + +class TestPermutations(object): + + def test_permutations(self): + permutations = Permutations() + assert_equal(permutations.find_permutations(None), None) + assert_equal(permutations.find_permutations(''), '') + string = 'AABC' + expected = [ + 'AABC', 'AACB', 'ABAC', 'ABCA', + 'ACAB', 'ACBA', 'BAAC', 'BACA', + 'BCAA', 'CAAB', 'CABA', 'CBAA' + ] + assert_equal(permutations.find_permutations(string), expected) + print('Success: test_permutations') + + +def main(): + test = TestPermutations() + test.test_permutations() + + +if __name__ == '__main__': + main() \ No newline at end of file From f0bca255ee7982f599f6c41743fd96ccfac2c878 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Mon, 27 Mar 2017 05:22:19 -0400 Subject: [PATCH 10/90] Add k max profit challenge --- recursion_dynamic/max_profit_k/__init__.py | 0 .../max_profit_k/max_profit_challenge.ipynb | 240 ++++++++++++ .../max_profit_k/max_profit_solution.ipynb | 346 ++++++++++++++++++ .../max_profit_k/test_max_profit.py | 45 +++ 4 files changed, 631 insertions(+) create mode 100644 recursion_dynamic/max_profit_k/__init__.py create mode 100644 recursion_dynamic/max_profit_k/max_profit_challenge.ipynb create mode 100644 recursion_dynamic/max_profit_k/max_profit_solution.ipynb create mode 100644 recursion_dynamic/max_profit_k/test_max_profit.py diff --git a/recursion_dynamic/max_profit_k/__init__.py b/recursion_dynamic/max_profit_k/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/recursion_dynamic/max_profit_k/max_profit_challenge.ipynb b/recursion_dynamic/max_profit_k/max_profit_challenge.ipynb new file mode 100644 index 0000000..94291e6 --- /dev/null +++ b/recursion_dynamic/max_profit_k/max_profit_challenge.ipynb @@ -0,0 +1,240 @@ +{ + "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 list of stock prices on each consecutive day, determine the max profits with k transactions.\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", + "* Is k the number of sell transactions?\n", + " * Yes\n", + "* Can we assume the prices input is an array of ints?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * No\n", + "* If the prices are all decreasing and there is no opportunity to make a profit, do we just return 0?\n", + " * Yes\n", + "* Should the output be the max profit and days to buy and sell?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "
\n",
+    "* Prices: None or k: None -> None\n",
+    "* Prices: [] or k <= 0 -> []\n",
+    "* Prices: [0, -1, -2, -3, -4, -5]\n",
+    "    * (max profit, list of transactions)\n",
+    "    * (0, [])\n",
+    "* Prices: [2, 5, 7, 1, 4, 3, 1, 3] k: 3\n",
+    "    * (max profit, list of transactions)\n",
+    "    * (10, [Type.SELL day: 7 price: 3, \n",
+    "            Type.BUY  day: 6 price: 1, \n",
+    "            Type.SELL day: 4 price: 4, \n",
+    "            Type.BUY  day: 3 price: 1, \n",
+    "            Type.SELL day: 2 price: 7, \n",
+    "            Type.BUY  day: 0 price: 2])\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": [ + "from enum import Enum # Python 2 users: Run pip install enum34\n", + "\n", + "\n", + "class Type(Enum):\n", + " SELL = 0\n", + " BUY = 1\n", + "\n", + "\n", + "class Transaction(object):\n", + "\n", + " def __init__(self, type, day, price):\n", + " self.type = type\n", + " self.day = day\n", + " self.price = price\n", + "\n", + " def __eq__(self, other):\n", + " return self.type == other.type and \\\n", + " self.day == other.day and \\\n", + " self.price == other.price\n", + "\n", + " def __repr__(self):\n", + " return str(self.type) + ' day: ' + \\\n", + " str(self.day) + ' price: ' + \\\n", + " str(self.price)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class StockTrader(object):\n", + "\n", + " def find_max_profit(self, prices, k):\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_max_profit.py\n", + "from nose.tools import assert_equal\n", + "from nose.tools import assert_raises\n", + "from nose.tools import assert_true\n", + "\n", + "\n", + "class TestMaxProfit(object):\n", + "\n", + " def test_max_profit(self):\n", + " stock_trader = StockTrader()\n", + " assert_raises(TypeError, stock_trader.find_max_profit, None, None)\n", + " assert_equal(stock_trader.find_max_profit(prices=[], k=0), [])\n", + " prices = [5, 4, 3, 2, 1]\n", + " k = 3\n", + " assert_equal(stock_trader.find_max_profit(prices, k), (0, []))\n", + " prices = [2, 5, 7, 1, 4, 3, 1, 3]\n", + " profit, transactions = stock_trader.find_max_profit(prices, k)\n", + " assert_equal(profit, 10)\n", + " assert_true(Transaction(Type.SELL,\n", + " day=7,\n", + " price=3) in transactions)\n", + " assert_true(Transaction(Type.BUY,\n", + " day=6,\n", + " price=1) in transactions)\n", + " assert_true(Transaction(Type.SELL,\n", + " day=4,\n", + " price=4) in transactions)\n", + " assert_true(Transaction(Type.BUY,\n", + " day=3,\n", + " price=1) in transactions)\n", + " assert_true(Transaction(Type.SELL,\n", + " day=2,\n", + " price=7) in transactions)\n", + " assert_true(Transaction(Type.BUY,\n", + " day=0,\n", + " price=2) in transactions)\n", + " print('Success: test_max_profit')\n", + "\n", + "\n", + "def main():\n", + " test = TestMaxProfit()\n", + " test.test_max_profit()\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/max_profit_k/max_profit_solution.ipynb b/recursion_dynamic/max_profit_k/max_profit_solution.ipynb new file mode 100644 index 0000000..fe9b866 --- /dev/null +++ b/recursion_dynamic/max_profit_k/max_profit_solution.ipynb @@ -0,0 +1,346 @@ +{ + "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 list of stock prices on each consecutive day, determine the max profits with k transactions.\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", + "* Is k the number of sell transactions?\n", + " * Yes\n", + "* Can we assume the prices input is an array of ints?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * No\n", + "* If the prices are all decreasing and there is no opportunity to make a profit, do we just return 0?\n", + " * Yes\n", + "* Should the output be the max profit and days to buy and sell?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "
\n",
+    "* Prices: None or k: None -> None\n",
+    "* Prices: [] or k <= 0 -> []\n",
+    "* Prices: [0, -1, -2, -3, -4, -5]\n",
+    "    * (max profit, list of transactions)\n",
+    "    * (0, [])\n",
+    "* Prices: [2, 5, 7, 1, 4, 3, 1, 3] k: 3\n",
+    "    * (max profit, list of transactions)\n",
+    "    * (10, [Type.SELL day: 7 price: 3, \n",
+    "            Type.BUY  day: 6 price: 1, \n",
+    "            Type.SELL day: 4 price: 4, \n",
+    "            Type.BUY  day: 3 price: 1, \n",
+    "            Type.SELL day: 2 price: 7, \n",
+    "            Type.BUY  day: 0 price: 2])\n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "We'll use bottom up dynamic programming to build a table.\n", + "\n", + "
\n",
+    "\n",
+    "The rows (i) represent the prices.\n",
+    "The columns (j) represent the number of transactions (k).\n",
+    "\n",
+    "T[i][j] = max(T[i][j - 1],\n",
+    "              prices[j] - price[m] + T[i - 1][m])\n",
+    "\n",
+    "m = 0...j-1\n",
+    "\n",
+    "      0   1   2   3   4   5   6   7\n",
+    "--------------------------------------\n",
+    "|   | 2 | 5 | 7 | 1 | 4 | 3 | 1 | 3  |\n",
+    "--------------------------------------\n",
+    "| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0  |\n",
+    "| 1 | 0 | 3 | 5 | 5 | 5 | 5 | 5 | 5  |\n",
+    "| 2 | 0 | 3 | 5 | 5 | 8 | 8 | 8 | 8  |\n",
+    "| 3 | 0 | 3 | 5 | 5 | 8 | 8 | 8 | 10 |\n",
+    "--------------------------------------\n",
+    "\n",
+    "Optimization:\n",
+    "\n",
+    "max_diff = max(max_diff,\n",
+    "               T[i - 1][j - 1] - prices[j - 1])\n",
+    "\n",
+    "T[i][j] = max(T[i][j - 1],\n",
+    "              prices[j] + max_diff)\n",
+    "\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(n * k)\n", + "* Space: O(n * k)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from enum import Enum # Python 2 users: Run pip install enum34\n", + "\n", + "\n", + "class Type(Enum):\n", + " SELL = 0\n", + " BUY = 1\n", + "\n", + "\n", + "class Transaction(object):\n", + "\n", + " def __init__(self, type, day, price):\n", + " self.type = type\n", + " self.day = day\n", + " self.price = price\n", + "\n", + " def __eq__(self, other):\n", + " return self.type == other.type and \\\n", + " self.day == other.day and \\\n", + " self.price == other.price\n", + "\n", + " def __repr__(self):\n", + " return str(self.type) + ' day: ' + \\\n", + " str(self.day) + ' price: ' + \\\n", + " str(self.price)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\n", + "\n", + "\n", + "class StockTrader(object):\n", + "\n", + " def find_max_profit(self, prices, k):\n", + " if prices is None or k is None:\n", + " raise TypeError('prices or k cannot be None')\n", + " if not prices or k <= 0:\n", + " return []\n", + " num_rows = k + 1 # 0th transaction for dp table\n", + " num_cols = len(prices)\n", + " T = [[None] * num_cols for _ in range(num_rows)]\n", + " for i in range(num_rows):\n", + " for j in range(num_cols):\n", + " if i == 0 or j == 0:\n", + " T[i][j] = 0\n", + " continue\n", + " max_profit = -sys.maxsize\n", + " for m in range(j):\n", + " profit = prices[j] - prices[m] + T[i - 1][m]\n", + " if profit > max_profit:\n", + " max_profit = profit\n", + " T[i][j] = max(T[i][j - 1], max_profit)\n", + " return self._find_max_profit_transactions(T, prices)\n", + "\n", + " def find_max_profit_optimized(self, prices, k):\n", + " if prices is None or k is None:\n", + " raise TypeError('prices or k cannot be None')\n", + " if not prices or k <= 0:\n", + " return []\n", + " num_rows = k + 1\n", + " num_cols = len(prices)\n", + " T = [[None] * num_cols for _ in range(num_rows)]\n", + " for i in range(num_rows):\n", + " max_diff = prices[0] * -1\n", + " for j in range(num_cols):\n", + " if i == 0 or j == 0:\n", + " T[i][j] = 0\n", + " continue\n", + " max_diff = max(\n", + " max_diff,\n", + " T[i - 1][j - 1] - prices[j - 1])\n", + " T[i][j] = max(\n", + " T[i][j - 1],\n", + " prices[j] + max_diff)\n", + " return self._find_max_profit_transactions(T, prices)\n", + "\n", + " def _find_max_profit_transactions(self, T, prices):\n", + " results = []\n", + " i = len(T) - 1\n", + " j = len(T[0]) - 1\n", + " max_profit = T[i][j]\n", + " while i != 0 and j != 0:\n", + " if T[i][j] == T[i][j - 1]:\n", + " j -= 1\n", + " else:\n", + " sell_price = prices[j]\n", + " results.append(Transaction(Type.SELL, j, sell_price))\n", + " profit = T[i][j] - T[i - 1][j - 1]\n", + " i -= 1\n", + " j -= 1\n", + " for m in range(j + 1)[::-1]:\n", + " if sell_price - prices[m] == profit:\n", + " results.append(Transaction(Type.BUY, m, prices[m]))\n", + " break\n", + " return (max_profit, results)" + ] + }, + { + "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_max_profit.py\n" + ] + } + ], + "source": [ + "%%writefile test_max_profit.py\n", + "from nose.tools import assert_equal\n", + "from nose.tools import assert_raises\n", + "from nose.tools import assert_true\n", + "\n", + "\n", + "class TestMaxProfit(object):\n", + "\n", + " def test_max_profit(self):\n", + " stock_trader = StockTrader()\n", + " assert_raises(TypeError, stock_trader.find_max_profit, None, None)\n", + " assert_equal(stock_trader.find_max_profit(prices=[], k=0), [])\n", + " prices = [5, 4, 3, 2, 1]\n", + " k = 3\n", + " assert_equal(stock_trader.find_max_profit(prices, k), (0, []))\n", + " prices = [2, 5, 7, 1, 4, 3, 1, 3]\n", + " profit, transactions = stock_trader.find_max_profit(prices, k)\n", + " assert_equal(profit, 10)\n", + " assert_true(Transaction(Type.SELL,\n", + " day=7,\n", + " price=3) in transactions)\n", + " assert_true(Transaction(Type.BUY,\n", + " day=6,\n", + " price=1) in transactions)\n", + " assert_true(Transaction(Type.SELL,\n", + " day=4,\n", + " price=4) in transactions)\n", + " assert_true(Transaction(Type.BUY,\n", + " day=3,\n", + " price=1) in transactions)\n", + " assert_true(Transaction(Type.SELL,\n", + " day=2,\n", + " price=7) in transactions)\n", + " assert_true(Transaction(Type.BUY,\n", + " day=0,\n", + " price=2) in transactions)\n", + " print('Success: test_max_profit')\n", + "\n", + "\n", + "def main():\n", + " test = TestMaxProfit()\n", + " test.test_max_profit()\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_max_profit\n" + ] + } + ], + "source": [ + "%run -i test_max_profit.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/max_profit_k/test_max_profit.py b/recursion_dynamic/max_profit_k/test_max_profit.py new file mode 100644 index 0000000..bd21cc1 --- /dev/null +++ b/recursion_dynamic/max_profit_k/test_max_profit.py @@ -0,0 +1,45 @@ +from nose.tools import assert_equal +from nose.tools import assert_raises +from nose.tools import assert_true + + +class TestMaxProfit(object): + + def test_max_profit(self): + stock_trader = StockTrader() + assert_raises(TypeError, stock_trader.find_max_profit, None, None) + assert_equal(stock_trader.find_max_profit(prices=[], k=0), []) + prices = [5, 4, 3, 2, 1] + k = 3 + assert_equal(stock_trader.find_max_profit(prices, k), (0, [])) + prices = [2, 5, 7, 1, 4, 3, 1, 3] + profit, transactions = stock_trader.find_max_profit(prices, k) + assert_equal(profit, 10) + assert_true(Transaction(Type.SELL, + day=7, + price=3) in transactions) + assert_true(Transaction(Type.BUY, + day=6, + price=1) in transactions) + assert_true(Transaction(Type.SELL, + day=4, + price=4) in transactions) + assert_true(Transaction(Type.BUY, + day=3, + price=1) in transactions) + assert_true(Transaction(Type.SELL, + day=2, + price=7) in transactions) + assert_true(Transaction(Type.BUY, + day=0, + price=2) in transactions) + print('Success: test_max_profit') + + +def main(): + test = TestMaxProfit() + test.test_max_profit() + + +if __name__ == '__main__': + main() \ No newline at end of file From e5ccefb389e87d1152b584a1f71ec19bbc600fab Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Mon, 27 Mar 2017 05:23:15 -0400 Subject: [PATCH 11/90] Add matrix mult challenge --- recursion_dynamic/matrix_mult/__init__.py | 0 .../matrix_mult/find_min_cost_challenge.ipynb | 183 ++++++++++ .../matrix_mult/find_min_cost_solution.ipynb | 316 ++++++++++++++++++ .../matrix_mult/test_find_min_cost.py | 25 ++ 4 files changed, 524 insertions(+) create mode 100644 recursion_dynamic/matrix_mult/__init__.py create mode 100644 recursion_dynamic/matrix_mult/find_min_cost_challenge.ipynb create mode 100644 recursion_dynamic/matrix_mult/find_min_cost_solution.ipynb create mode 100644 recursion_dynamic/matrix_mult/test_find_min_cost.py diff --git a/recursion_dynamic/matrix_mult/__init__.py b/recursion_dynamic/matrix_mult/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/recursion_dynamic/matrix_mult/find_min_cost_challenge.ipynb b/recursion_dynamic/matrix_mult/find_min_cost_challenge.ipynb new file mode 100644 index 0000000..5f4a260 --- /dev/null +++ b/recursion_dynamic/matrix_mult/find_min_cost_challenge.ipynb @@ -0,0 +1,183 @@ +{ + "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 list of 2x2 matrices, minimize the cost of matrix multiplication.\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", + "* Do we just want to calculate the cost and not list the actual order of operations?\n", + " * Yes\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 -> Exception\n", + "* [] -> 0\n", + "* [Matrix(2, 3), Matrix(3, 6), Matrix(6, 4), Matrix(4, 5)] -> 124" + ] + }, + { + "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 Matrix(object):\n", + "\n", + " def __init__(self, first, second):\n", + " self.first = first\n", + " self.second = second" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class MatrixMultiplicationCost(object):\n", + "\n", + " def find_min_cost(self, matrices):\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_find_min_cost.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestMatrixMultiplicationCost(object):\n", + "\n", + " def test_find_min_cost(self):\n", + " matrix_mult_cost = MatrixMultiplicationCost()\n", + " assert_raises(TypeError, matrix_mult_cost.find_min_cost, None)\n", + " assert_equal(matrix_mult_cost.find_min_cost([]), 0)\n", + " matrices = [Matrix(2, 3),\n", + " Matrix(3, 6),\n", + " Matrix(6, 4),\n", + " Matrix(4, 5)]\n", + " expected_cost = 124\n", + " assert_equal(matrix_mult_cost.find_min_cost(matrices), expected_cost)\n", + " print('Success: test_find_min_cost')\n", + "\n", + "\n", + "def main():\n", + " test = TestMatrixMultiplicationCost()\n", + " test.test_find_min_cost()\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/matrix_mult/find_min_cost_solution.ipynb b/recursion_dynamic/matrix_mult/find_min_cost_solution.ipynb new file mode 100644 index 0000000..7eb3c9a --- /dev/null +++ b/recursion_dynamic/matrix_mult/find_min_cost_solution.ipynb @@ -0,0 +1,316 @@ +{ + "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 list of 2x2 matrices, minimize the cost of matrix multiplication.\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", + "* Do we just want to calculate the cost and not list the actual order of operations?\n", + " * Yes\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 -> Exception\n", + "* [] -> 0\n", + "* [Matrix(2, 3), Matrix(3, 6), Matrix(6, 4), Matrix(4, 5)] -> 124" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "We'll use bottom up dynamic programming to build a table.\n", + "\n", + "
\n",
+    "\n",
+    "  0    1    2    3\n",
+    "[2,3][3,6][6,4][4,5]\n",
+    "\n",
+    "Case: 0 * 1\n",
+    "2 * 3 * 6 = 36\n",
+    "\n",
+    "Case: 1 * 2\n",
+    "3 * 6 * 4 = 72\n",
+    "\n",
+    "Case: 2 * 3\n",
+    "6 * 4 * 5 = 120\n",
+    "\n",
+    "Case: 0 * 1 * 2\n",
+    "0 * (1 * 2) = 2 * 3 * 4 + 72 = 96\n",
+    "(0 * 1) * 2 = 36 + 2 * 6 * 4 = 84\n",
+    "min: 84\n",
+    "\n",
+    "Case: 1 * 2 * 3\n",
+    "1 * (2 * 3) = 3 * 6 * 5 + 120 = 210\n",
+    "(1 * 2) * 3 = 72 + 3 * 4 * 5 = 132\n",
+    "min: 132\n",
+    "\n",
+    "Case: 0 * 1 * 2 * 3\n",
+    "0 * (1 * 2 * 3) = 2 * 3 * 5 + 132 = 162\n",
+    "(0 * 1) * (2 * 3) = 36 + 120 + 2 * 6 * 5 = 216\n",
+    "(0 * 1 * 2) * 3 = 84 + 2 * 4 * 5 = 124\n",
+    "min: 124\n",
+    "\n",
+    "  ---------------------\n",
+    "  | 0 |  1 |  2 |   3 |\n",
+    "  ---------------------\n",
+    "0 | 0 | 36 | 84 | 124 |\n",
+    "1 | x |  0 | 72 | 132 |\n",
+    "2 | x |  x |  0 | 120 |\n",
+    "3 | x |  x |  x |   0 |\n",
+    "  ---------------------\n",
+    "\n",
+    "min cost = T[0][cols-1] = 124\n",
+    "\n",
+    "for k in range(i, j):\n",
+    "    T[i][j] = minimum of (T[i][k] + T[k+1][j] +\n",
+    "                          m[i].first * m[k].second * m[j].second) for all k\n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Explanation of k" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n",
+    "  0    1    2    3\n",
+    "[2,3][3,6][6,4][4,5]\n",
+    "\n",
+    "Fill in the missing cell, where i = 0, j = 3\n",
+    "\n",
+    "  ---------------------\n",
+    "  | 0 |  1 |  2 |   3 |\n",
+    "  ---------------------\n",
+    "0 | 0 | 36 | 84 | ??? |\n",
+    "1 | x |  0 | 72 | 132 |\n",
+    "2 | x |  x |  0 | 120 |\n",
+    "3 | x |  x |  x |   0 |\n",
+    "  ---------------------\n",
+    "\n",
+    "Case: 0 * (1 * 2 * 3), k = 0\n",
+    "i = 0, j = 3\n",
+    "\n",
+    "0 * (1 * 2 * 3) = 2 * 3 * 5 + 132 = 162\n",
+    "T[i][k] + T[k+1][j] + m[i].first * m[k].second * m[j].second\n",
+    "T[0][0] + T[1][3] + 2 * 3 * 5\n",
+    "0 + 132 + 30 = 162\n",
+    "\n",
+    "Case: (0 * 1) * (2 * 3), k = 1\n",
+    "i = 0, j = 3\n",
+    "\n",
+    "(0 * 1) * (2 * 3) = 36 + 120 + 2 * 6 * 5 = 216\n",
+    "T[i][k] + T[k+1][j] + m[i].first * m[k].second * m[j].second\n",
+    "T[0][1] + T[2][3] + 2 * 6 * 5\n",
+    "36 + 120 + 60 = 216\n",
+    "\n",
+    "Case: (0 * 1 * 2) * 3, k = 2\n",
+    "i = 0, j = 3\n",
+    "\n",
+    "(0 * 1 * 2) * 3 = 84 + 2 * 4 * 5 = 124\n",
+    "T[i][k] + T[k+1][j] + m[i].first * m[k].second * m[j].second\n",
+    "T[0][2] + T[3][3] + 2 * 4 * 5\n",
+    "84 + 0 + 40 = 124\n",
+    "\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(n^3)\n", + "* Space: O(n^2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class Matrix(object):\n", + "\n", + " def __init__(self, first, second):\n", + " self.first = first\n", + " self.second = second" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\n", + "\n", + "\n", + "class MatrixMultiplicationCost(object):\n", + "\n", + " def find_min_cost(self, matrices):\n", + " if matrices is None:\n", + " raise TypeError('matrices cannot be None')\n", + " if not matrices:\n", + " return 0\n", + " size = len(matrices)\n", + " T = [[0] * size for _ in range(size)]\n", + " for offset in range(1, size):\n", + " for i in range(size-offset):\n", + " j = i + offset\n", + " min_cost = sys.maxsize\n", + " for k in range(i, j):\n", + " cost = (T[i][k] + T[k+1][j] +\n", + " matrices[i].first *\n", + " matrices[k].second *\n", + " matrices[j].second)\n", + " if cost < min_cost:\n", + " min_cost = cost\n", + " T[i][j] = min_cost\n", + " return T[0][size-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_find_min_cost.py\n" + ] + } + ], + "source": [ + "%%writefile test_find_min_cost.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestMatrixMultiplicationCost(object):\n", + "\n", + " def test_find_min_cost(self):\n", + " matrix_mult_cost = MatrixMultiplicationCost()\n", + " assert_raises(TypeError, matrix_mult_cost.find_min_cost, None)\n", + " assert_equal(matrix_mult_cost.find_min_cost([]), 0)\n", + " matrices = [Matrix(2, 3),\n", + " Matrix(3, 6),\n", + " Matrix(6, 4),\n", + " Matrix(4, 5)]\n", + " expected_cost = 124\n", + " assert_equal(matrix_mult_cost.find_min_cost(matrices), expected_cost)\n", + " print('Success: test_find_min_cost')\n", + "\n", + "\n", + "def main():\n", + " test = TestMatrixMultiplicationCost()\n", + " test.test_find_min_cost()\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_find_min_cost\n" + ] + } + ], + "source": [ + "%run -i test_find_min_cost.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/matrix_mult/test_find_min_cost.py b/recursion_dynamic/matrix_mult/test_find_min_cost.py new file mode 100644 index 0000000..20c4cc8 --- /dev/null +++ b/recursion_dynamic/matrix_mult/test_find_min_cost.py @@ -0,0 +1,25 @@ +from nose.tools import assert_equal, assert_raises + + +class TestMatrixMultiplicationCost(object): + + def test_find_min_cost(self): + matrix_mult_cost = MatrixMultiplicationCost() + assert_raises(TypeError, matrix_mult_cost.find_min_cost, None) + assert_equal(matrix_mult_cost.find_min_cost([]), 0) + matrices = [Matrix(2, 3), + Matrix(3, 6), + Matrix(6, 4), + Matrix(4, 5)] + expected_cost = 124 + assert_equal(matrix_mult_cost.find_min_cost(matrices), expected_cost) + print('Success: test_find_min_cost') + + +def main(): + test = TestMatrixMultiplicationCost() + test.test_find_min_cost() + + +if __name__ == '__main__': + main() \ No newline at end of file From 54cee3457a6b03ac5e6dc0d0678ebc878001ac1d Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Mon, 27 Mar 2017 05:24:00 -0400 Subject: [PATCH 12/90] Add magic index challenge --- recursion_dynamic/magic_index/__init__.py | 0 .../magic_index/magic_index_challenge.ipynb | 196 ++++++++++++++ .../magic_index/magic_index_solution.ipynb | 256 ++++++++++++++++++ .../magic_index/test_find_magic_index.py | 25 ++ 4 files changed, 477 insertions(+) create mode 100644 recursion_dynamic/magic_index/__init__.py create mode 100644 recursion_dynamic/magic_index/magic_index_challenge.ipynb create mode 100644 recursion_dynamic/magic_index/magic_index_solution.ipynb create mode 100644 recursion_dynamic/magic_index/test_find_magic_index.py diff --git a/recursion_dynamic/magic_index/__init__.py b/recursion_dynamic/magic_index/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/recursion_dynamic/magic_index/magic_index_challenge.ipynb b/recursion_dynamic/magic_index/magic_index_challenge.ipynb new file mode 100644 index 0000000..3dc9734 --- /dev/null +++ b/recursion_dynamic/magic_index/magic_index_challenge.ipynb @@ -0,0 +1,196 @@ +{ + "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: Find the magic index in an array, where array[i] = i.\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", + "* Is the array sorted?\n", + " * Yes\n", + "* Are the elements in the array distinct?\n", + " * No\n", + "* Does a magic index always exist?\n", + " * No\n", + "* If there is no magic index, do we just return -1?\n", + " * Yes\n", + "* Are negative values allowed in the array?\n", + " * Yes\n", + "* If there are multiple magic values, what do we return?\n", + " * Return the left-most one\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None input -> -1\n", + "* Empty array -> -1\n", + "\n", + "
\n",
+    "a[i]  -4 -2  2  6  6  6  6 10\n",
+    "  i    0  1  2  3  4  5  6  7\n",
+    "
\n", + "\n", + "Result: 2\n", + "\n", + "
\n",
+    "a[i]  -4 -2  1  6  6  6  6 10\n",
+    "  i    0  1  2  3  4  5  6  7\n",
+    "
\n", + "\n", + "Result: 6\n", + "\n", + "
\n",
+    "a[i]  -4 -2  1  6  6  6  7 10\n",
+    "  i    0  1  2  3  4  5  6  7\n",
+    "
\n", + "\n", + "Result: -1" + ] + }, + { + "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 MagicIndex(object):\n", + "\n", + " def find_magic_index(self, array):\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_find_magic_index.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestFindMagicIndex(object):\n", + "\n", + " def test_find_magic_index(self):\n", + " magic_index = MagicIndex()\n", + " assert_equal(magic_index.find_magic_index(None), -1)\n", + " assert_equal(magic_index.find_magic_index([]), -1)\n", + " array = [-4, -2, 2, 6, 6, 6, 6, 10]\n", + " assert_equal(magic_index.find_magic_index(array), 2)\n", + " array = [-4, -2, 1, 6, 6, 6, 6, 10]\n", + " assert_equal(magic_index.find_magic_index(array), 6)\n", + " array = [-4, -2, 1, 6, 6, 6, 7, 10]\n", + " assert_equal(magic_index.find_magic_index(array), -1)\n", + " print('Success: test_find_magic')\n", + "\n", + "\n", + "def main():\n", + " test = TestFindMagicIndex()\n", + " test.test_find_magic_index()\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/magic_index/magic_index_solution.ipynb b/recursion_dynamic/magic_index/magic_index_solution.ipynb new file mode 100644 index 0000000..eade7fc --- /dev/null +++ b/recursion_dynamic/magic_index/magic_index_solution.ipynb @@ -0,0 +1,256 @@ +{ + "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: Find the magic index in an array, where array[i] = i.\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", + "* Is the array sorted?\n", + " * Yes\n", + "* Are the elements in the array distinct?\n", + " * No\n", + "* Does a magic index always exist?\n", + " * No\n", + "* If there is no magic index, do we just return -1?\n", + " * Yes\n", + "* Are negative values allowed in the array?\n", + " * Yes\n", + "* If there are multiple magic values, what do we return?\n", + " * Return the left-most one\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None input -> -1\n", + "* Empty array -> -1\n", + "\n", + "
\n",
+    "a[i]  -4 -2  2  6  6  6  6 10\n",
+    "  i    0  1  2  3  4  5  6  7\n",
+    "
\n", + "\n", + "Result: 2\n", + "\n", + "
\n",
+    "a[i]  -4 -2  1  6  6  6  6 10\n",
+    "  i    0  1  2  3  4  5  6  7\n",
+    "
\n", + "\n", + "Result: 6\n", + "\n", + "
\n",
+    "a[i]  -4 -2  1  6  6  6  7 10\n",
+    "  i    0  1  2  3  4  5  6  7\n",
+    "
\n", + "\n", + "Result: -1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "We'll use a binary search to split the search space in half on each iteration. To obtain more efficiency, we can do a little better than a naive left and half split.\n", + "\n", + "In the example below, we see that i == 5 cannot be the magic index, otherwise a[5] would have to equal 5 (note a[4] == 6).\n", + "\n", + "
\n",
+    "a[i]  -4 -2  2  6  6  6  6 10\n",
+    "  i    0  1  1  3  4  5  6  7\n",
+    "                  mid\n",
+    "
\n", + "\n", + "Similarly, in the example below we can further trim the left search space.\n", + "\n", + "
\n",
+    "a[i]  -4 -2  2  2  2  6  6 10\n",
+    "  i    0  1  2  3  4  5  6  7\n",
+    "                  mid\n",
+    "
\n", + "\n", + "\n", + "* Calculate mid\n", + "* If mid == array[mid], return mid\n", + "* Recurse on the left side of the array\n", + " * start: 0\n", + " * end: min(mid-1, array[mid]\n", + "* Recurse on the right side of the array\n", + " * start: max(mid+1, array[mid]\n", + " * end: end\n", + "\n", + "Complexity:\n", + "* Time: O(log(n))\n", + "* Space: O(log(n))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from __future__ import division\n", + "\n", + "\n", + "class MagicIndex(object):\n", + "\n", + " def find_magic_index(self, array):\n", + " if array is None or not array:\n", + " return -1\n", + " return self._find_magic_index(array, 0, len(array) - 1)\n", + "\n", + " def _find_magic_index(self, array, start, end):\n", + " if end < start or start < 0 or end >= len(array):\n", + " return -1\n", + " mid = (start + end) // 2\n", + " if mid == array[mid]:\n", + " return mid\n", + " left_end = min(mid - 1, array[mid])\n", + " left_result = self._find_magic_index(array, start, end=left_end)\n", + " if left_result != -1:\n", + " return left_result\n", + " right_start = max(mid + 1, array[mid])\n", + " right_result = self._find_magic_index(array, start=right_start, end=end)\n", + " if right_result != -1:\n", + " return right_result\n", + " return -1" + ] + }, + { + "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_find_magic_index.py\n" + ] + } + ], + "source": [ + "%%writefile test_find_magic_index.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestFindMagicIndex(object):\n", + "\n", + " def test_find_magic_index(self):\n", + " magic_index = MagicIndex()\n", + " assert_equal(magic_index.find_magic_index(None), -1)\n", + " assert_equal(magic_index.find_magic_index([]), -1)\n", + " array = [-4, -2, 2, 6, 6, 6, 6, 10]\n", + " assert_equal(magic_index.find_magic_index(array), 2)\n", + " array = [-4, -2, 1, 6, 6, 6, 6, 10]\n", + " assert_equal(magic_index.find_magic_index(array), 6)\n", + " array = [-4, -2, 1, 6, 6, 6, 7, 10]\n", + " assert_equal(magic_index.find_magic_index(array), -1)\n", + " print('Success: test_find_magic')\n", + "\n", + "\n", + "def main():\n", + " test = TestFindMagicIndex()\n", + " test.test_find_magic_index()\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_find_magic\n" + ] + } + ], + "source": [ + "%run -i test_find_magic_index.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/magic_index/test_find_magic_index.py b/recursion_dynamic/magic_index/test_find_magic_index.py new file mode 100644 index 0000000..d6b1bb2 --- /dev/null +++ b/recursion_dynamic/magic_index/test_find_magic_index.py @@ -0,0 +1,25 @@ +from nose.tools import assert_equal + + +class TestFindMagicIndex(object): + + def test_find_magic_index(self): + magic_index = MagicIndex() + assert_equal(magic_index.find_magic_index(None), -1) + assert_equal(magic_index.find_magic_index([]), -1) + array = [-4, -2, 2, 6, 6, 6, 6, 10] + assert_equal(magic_index.find_magic_index(array), 2) + array = [-4, -2, 1, 6, 6, 6, 6, 10] + assert_equal(magic_index.find_magic_index(array), 6) + array = [-4, -2, 1, 6, 6, 6, 7, 10] + assert_equal(magic_index.find_magic_index(array), -1) + print('Success: test_find_magic') + + +def main(): + test = TestFindMagicIndex() + test.test_find_magic_index() + + +if __name__ == '__main__': + main() \ No newline at end of file From 391e814d2b23476da80abf0d6ac7e460e923ca58 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Tue, 28 Mar 2017 05:00:23 -0400 Subject: [PATCH 13/90] Add longest substring challenge --- .../longest_substring/__init__.py | 0 .../longest_common_substr_challenge.ipynb | 177 ++++++++++++ .../longest_common_substr_solution.ipynb | 252 ++++++++++++++++++ .../test_longest_common_substr.py | 23 ++ 4 files changed, 452 insertions(+) create mode 100644 recursion_dynamic/longest_substring/__init__.py create mode 100644 recursion_dynamic/longest_substring/longest_common_substr_challenge.ipynb create mode 100644 recursion_dynamic/longest_substring/longest_common_substr_solution.ipynb create mode 100644 recursion_dynamic/longest_substring/test_longest_common_substr.py diff --git a/recursion_dynamic/longest_substring/__init__.py b/recursion_dynamic/longest_substring/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/recursion_dynamic/longest_substring/longest_common_substr_challenge.ipynb b/recursion_dynamic/longest_substring/longest_common_substr_challenge.ipynb new file mode 100644 index 0000000..77d4218 --- /dev/null +++ b/recursion_dynamic/longest_substring/longest_common_substr_challenge.ipynb @@ -0,0 +1,177 @@ +{ + "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 two strings, find the longest common substring.\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 assume the inputs are valid?\n", + " * No\n", + "* Can we assume the strings are ASCII?\n", + " * Yes\n", + "* Is this case sensitive?\n", + " * Yes\n", + "* Is a substring a contiguous block of chars?\n", + " * Yes\n", + "* Do we expect a string as a result?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* str0 or str1 is None -> Exception\n", + "* str0 or str1 equals 0 -> ''\n", + "* General case\n", + "\n", + "str0 = 'ABCDEFGHIJ'\n", + "str1 = 'FOOBCDBCDE'\n", + "\n", + "result: 'BCDE'" + ] + }, + { + "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 StringCompare(object):\n", + "\n", + " def longest_common_substr(self, str0, str1):\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_longest_common_substr.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestLongestCommonSubstr(object):\n", + "\n", + " def test_longest_common_substr(self):\n", + " str_comp = StringCompare()\n", + " assert_raises(TypeError, str_comp.longest_common_substr, None, None)\n", + " assert_equal(str_comp.longest_common_substr('', ''), '')\n", + " str0 = 'ABCDEFGHIJ'\n", + " str1 = 'FOOBCDBCDE'\n", + " expected = 'BCDE'\n", + " assert_equal(str_comp.longest_common_substr(str0, str1), expected)\n", + " print('Success: test_longest_common_substr')\n", + "\n", + "\n", + "def main():\n", + " test = TestLongestCommonSubstr()\n", + " test.test_longest_common_substr()\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/longest_substring/longest_common_substr_solution.ipynb b/recursion_dynamic/longest_substring/longest_common_substr_solution.ipynb new file mode 100644 index 0000000..d3c4147 --- /dev/null +++ b/recursion_dynamic/longest_substring/longest_common_substr_solution.ipynb @@ -0,0 +1,252 @@ +{ + "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 two strings, find the longest common substring.\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 assume the inputs are valid?\n", + " * No\n", + "* Can we assume the strings are ASCII?\n", + " * Yes\n", + "* Is this case sensitive?\n", + " * Yes\n", + "* Is a substring a contiguous block of chars?\n", + " * Yes\n", + "* Do we expect a string as a result?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* str0 or str1 is None -> Exception\n", + "* str0 or str1 equals 0 -> ''\n", + "* General case\n", + "\n", + "str0 = 'ABCDEFGHIJ'\n", + "str1 = 'FOOBCDBCDE'\n", + "\n", + "result: 'BCDE'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "We'll use bottom up dynamic programming to build a table. \n", + "\n", + "
\n",
+    "\n",
+    "The rows (i) represent str0.\n",
+    "The columns (j) represent str1.\n",
+    "\n",
+    "                       str1\n",
+    "  -------------------------------------------------\n",
+    "  |   |   | A | B | C | D | E | F | G | H | I | J |\n",
+    "  -------------------------------------------------\n",
+    "  |   | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |\n",
+    "  | F | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 |\n",
+    "  | O | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 |\n",
+    "s | O | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 |\n",
+    "t | B | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |\n",
+    "r | C | 0 | 0 | 1 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 |\n",
+    "0 | D | 0 | 0 | 1 | 2 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |\n",
+    "  | B | 0 | 0 | 1 | 2 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |\n",
+    "  | C | 0 | 0 | 1 | 2 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |\n",
+    "  | D | 0 | 0 | 1 | 2 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |\n",
+    "  | E | 0 | 0 | 1 | 2 | 3 | 4 | 4 | 4 | 4 | 4 | 4 |\n",
+    "  -------------------------------------------------\n",
+    "\n",
+    "if str1[j] != str0[i]:\n",
+    "    T[i][j] = max(\n",
+    "        T[i][j-1],\n",
+    "        T[i-1][j])\n",
+    "else:\n",
+    "    T[i][j] = T[i-1][j-1] + 1\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(m * n), where m is the length of str0 and n is the length of str1\n", + "* Space: O(m * n), where m is the length of str0 and n is the length of str1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class StringCompare(object):\n", + "\n", + " def longest_common_substr(self, str0, str1):\n", + " if str0 is None or str1 is None:\n", + " raise TypeError('str input cannot be None')\n", + " # Add one to number of rows and cols for the dp table's\n", + " # first row of 0's and first col of 0's\n", + " num_rows = len(str0) + 1\n", + " num_cols = len(str1) + 1\n", + " T = [[None] * num_cols for _ in range(num_rows)]\n", + " for i in range(num_rows):\n", + " for j in range(num_cols):\n", + " if i == 0 or j == 0:\n", + " T[i][j] = 0\n", + " elif str0[j-1] != str1[i-1]:\n", + " T[i][j] = max(T[i][j-1],\n", + " T[i-1][j])\n", + " else:\n", + " T[i][j] = T[i-1][j-1] + 1\n", + " results = ''\n", + " i = num_rows - 1\n", + " j = num_cols - 1\n", + " # Walk backwards to determine the substring\n", + " while T[i][j]:\n", + " if T[i][j] == T[i][j-1]:\n", + " j -= 1\n", + " elif T[i][j] == T[i-1][j]:\n", + " i -= 1\n", + " elif T[i][j] == T[i-1][j-1] + 1:\n", + " results += str1[i-1]\n", + " i -= 1\n", + " j -= 1\n", + " else:\n", + " raise Exception('Error constructing table')\n", + " # Walking backwards results in a string in reverse order\n", + " return results[::-1] " + ] + }, + { + "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_longest_common_substr.py\n" + ] + } + ], + "source": [ + "%%writefile test_longest_common_substr.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestLongestCommonSubstr(object):\n", + "\n", + " def test_longest_common_substr(self):\n", + " str_comp = StringCompare()\n", + " assert_raises(TypeError, str_comp.longest_common_substr, None, None)\n", + " assert_equal(str_comp.longest_common_substr('', ''), '')\n", + " str0 = 'ABCDEFGHIJ'\n", + " str1 = 'FOOBCDBCDE'\n", + " expected = 'BCDE'\n", + " assert_equal(str_comp.longest_common_substr(str0, str1), expected)\n", + " print('Success: test_longest_common_substr')\n", + "\n", + "\n", + "def main():\n", + " test = TestLongestCommonSubstr()\n", + " test.test_longest_common_substr()\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_longest_common_substr\n" + ] + } + ], + "source": [ + "%run -i test_longest_common_substr.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/longest_substring/test_longest_common_substr.py b/recursion_dynamic/longest_substring/test_longest_common_substr.py new file mode 100644 index 0000000..8af8fe7 --- /dev/null +++ b/recursion_dynamic/longest_substring/test_longest_common_substr.py @@ -0,0 +1,23 @@ +from nose.tools import assert_equal, assert_raises + + +class TestLongestCommonSubstr(object): + + def test_longest_common_substr(self): + str_comp = StringCompare() + assert_raises(TypeError, str_comp.longest_common_substr, None, None) + assert_equal(str_comp.longest_common_substr('', ''), '') + str0 = 'ABCDEFGHIJ' + str1 = 'FOOBCDBCDE' + expected = 'BCDE' + assert_equal(str_comp.longest_common_substr(str0, str1), expected) + print('Success: test_longest_common_substr') + + +def main(): + test = TestLongestCommonSubstr() + test.test_longest_common_substr() + + +if __name__ == '__main__': + main() \ No newline at end of file From c9510e71b629c3d18a5c3bfc6dc0e25967234692 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Tue, 28 Mar 2017 05:01:34 -0400 Subject: [PATCH 14/90] Add longest substring k distinct challenge --- .../longest_substr_k_distinct/__init__.py | 0 .../longest_substr_challenge.ipynb | 171 ++++++++++++++ .../longest_substr_solution.ipynb | 208 ++++++++++++++++++ .../test_longest_substr.py | 21 ++ 4 files changed, 400 insertions(+) create mode 100644 recursion_dynamic/longest_substr_k_distinct/__init__.py create mode 100644 recursion_dynamic/longest_substr_k_distinct/longest_substr_challenge.ipynb create mode 100644 recursion_dynamic/longest_substr_k_distinct/longest_substr_solution.ipynb create mode 100644 recursion_dynamic/longest_substr_k_distinct/test_longest_substr.py diff --git a/recursion_dynamic/longest_substr_k_distinct/__init__.py b/recursion_dynamic/longest_substr_k_distinct/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/recursion_dynamic/longest_substr_k_distinct/longest_substr_challenge.ipynb b/recursion_dynamic/longest_substr_k_distinct/longest_substr_challenge.ipynb new file mode 100644 index 0000000..0636cdd --- /dev/null +++ b/recursion_dynamic/longest_substr_k_distinct/longest_substr_challenge.ipynb @@ -0,0 +1,171 @@ +{ + "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: Find the longest substring with at most k distinct characters.\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 assume the inputs are valid?\n", + " * No\n", + "* Can we assume the strings are ASCII?\n", + " * Yes\n", + "* Is this case sensitive?\n", + " * Yes\n", + "* Is a substring a contiguous block of chars?\n", + " * Yes\n", + "* Do we expect an int as a result?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> TypeError\n", + "* '', k = 3 -> 0\n", + "* 'abcabcdefgghiij', k=3 -> 6\n", + "* 'abcabcdefgghighij', k=3 -> 7" + ] + }, + { + "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 Solution(object):\n", + "\n", + " def longest_substr(self, string, k):\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_longest_substr.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSolution(object):\n", + "\n", + " def test_longest_substr(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.longest_substr, None)\n", + " assert_equal(solution.longest_substr('', k=3), 0)\n", + " assert_equal(solution.longest_substr('abcabcdefgghiij', k=3), 6)\n", + " assert_equal(solution.longest_substr('abcabcdefgghighij', k=3), 7)\n", + " print('Success: test_longest_substr')\n", + "\n", + "\n", + "def main():\n", + " test = TestSolution()\n", + " test.test_longest_substr()\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/longest_substr_k_distinct/longest_substr_solution.ipynb b/recursion_dynamic/longest_substr_k_distinct/longest_substr_solution.ipynb new file mode 100644 index 0000000..bd71b1f --- /dev/null +++ b/recursion_dynamic/longest_substr_k_distinct/longest_substr_solution.ipynb @@ -0,0 +1,208 @@ +{ + "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: Find the length longest substring with at most k distinct characters.\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 assume the inputs are valid?\n", + " * No\n", + "* Can we assume the strings are ASCII?\n", + " * Yes\n", + "* Is this case sensitive?\n", + " * Yes\n", + "* Is a substring a contiguous block of chars?\n", + " * Yes\n", + "* Do we expect an int as a result?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> TypeError\n", + "* '', k = 3 -> 0\n", + "* 'abcabcdefgghiij', k=3 -> 6\n", + "* 'abcabcdefgghighij', k=3 -> 7" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "We'll use a `chars_to_index_map` dictionary: char (key) to index (val) map to maintain a sliding window.\n", + "\n", + "The index (val) will keep track of the character index in the input string.\n", + "\n", + "For each character in the string:\n", + "\n", + "* Add the char (key) and index (value) to the map\n", + "* If the length of our map is greater than k, then we'll need to eliminate one item\n", + " * Scan the map to find the lowest index and remove it\n", + " * The new lowest index will therefore be incremented by 1\n", + "* The max length will be the current index minus the lower index + 1\n", + "\n", + "Complexity:\n", + "* Time: O(n*k), where n is the number of chars, k is the length of the map due to the min() call\n", + "* Space: O(n)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def longest_substr(self, string, k):\n", + " if not isinstance(string, str):\n", + " raise TypeError('string must be of type str')\n", + " if not isinstance(k, int):\n", + " raise TypeError('k must be of type int')\n", + " low_index = 0\n", + " max_length = 0\n", + " chars_to_index_map = {}\n", + " for index, char in enumerate(string):\n", + " chars_to_index_map[char] = index\n", + " if len(chars_to_index_map) > k:\n", + " low_index = min(chars_to_index_map.values())\n", + " del chars_to_index_map[string[low_index]]\n", + " low_index += 1\n", + " max_length = max(max_length, index - low_index + 1)\n", + " return max_length" + ] + }, + { + "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_longest_substr.py\n" + ] + } + ], + "source": [ + "%%writefile test_longest_substr.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSolution(object):\n", + "\n", + " def test_longest_substr(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.longest_substr, None)\n", + " assert_equal(solution.longest_substr('', k=3), 0)\n", + " assert_equal(solution.longest_substr('abcabcdefgghiij', k=3), 6)\n", + " assert_equal(solution.longest_substr('abcabcdefgghighij', k=3), 7)\n", + " print('Success: test_longest_substr')\n", + "\n", + "\n", + "def main():\n", + " test = TestSolution()\n", + " test.test_longest_substr()\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_longest_substr\n" + ] + } + ], + "source": [ + "%run -i test_longest_substr.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/longest_substr_k_distinct/test_longest_substr.py b/recursion_dynamic/longest_substr_k_distinct/test_longest_substr.py new file mode 100644 index 0000000..6410c7f --- /dev/null +++ b/recursion_dynamic/longest_substr_k_distinct/test_longest_substr.py @@ -0,0 +1,21 @@ +from nose.tools import assert_equal, assert_raises + + +class TestSolution(object): + + def test_longest_substr(self): + solution = Solution() + assert_raises(TypeError, solution.longest_substr, None) + assert_equal(solution.longest_substr('', k=3), 0) + assert_equal(solution.longest_substr('abcabcdefgghiij', k=3), 6) + assert_equal(solution.longest_substr('abcabcdefgghighij', k=3), 7) + print('Success: test_longest_substr') + + +def main(): + test = TestSolution() + test.test_longest_substr() + + +if __name__ == '__main__': + main() \ No newline at end of file From f6abe150e3b54681a03764d1fa54f486beb3b8cb Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Tue, 28 Mar 2017 05:02:00 -0400 Subject: [PATCH 15/90] Add longest increasing subseq challenge --- .../longest_inc_subseq/__init__.py | 0 .../longest_inc_subseq_challenge.ipynb | 169 +++++++++++++ .../longest_inc_subseq_solution.ipynb | 231 ++++++++++++++++++ .../test_longest_increasing_subseq.py | 22 ++ 4 files changed, 422 insertions(+) create mode 100644 recursion_dynamic/longest_inc_subseq/__init__.py create mode 100644 recursion_dynamic/longest_inc_subseq/longest_inc_subseq_challenge.ipynb create mode 100644 recursion_dynamic/longest_inc_subseq/longest_inc_subseq_solution.ipynb create mode 100644 recursion_dynamic/longest_inc_subseq/test_longest_increasing_subseq.py diff --git a/recursion_dynamic/longest_inc_subseq/__init__.py b/recursion_dynamic/longest_inc_subseq/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/recursion_dynamic/longest_inc_subseq/longest_inc_subseq_challenge.ipynb b/recursion_dynamic/longest_inc_subseq/longest_inc_subseq_challenge.ipynb new file mode 100644 index 0000000..dfe8d4d --- /dev/null +++ b/recursion_dynamic/longest_inc_subseq/longest_inc_subseq_challenge.ipynb @@ -0,0 +1,169 @@ +{ + "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: Find the longest increasing subsequence.\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 duplicates possible?\n", + " * Yes\n", + "* Can we assume the inputs are integers?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * No\n", + "* Do we expect the result to be an array of the longest increasing subsequence?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> Exception\n", + "* [] -> []\n", + "* [3, 4, -1, 0, 6, 2, 3] -> [-1, 0, 2, 3]" + ] + }, + { + "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 Subsequence(object):\n", + "\n", + " def longest_inc_subseq(self, seq):\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_longest_increasing_subseq.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestLongestIncreasingSubseq(object):\n", + "\n", + " def test_longest_increasing_subseq(self):\n", + " subseq = Subsequence()\n", + " assert_raises(TypeError, subseq.longest_inc_subseq, None)\n", + " assert_equal(subseq.longest_inc_subseq([]), [])\n", + " seq = [3, 4, -1, 0, 6, 2, 3]\n", + " expected = [-1, 0, 2, 3]\n", + " assert_equal(subseq.longest_inc_subseq(seq), expected)\n", + " print('Success: test_longest_increasing_subseq')\n", + "\n", + "\n", + "def main():\n", + " test = TestLongestIncreasingSubseq()\n", + " test.test_longest_increasing_subseq()\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/longest_inc_subseq/longest_inc_subseq_solution.ipynb b/recursion_dynamic/longest_inc_subseq/longest_inc_subseq_solution.ipynb new file mode 100644 index 0000000..a46c896 --- /dev/null +++ b/recursion_dynamic/longest_inc_subseq/longest_inc_subseq_solution.ipynb @@ -0,0 +1,231 @@ +{ + "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: Find the longest increasing subsequence.\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 duplicates possible?\n", + " * Yes\n", + "* Can we assume the inputs are integers?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * No\n", + "* Do we expect the result to be an array of the longest increasing subsequence?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> Exception\n", + "* [] -> []\n", + "* [3, 4, -1, 0, 6, 2, 3] -> [-1, 0, 2, 3]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "We'll use bottom up dynamic programming to build a table.\n", + "\n", + "
\n",
+    "Init a temp array of size len(input) to 1.  \n",
+    "We'll use l and r to iterate through the input.\n",
+    "Array prev will hold the index of the prior smaller value, used to reconstruct the final sequence.\n",
+    "\n",
+    "if input[l] < input[r]:\n",
+    "    if temp[r] < temp[l] + 1:\n",
+    "        temp[r] = temp[l] + 1\n",
+    "        prev[r] = l\n",
+    "\n",
+    "        l  r\n",
+    "index:  0  1  2  3  4  5  6\n",
+    "---------------------------\n",
+    "input:  3  4 -1  0  6  2  3\n",
+    "temp:   1  2  1  1  1  1  1\n",
+    "prev:   x  x  x  x  x  x  x\n",
+    "\n",
+    "End result:\n",
+    "\n",
+    "index:  0  1  2  3  4  5  6\n",
+    "---------------------------\n",
+    "input:  3  4 -1  0  6  2  3\n",
+    "temp:   1  2  1  2  3  3  4\n",
+    "prev:   x  0  x  2  1  3  5\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(n^2)\n", + "* Space: O(n)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class Subsequence(object):\n", + "\n", + " def longest_inc_subseq(self, seq):\n", + " if seq is None:\n", + " raise TypeError('seq cannot be None')\n", + " if not seq:\n", + " return []\n", + " temp = [1] * len(seq)\n", + " prev = [None] * len(seq)\n", + " for r in range(1, len(seq)):\n", + " for l in range(r):\n", + " if seq[l] < seq[r]:\n", + " if temp[r] < temp[l] + 1:\n", + " temp[r] = temp[l] + 1\n", + " prev[r] = l\n", + " max_val = 0\n", + " max_index = -1\n", + " results = []\n", + " for index, value in enumerate(temp):\n", + " if value > max_val:\n", + " max_val = value\n", + " max_index = index\n", + " curr_index = max_index\n", + " while curr_index is not None:\n", + " results.append(seq[curr_index])\n", + " curr_index = prev[curr_index]\n", + " return results[::-1]" + ] + }, + { + "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_longest_increasing_subseq.py\n" + ] + } + ], + "source": [ + "%%writefile test_longest_increasing_subseq.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestLongestIncreasingSubseq(object):\n", + "\n", + " def test_longest_increasing_subseq(self):\n", + " subseq = Subsequence()\n", + " assert_raises(TypeError, subseq.longest_inc_subseq, None)\n", + " assert_equal(subseq.longest_inc_subseq([]), [])\n", + " seq = [3, 4, -1, 0, 6, 2, 3]\n", + " expected = [-1, 0, 2, 3]\n", + " assert_equal(subseq.longest_inc_subseq(seq), expected)\n", + " print('Success: test_longest_increasing_subseq')\n", + "\n", + "\n", + "def main():\n", + " test = TestLongestIncreasingSubseq()\n", + " test.test_longest_increasing_subseq()\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_longest_increasing_subseq\n" + ] + } + ], + "source": [ + "%run -i test_longest_increasing_subseq.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/longest_inc_subseq/test_longest_increasing_subseq.py b/recursion_dynamic/longest_inc_subseq/test_longest_increasing_subseq.py new file mode 100644 index 0000000..7a0f9c0 --- /dev/null +++ b/recursion_dynamic/longest_inc_subseq/test_longest_increasing_subseq.py @@ -0,0 +1,22 @@ +from nose.tools import assert_equal, assert_raises + + +class TestLongestIncreasingSubseq(object): + + def test_longest_increasing_subseq(self): + subseq = Subsequence() + assert_raises(TypeError, subseq.longest_inc_subseq, None) + assert_equal(subseq.longest_inc_subseq([]), []) + seq = [3, 4, -1, 0, 6, 2, 3] + expected = [-1, 0, 2, 3] + assert_equal(subseq.longest_inc_subseq(seq), expected) + print('Success: test_longest_increasing_subseq') + + +def main(): + test = TestLongestIncreasingSubseq() + test.test_longest_increasing_subseq() + + +if __name__ == '__main__': + main() \ No newline at end of file From 92590ff3b6bb2205b8c9ec3a8e006caf6f7d8ef8 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Tue, 28 Mar 2017 05:02:34 -0400 Subject: [PATCH 16/90] Add longest common substring challenge --- .../longest_common_substring/__init__.py | 0 .../longest_common_substr_challenge.ipynb | 177 ++++++++++++ .../longest_common_substr_solution.ipynb | 252 ++++++++++++++++++ .../test_longest_common_substr.py | 23 ++ 4 files changed, 452 insertions(+) create mode 100644 recursion_dynamic/longest_common_substring/__init__.py create mode 100644 recursion_dynamic/longest_common_substring/longest_common_substr_challenge.ipynb create mode 100644 recursion_dynamic/longest_common_substring/longest_common_substr_solution.ipynb create mode 100644 recursion_dynamic/longest_common_substring/test_longest_common_substr.py diff --git a/recursion_dynamic/longest_common_substring/__init__.py b/recursion_dynamic/longest_common_substring/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/recursion_dynamic/longest_common_substring/longest_common_substr_challenge.ipynb b/recursion_dynamic/longest_common_substring/longest_common_substr_challenge.ipynb new file mode 100644 index 0000000..77d4218 --- /dev/null +++ b/recursion_dynamic/longest_common_substring/longest_common_substr_challenge.ipynb @@ -0,0 +1,177 @@ +{ + "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 two strings, find the longest common substring.\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 assume the inputs are valid?\n", + " * No\n", + "* Can we assume the strings are ASCII?\n", + " * Yes\n", + "* Is this case sensitive?\n", + " * Yes\n", + "* Is a substring a contiguous block of chars?\n", + " * Yes\n", + "* Do we expect a string as a result?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* str0 or str1 is None -> Exception\n", + "* str0 or str1 equals 0 -> ''\n", + "* General case\n", + "\n", + "str0 = 'ABCDEFGHIJ'\n", + "str1 = 'FOOBCDBCDE'\n", + "\n", + "result: 'BCDE'" + ] + }, + { + "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 StringCompare(object):\n", + "\n", + " def longest_common_substr(self, str0, str1):\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_longest_common_substr.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestLongestCommonSubstr(object):\n", + "\n", + " def test_longest_common_substr(self):\n", + " str_comp = StringCompare()\n", + " assert_raises(TypeError, str_comp.longest_common_substr, None, None)\n", + " assert_equal(str_comp.longest_common_substr('', ''), '')\n", + " str0 = 'ABCDEFGHIJ'\n", + " str1 = 'FOOBCDBCDE'\n", + " expected = 'BCDE'\n", + " assert_equal(str_comp.longest_common_substr(str0, str1), expected)\n", + " print('Success: test_longest_common_substr')\n", + "\n", + "\n", + "def main():\n", + " test = TestLongestCommonSubstr()\n", + " test.test_longest_common_substr()\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/longest_common_substring/longest_common_substr_solution.ipynb b/recursion_dynamic/longest_common_substring/longest_common_substr_solution.ipynb new file mode 100644 index 0000000..4c90b2b --- /dev/null +++ b/recursion_dynamic/longest_common_substring/longest_common_substr_solution.ipynb @@ -0,0 +1,252 @@ +{ + "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 two strings, find the longest common substring.\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 assume the inputs are valid?\n", + " * No\n", + "* Can we assume the strings are ASCII?\n", + " * Yes\n", + "* Is this case sensitive?\n", + " * Yes\n", + "* Is a substring a contiguous block of chars?\n", + " * Yes\n", + "* Do we expect a string as a result?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* str0 or str1 is None -> Exception\n", + "* str0 or str1 equals 0 -> ''\n", + "* General case\n", + "\n", + "str0 = 'ABCDEFGHIJ'\n", + "str1 = 'FOOBCDBCDE'\n", + "\n", + "result: 'BCDE'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "We'll use bottom up dynamic programming to build a table. \n", + "\n", + "
\n",
+    "\n",
+    "The rows (i) represent str0.\n",
+    "The columns (j) represent str1.\n",
+    "\n",
+    "                       str1\n",
+    "  -------------------------------------------------\n",
+    "  |   |   | A | B | C | D | E | F | G | H | I | J |\n",
+    "  -------------------------------------------------\n",
+    "  |   | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |\n",
+    "  | F | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 |\n",
+    "  | O | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 |\n",
+    "s | O | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 |\n",
+    "t | B | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |\n",
+    "r | C | 0 | 0 | 1 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 |\n",
+    "0 | D | 0 | 0 | 1 | 2 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |\n",
+    "  | B | 0 | 0 | 1 | 2 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |\n",
+    "  | C | 0 | 0 | 1 | 2 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |\n",
+    "  | D | 0 | 0 | 1 | 2 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |\n",
+    "  | E | 0 | 0 | 1 | 2 | 3 | 4 | 4 | 4 | 4 | 4 | 4 |\n",
+    "  -------------------------------------------------\n",
+    "\n",
+    "if str1[j] != str0[i]:\n",
+    "    T[i][j] = max(\n",
+    "        T[i][j - 1],\n",
+    "        T[i - 1][j])\n",
+    "else:\n",
+    "    T[i][j] = T[i - 1][j - 1] + 1\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(m * n), where m is the length of str0 and n is the length of str1\n", + "* Space: O(m * n), where m is the length of str0 and n is the length of str1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class StringCompare(object):\n", + "\n", + " def longest_common_substr(self, str0, str1):\n", + " if str0 is None or str1 is None:\n", + " raise TypeError('str input cannot be None')\n", + " # Add one to number of rows and cols for the dp table's\n", + " # first row of 0's and first col of 0's\n", + " num_rows = len(str0) + 1\n", + " num_cols = len(str1) + 1\n", + " T = [[None] * num_cols for _ in range(num_rows)]\n", + " for i in range(num_rows):\n", + " for j in range(num_cols):\n", + " if i == 0 or j == 0:\n", + " T[i][j] = 0\n", + " elif str0[j - 1] != str1[i - 1]:\n", + " T[i][j] = max(T[i][j - 1],\n", + " T[i - 1][j])\n", + " else:\n", + " T[i][j] = T[i - 1][j - 1] + 1\n", + " results = ''\n", + " i = num_rows - 1\n", + " j = num_cols - 1\n", + " # Walk backwards to determine the substring\n", + " while T[i][j]:\n", + " if T[i][j] == T[i][j - 1]:\n", + " j -= 1\n", + " elif T[i][j] == T[i - 1][j]:\n", + " i -= 1\n", + " elif T[i][j] == T[i - 1][j - 1] + 1:\n", + " results += str1[i - 1]\n", + " i -= 1\n", + " j -= 1\n", + " else:\n", + " raise Exception('Error constructing table')\n", + " # Walking backwards results in a string in reverse order\n", + " return results[::-1] " + ] + }, + { + "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_longest_common_substr.py\n" + ] + } + ], + "source": [ + "%%writefile test_longest_common_substr.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestLongestCommonSubstr(object):\n", + "\n", + " def test_longest_common_substr(self):\n", + " str_comp = StringCompare()\n", + " assert_raises(TypeError, str_comp.longest_common_substr, None, None)\n", + " assert_equal(str_comp.longest_common_substr('', ''), '')\n", + " str0 = 'ABCDEFGHIJ'\n", + " str1 = 'FOOBCDBCDE'\n", + " expected = 'BCDE'\n", + " assert_equal(str_comp.longest_common_substr(str0, str1), expected)\n", + " print('Success: test_longest_common_substr')\n", + "\n", + "\n", + "def main():\n", + " test = TestLongestCommonSubstr()\n", + " test.test_longest_common_substr()\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_longest_common_substr\n" + ] + } + ], + "source": [ + "%run -i test_longest_common_substr.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/longest_common_substring/test_longest_common_substr.py b/recursion_dynamic/longest_common_substring/test_longest_common_substr.py new file mode 100644 index 0000000..8af8fe7 --- /dev/null +++ b/recursion_dynamic/longest_common_substring/test_longest_common_substr.py @@ -0,0 +1,23 @@ +from nose.tools import assert_equal, assert_raises + + +class TestLongestCommonSubstr(object): + + def test_longest_common_substr(self): + str_comp = StringCompare() + assert_raises(TypeError, str_comp.longest_common_substr, None, None) + assert_equal(str_comp.longest_common_substr('', ''), '') + str0 = 'ABCDEFGHIJ' + str1 = 'FOOBCDBCDE' + expected = 'BCDE' + assert_equal(str_comp.longest_common_substr(str0, str1), expected) + print('Success: test_longest_common_substr') + + +def main(): + test = TestLongestCommonSubstr() + test.test_longest_common_substr() + + +if __name__ == '__main__': + main() \ No newline at end of file From 734d3385f62b72258b897319b20952b8d25b98eb Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Tue, 28 Mar 2017 05:04:13 -0400 Subject: [PATCH 17/90] Add knapsack unbounded challenge --- .../knapsack_unbounded/__init__.py | 0 .../knapsack_unbounded_challenge.ipynb | 211 +++++++++++++ .../knapsack_unbounded_solution.ipynb | 298 ++++++++++++++++++ .../test_knapsack_unbounded.py | 29 ++ 4 files changed, 538 insertions(+) create mode 100644 recursion_dynamic/knapsack_unbounded/__init__.py create mode 100644 recursion_dynamic/knapsack_unbounded/knapsack_unbounded_challenge.ipynb create mode 100644 recursion_dynamic/knapsack_unbounded/knapsack_unbounded_solution.ipynb create mode 100644 recursion_dynamic/knapsack_unbounded/test_knapsack_unbounded.py 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 From 67f4ea99b40c6c9201a9459ad5dbadc30740b6c1 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Tue, 28 Mar 2017 05:06:47 -0400 Subject: [PATCH 18/90] Add knapsack 01 challenge --- recursion_dynamic/knapsack_01/__init__.py | 0 .../knapsack_01/knapsack_challenge.ipynb | 232 ++++++++++ .../knapsack_01/knapsack_solution.ipynb | 436 ++++++++++++++++++ .../knapsack_01/test_knapsack.py | 47 ++ 4 files changed, 715 insertions(+) create mode 100644 recursion_dynamic/knapsack_01/__init__.py create mode 100644 recursion_dynamic/knapsack_01/knapsack_challenge.ipynb create mode 100644 recursion_dynamic/knapsack_01/knapsack_solution.ipynb create mode 100644 recursion_dynamic/knapsack_01/test_knapsack.py diff --git a/recursion_dynamic/knapsack_01/__init__.py b/recursion_dynamic/knapsack_01/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/recursion_dynamic/knapsack_01/knapsack_challenge.ipynb b/recursion_dynamic/knapsack_01/knapsack_challenge.ipynb new file mode 100644 index 0000000..81b4107 --- /dev/null +++ b/recursion_dynamic/knapsack_01/knapsack_challenge.ipynb @@ -0,0 +1,232 @@ +{ + "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 which items to select to maximize total value.\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", + " * No, this is the 0/1 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", + "* Can we assume the inputs are valid?\n", + " * No\n", + "* Are the inputs in sorted order by val/weight?\n", + " * Yes, if not we'd need to sort them first\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 2 | 2\n",
+    "b 4 | 2\n",
+    "c 6 | 4\n",
+    "d 9 | 5\n",
+    "\n",
+    "max value = 13\n",
+    "items\n",
+    "  v | w\n",
+    "b 4 | 2\n",
+    "d 9 | 5 \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.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestKnapsack(object):\n", + "\n", + " def test_knapsack_bottom_up(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=2, weight=2))\n", + " items.append(Item(label='b', value=4, weight=2))\n", + " items.append(Item(label='c', value=6, weight=4))\n", + " items.append(Item(label='d', value=9, weight=5))\n", + " total_weight = 8\n", + " expected_value = 13\n", + " results = knapsack.fill_knapsack(items, total_weight)\n", + " assert_equal(results[0].label, 'd')\n", + " assert_equal(results[1].label, 'b')\n", + " total_value = 0\n", + " for item in results:\n", + " total_value += item.value\n", + " assert_equal(total_value, expected_value)\n", + " print('Success: test_knapsack_bottom_up')\n", + "\n", + " def test_knapsack_top_down(self):\n", + " knapsack = KnapsackTopDown()\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=2, weight=2))\n", + " items.append(Item(label='b', value=4, weight=2))\n", + " items.append(Item(label='c', value=6, weight=4))\n", + " items.append(Item(label='d', value=9, weight=5))\n", + " total_weight = 8\n", + " expected_value = 13\n", + " assert_equal(knapsack.fill_knapsack(items, total_weight), expected_value)\n", + " print('Success: test_knapsack_top_down')\n", + "\n", + "def main():\n", + " test = TestKnapsack()\n", + " test.test_knapsack_bottom_up()\n", + " test.test_knapsack_top_down()\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_01/knapsack_solution.ipynb b/recursion_dynamic/knapsack_01/knapsack_solution.ipynb new file mode 100644 index 0000000..aff4319 --- /dev/null +++ b/recursion_dynamic/knapsack_01/knapsack_solution.ipynb @@ -0,0 +1,436 @@ +{ + "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 which items to select to maximize total value.\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", + " * No, this is the 0/1 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", + "* Can we assume the inputs are valid?\n", + " * No\n", + "* Are the inputs in sorted order by val/weight?\n", + " * Yes, if not we'd need to sort them first\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 2 | 2\n",
+    "b 4 | 2\n",
+    "c 6 | 4\n",
+    "d 9 | 5\n",
+    "\n",
+    "max value = 13\n",
+    "items\n",
+    "  v | w\n",
+    "b 4 | 2\n",
+    "d 9 | 5 \n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "We'll use bottom up dynamic programming to build a table.\n", + "\n", + "The solution for the top down approach is also provided below.\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",
+    "i a | 2 | 2 || 0 | 0 | 2 | 2 | 2 | 2 | 2  | 2  | 2  |\n",
+    "  b | 4 | 2 || 0 | 0 | 4 | 4 | 6 | 6 | 6  | 6  | 6  |\n",
+    "  c | 6 | 4 || 0 | 0 | 4 | 4 | 6 | 6 | 10 | 10 | 12 |\n",
+    "  d | 9 | 5 || 0 | 0 | 4 | 4 | 6 | 9 | 10 | 13 | 13 |\n",
+    "    -------------------------------------------------\n",
+    "\n",
+    "i = row\n",
+    "j = col\n",
+    "\n",
+    "if j >= item[i].weight:\n",
+    "    T[i][j] = max(item[i].value + T[i - 1][j - item[i].weight],\n",
+    "                  T[i - 1][j])\n",
+    "else:\n",
+    "    T[i][j] = T[i - 1][j]\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(n * w), where n is the number of items and 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, input_items, total_weight):\n", + " if input_items is None or total_weight is None:\n", + " raise TypeError('input_items or total_weight cannot be None')\n", + " if not input_items or total_weight == 0:\n", + " return 0\n", + " items = list([Item(label='', value=0, weight=0)] + input_items)\n", + " num_rows = len(items)\n", + " num_cols = total_weight + 1\n", + " T = [[None] * num_cols for _ in range(num_rows)]\n", + " for i in range(num_rows):\n", + " for j in range(num_cols):\n", + " if i == 0 or j == 0:\n", + " T[i][j] = 0\n", + " elif j >= items[i].weight:\n", + " T[i][j] = max(items[i].value + T[i - 1][j - items[i].weight],\n", + " T[i - 1][j])\n", + " else:\n", + " T[i][j] = T[i - 1][j]\n", + " results = []\n", + " i = num_rows - 1\n", + " j = num_cols - 1\n", + " while T[i][j] != 0:\n", + " if T[i - 1][j] == T[i][j]:\n", + " i -= 1\n", + " elif T[i][j - 1] == T[i][j]:\n", + " j -= 1\n", + " else:\n", + " results.append(items[i])\n", + " i -= 1\n", + " j -= items[i].weight\n", + " return results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Knapsack Top Down" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class KnapsackTopDown(object):\n", + "\n", + " def fill_knapsack(self, items, total_weight):\n", + " if items is None or total_weight is None:\n", + " raise TypeError('input_items or total_weight cannot be None')\n", + " if not items or not total_weight:\n", + " return 0\n", + " memo = {}\n", + " result = self._fill_knapsack(items, total_weight, memo, index=0)\n", + " return result\n", + "\n", + "\n", + " def _fill_knapsack(self, items, total_weight, memo, index):\n", + " if total_weight < 0:\n", + " return 0\n", + " if not total_weight or index >= len(items):\n", + " return items[index - 1].value\n", + " if (total_weight, len(items) - index - 1) in memo:\n", + " return memo[(total_weight, len(items) - index - 1)] + items[index - 1].value\n", + " results = []\n", + " for i in range(index, len(items)):\n", + " total_weight -= items[i].weight\n", + " result = self._fill_knapsack(items, total_weight, memo, index=i + 1)\n", + " total_weight += items[i].weight\n", + " results.append(result)\n", + " results_index = 0\n", + " for i in range(index, len(items)):\n", + " memo[total_weight, len(items) - i] = max(results[results_index:])\n", + " results_index += 1\n", + " return max(results) + (items[index - 1].value if index > 0 else 0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Knapsack Top Down Alternate" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Result(object):\n", + "\n", + " def __init__(self, total_weight, item):\n", + " self.total_weight = total_weight\n", + " self.item = item\n", + "\n", + " def __repr__(self):\n", + " return 'w:' + str(self.total_weight) + ' i:' + str(self.item)\n", + "\n", + " def __lt__(self, other):\n", + " return self.total_weight < other.total_weight\n", + "\n", + "\n", + "def knapsack_top_down_alt(items, total_weight):\n", + " if items is None or total_weight is None:\n", + " raise TypeError('input_items or total_weight cannot be None')\n", + " if not items or not total_weight:\n", + " return 0\n", + " memo = {}\n", + " result = _knapsack_top_down_alt(items, total_weight, memo, index=0)\n", + " curr_item = result.item\n", + " curr_weight = curr_item.weight\n", + " picked_items = [curr_item]\n", + " while curr_weight > 0:\n", + " total_weight -= curr_item.weight\n", + " curr_item = memo[(total_weight, len(items) - len(picked_items))].item\n", + " return result\n", + "\n", + "\n", + "def _knapsack_top_down_alt(items, total_weight, memo, index):\n", + " if total_weight < 0:\n", + " return Result(total_weight=0, item=None)\n", + " if not total_weight or index >= len(items):\n", + " return Result(total_weight=items[index - 1].value, item=items[index - 1])\n", + " if (total_weight, len(items) - index - 1) in memo:\n", + " weight=memo[(total_weight, \n", + " len(items) - index - 1)].total_weight + items[index - 1].value\n", + " return Result(total_weight=weight,\n", + " item=items[index-1])\n", + " results = []\n", + " for i in range(index, len(items)):\n", + " total_weight -= items[i].weight\n", + " result = _knapsack_top_down_alt(items, total_weight, memo, index=i + 1)\n", + " total_weight += items[i].weight\n", + " results.append(result)\n", + " results_index = 0\n", + " for i in range(index, len(items)):\n", + " memo[(total_weight, len(items) - i)] = max(results[results_index:])\n", + " results_index += 1\n", + " if index == 0:\n", + " result_item = memo[(total_weight, len(items) - 1)].item\n", + " else:\n", + " result_item = items[index - 1]\n", + " weight = max(results).total_weight + (items[index - 1].value if index > 0 else 0)\n", + " return Result(total_weight=weight,\n", + " item=result_item)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Unit Test" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting test_knapsack.py\n" + ] + } + ], + "source": [ + "%%writefile test_knapsack.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestKnapsack(object):\n", + "\n", + " def test_knapsack_bottom_up(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=2, weight=2))\n", + " items.append(Item(label='b', value=4, weight=2))\n", + " items.append(Item(label='c', value=6, weight=4))\n", + " items.append(Item(label='d', value=9, weight=5))\n", + " total_weight = 8\n", + " expected_value = 13\n", + " results = knapsack.fill_knapsack(items, total_weight)\n", + " assert_equal(results[0].label, 'd')\n", + " assert_equal(results[1].label, 'b')\n", + " total_value = 0\n", + " for item in results:\n", + " total_value += item.value\n", + " assert_equal(total_value, expected_value)\n", + " print('Success: test_knapsack_bottom_up')\n", + "\n", + " def test_knapsack_top_down(self):\n", + " knapsack = KnapsackTopDown()\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=2, weight=2))\n", + " items.append(Item(label='b', value=4, weight=2))\n", + " items.append(Item(label='c', value=6, weight=4))\n", + " items.append(Item(label='d', value=9, weight=5))\n", + " total_weight = 8\n", + " expected_value = 13\n", + " assert_equal(knapsack.fill_knapsack(items, total_weight), expected_value)\n", + " print('Success: test_knapsack_top_down')\n", + "\n", + "def main():\n", + " test = TestKnapsack()\n", + " test.test_knapsack_bottom_up()\n", + " test.test_knapsack_top_down()\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " main()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Success: test_knapsack_bottom_up\n", + "Success: test_knapsack_top_down\n" + ] + } + ], + "source": [ + "%run -i test_knapsack.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_01/test_knapsack.py b/recursion_dynamic/knapsack_01/test_knapsack.py new file mode 100644 index 0000000..5388b1b --- /dev/null +++ b/recursion_dynamic/knapsack_01/test_knapsack.py @@ -0,0 +1,47 @@ +from nose.tools import assert_equal, assert_raises + + +class TestKnapsack(object): + + def test_knapsack_bottom_up(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=2, weight=2)) + items.append(Item(label='b', value=4, weight=2)) + items.append(Item(label='c', value=6, weight=4)) + items.append(Item(label='d', value=9, weight=5)) + total_weight = 8 + expected_value = 13 + results = knapsack.fill_knapsack(items, total_weight) + assert_equal(results[0].label, 'd') + assert_equal(results[1].label, 'b') + total_value = 0 + for item in results: + total_value += item.value + assert_equal(total_value, expected_value) + print('Success: test_knapsack_bottom_up') + + def test_knapsack_top_down(self): + knapsack = KnapsackTopDown() + assert_raises(TypeError, knapsack.fill_knapsack, None, None) + assert_equal(knapsack.fill_knapsack(0, 0), 0) + items = [] + items.append(Item(label='a', value=2, weight=2)) + items.append(Item(label='b', value=4, weight=2)) + items.append(Item(label='c', value=6, weight=4)) + items.append(Item(label='d', value=9, weight=5)) + total_weight = 8 + expected_value = 13 + assert_equal(knapsack.fill_knapsack(items, total_weight), expected_value) + print('Success: test_knapsack_top_down') + +def main(): + test = TestKnapsack() + test.test_knapsack_bottom_up() + test.test_knapsack_top_down() + + +if __name__ == '__main__': + main() \ No newline at end of file From 460406097d2e4c5a15615159e5aa8807f75a3584 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Tue, 28 Mar 2017 05:07:25 -0400 Subject: [PATCH 19/90] Add grid path challenge --- recursion_dynamic/grid_path/__init__.py | 0 .../grid_path/grid_path_challenge.ipynb | 212 ++++++++++++++ .../grid_path/grid_path_solution.ipynb | 276 ++++++++++++++++++ recursion_dynamic/grid_path/test_grid_path.py | 39 +++ 4 files changed, 527 insertions(+) create mode 100644 recursion_dynamic/grid_path/__init__.py create mode 100644 recursion_dynamic/grid_path/grid_path_challenge.ipynb create mode 100644 recursion_dynamic/grid_path/grid_path_solution.ipynb create mode 100644 recursion_dynamic/grid_path/test_grid_path.py 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 From 88d860076b1340ddbbb3c9c5712362bbb235cd53 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Tue, 28 Mar 2017 05:09:48 -0400 Subject: [PATCH 20/90] Add coin change min challenge --- recursion_dynamic/coin_change_min/__init__.py | 0 .../coin_change_min_challenge.ipynb | 169 ++++++++++++ .../coin_change_min_solution.ipynb | 242 ++++++++++++++++++ .../coin_change_min/test_coin_change_min.py | 22 ++ 4 files changed, 433 insertions(+) create mode 100644 recursion_dynamic/coin_change_min/__init__.py create mode 100644 recursion_dynamic/coin_change_min/coin_change_min_challenge.ipynb create mode 100644 recursion_dynamic/coin_change_min/coin_change_min_solution.ipynb create mode 100644 recursion_dynamic/coin_change_min/test_coin_change_min.py diff --git a/recursion_dynamic/coin_change_min/__init__.py b/recursion_dynamic/coin_change_min/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/recursion_dynamic/coin_change_min/coin_change_min_challenge.ipynb b/recursion_dynamic/coin_change_min/coin_change_min_challenge.ipynb new file mode 100644 index 0000000..5acd777 --- /dev/null +++ b/recursion_dynamic/coin_change_min/coin_change_min_challenge.ipynb @@ -0,0 +1,169 @@ +{ + "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: Determine the minimum number of ways to make n cents, given coins of denominations less than n cents.\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", + "* Do the coins have to reach exactly n cents?\n", + " * Yes\n", + "* Can we assume we have an infinite number of coins to make n cents?\n", + " * Yes\n", + "* Do we need to report the combination(s) of coins that represent the minimum?\n", + " * No\n", + "* Can we assume the coin denominations are given in sorted order?\n", + " * No\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* coins: None or n: None -> Exception\n", + "* coins: [] or n: 0 -> 0\n", + "* coins: [1, 2, 3] or [3, 2, 1] -> 2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "Refer to the [Solution Notebook](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/coin_change_min/coin_change_min_solution.ipynb). 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 CoinChanger(object):\n", + "\n", + " def make_change(self, coins, total):\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_coin_change_min.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestCoinChange(object):\n", + "\n", + " def test_coin_change(self):\n", + " coin_changer = CoinChanger()\n", + " assert_raises(TypeError, coin_changer.make_change, None, None)\n", + " assert_equal(coin_changer.make_change([], 0), 0)\n", + " assert_equal(coin_changer.make_change([1, 2, 3], 5), 2)\n", + " assert_equal(coin_changer.make_change([3, 2, 1], 5), 2)\n", + " assert_equal(coin_changer.make_change([3, 2, 1], 8), 3)\n", + " print('Success: test_coin_change')\n", + "\n", + "\n", + "def main():\n", + " test = TestCoinChange()\n", + " test.test_coin_change()\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " main()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Solution Notebook\n", + "\n", + "Review the [Solution Notebook](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/coin_change_min/coin_change_min_solution.ipynb) 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/coin_change_min/coin_change_min_solution.ipynb b/recursion_dynamic/coin_change_min/coin_change_min_solution.ipynb new file mode 100644 index 0000000..d1960fb --- /dev/null +++ b/recursion_dynamic/coin_change_min/coin_change_min_solution.ipynb @@ -0,0 +1,242 @@ +{ + "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: Determine the minimum number of ways to make n cents, given coins of denominations less than n cents.\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", + "* Do the coins have to reach exactly n cents?\n", + " * Yes\n", + "* Can we assume we have an infinite number of coins to make n cents?\n", + " * Yes\n", + "* Do we need to report the combination(s) of coins that represent the minimum?\n", + " * No\n", + "* Can we assume the coin denominations are given in sorted order?\n", + " * No\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* coins: None or n: None -> Exception\n", + "* coins: [] or n: 0 -> 0\n", + "* coins: [1, 2, 3] or [3, 2, 1], n: 5 -> 2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "We'll use top down dynamic programming with memoization.\n", + "\n", + "* Base case: If the total is 0, return 0\n", + "* If the total is already in the memo, return it\n", + "* For each coin denomination:\n", + " * If this coin > total, continue\n", + " * Recurse, decreasing total by the coin denomination, keeping track of the min return\n", + "* Set memo[total] to the min value + 1\n", + "* Return the memo[total]\n", + "\n", + "
\n",
+    "total: 5\n",
+    "coins: [1,2,3]\n",
+    "memo key: total value: min ways\n",
+    "memo = {\n",
+    "    1: 1,\n",
+    "    2: 1,\n",
+    "    3: 1,\n",
+    "    4: 2,\n",
+    "    5: 2\n",
+    "}\n",
+    "                              5\n",
+    "                           1, 2, 3\n",
+    "                          /\n",
+    "                         4\n",
+    "                      1, 2, 3\n",
+    "                     /\n",
+    "                    3\n",
+    "              1,    2,    3\n",
+    "             /       \\     \\____\n",
+    "            2         1         0\n",
+    "         1, 2, 3   1, 2, 3\n",
+    "        /   |\n",
+    "       1    0\n",
+    "    1, 2, 3\n",
+    "   /\n",
+    "  0\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(t * n), where t is the total and n is the number of coin denominations\n", + "* Space: O(t) for the recursion depth" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\n", + "\n", + "\n", + "class CoinChanger(object):\n", + "\n", + " def make_change(self, coins, total):\n", + " if coins is None or total is None:\n", + " raise TypeError('coins or total cannot be None')\n", + " if not coins or total == 0:\n", + " return 0\n", + " cache = {}\n", + " return self._make_change(coins, total, cache)\n", + "\n", + " def _make_change(self, coins, total, cache):\n", + " if total == 0:\n", + " return 0\n", + " if total in cache:\n", + " return cache[total]\n", + " min_ways = sys.maxsize\n", + " for coin in coins:\n", + " if total - coin < 0:\n", + " continue\n", + " ways = self._make_change(coins, total - coin, cache)\n", + " if ways < min_ways:\n", + " min_ways = ways\n", + " cache[total] = min_ways + 1\n", + " return cache[total]" + ] + }, + { + "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_coin_change_min.py\n" + ] + } + ], + "source": [ + "%%writefile test_coin_change_min.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestCoinChange(object):\n", + "\n", + " def test_coin_change(self):\n", + " coin_changer = CoinChanger()\n", + " assert_raises(TypeError, coin_changer.make_change, None, None)\n", + " assert_equal(coin_changer.make_change([], 0), 0)\n", + " assert_equal(coin_changer.make_change([1, 2, 3], 5), 2)\n", + " assert_equal(coin_changer.make_change([3, 2, 1], 5), 2)\n", + " assert_equal(coin_changer.make_change([3, 2, 1], 8), 3)\n", + " print('Success: test_coin_change')\n", + "\n", + "\n", + "def main():\n", + " test = TestCoinChange()\n", + " test.test_coin_change()\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_coin_change\n" + ] + } + ], + "source": [ + "%run -i test_coin_change_min.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/coin_change_min/test_coin_change_min.py b/recursion_dynamic/coin_change_min/test_coin_change_min.py new file mode 100644 index 0000000..2440185 --- /dev/null +++ b/recursion_dynamic/coin_change_min/test_coin_change_min.py @@ -0,0 +1,22 @@ +from nose.tools import assert_equal, assert_raises + + +class TestCoinChange(object): + + def test_coin_change(self): + coin_changer = CoinChanger() + assert_raises(TypeError, coin_changer.make_change, None, None) + assert_equal(coin_changer.make_change([], 0), 0) + assert_equal(coin_changer.make_change([1, 2, 3], 5), 2) + assert_equal(coin_changer.make_change([3, 2, 1], 5), 2) + assert_equal(coin_changer.make_change([3, 2, 1], 8), 3) + print('Success: test_coin_change') + + +def main(): + test = TestCoinChange() + test.test_coin_change() + + +if __name__ == '__main__': + main() \ No newline at end of file From a97d35f86a478e25834cbb14d623160222a28349 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Tue, 28 Mar 2017 05:10:10 -0400 Subject: [PATCH 21/90] Add coin change challenge --- recursion_dynamic/coin_change/__init__.py | 0 .../coin_change/coin_change_challenge.ipynb | 164 +++++++++++++ .../coin_change/coin_change_solution.ipynb | 230 ++++++++++++++++++ .../coin_change/test_coin_change.py | 20 ++ 4 files changed, 414 insertions(+) create mode 100644 recursion_dynamic/coin_change/__init__.py create mode 100644 recursion_dynamic/coin_change/coin_change_challenge.ipynb create mode 100644 recursion_dynamic/coin_change/coin_change_solution.ipynb create mode 100644 recursion_dynamic/coin_change/test_coin_change.py diff --git a/recursion_dynamic/coin_change/__init__.py b/recursion_dynamic/coin_change/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/recursion_dynamic/coin_change/coin_change_challenge.ipynb b/recursion_dynamic/coin_change/coin_change_challenge.ipynb new file mode 100644 index 0000000..c552f50 --- /dev/null +++ b/recursion_dynamic/coin_change/coin_change_challenge.ipynb @@ -0,0 +1,164 @@ +{ + "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: Determine the total number of unique ways to make n cents, given coins of denominations less than n cents.\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", + "* Do the coins have to reach exactly n cents?\n", + " * Yes\n", + "* Can we assume we have an infinite number of coins to make n cents?\n", + " * Yes\n", + "* Do we need to report the combination(s) of coins that represent the minimum?\n", + " * No\n", + "* Can we assume the coin denominations are given in sorted order?\n", + " * No\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* coins: None or n: None -> Exception\n", + "* coins: [] or n: 0 -> 0\n", + "* coins: [1, 2, 3], n: 5 -> 5" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "Refer to the [Solution Notebook](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/coin_change/coin_change_solution.ipynb). 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 CoinChanger(object):\n", + "\n", + " def make_change(self, coins, total):\n", + " # TODO: Implement me\n", + " return n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Unit Test\n", + "\n", + "\n", + "\n", + "**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_coin_change.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class Challenge(object):\n", + "\n", + " def test_coin_change(self):\n", + " coin_changer = CoinChanger()\n", + " assert_equal(coin_changer.make_change([1, 2], 0), 0)\n", + " assert_equal(coin_changer.make_change([1, 2, 3], 5), 5)\n", + " assert_equal(coin_changer.make_change([1, 5, 25, 50], 10), 3)\n", + " print('Success: test_coin_change')\n", + "\n", + "\n", + "def main():\n", + " test = Challenge()\n", + " test.test_coin_change()\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " main()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Solution Notebook\n", + "\n", + "Review the [Solution Notebook](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/coin_change/coin_change_solution.ipynb) 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/coin_change/coin_change_solution.ipynb b/recursion_dynamic/coin_change/coin_change_solution.ipynb new file mode 100644 index 0000000..44da41f --- /dev/null +++ b/recursion_dynamic/coin_change/coin_change_solution.ipynb @@ -0,0 +1,230 @@ +{ + "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: Determine the total number of unique ways to make n cents, given coins of denominations less than n cents.\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", + "* Do the coins have to reach exactly n cents?\n", + " * Yes\n", + "* Can we assume we have an infinite number of coins to make n cents?\n", + " * Yes\n", + "* Do we need to report the combination(s) of coins that represent the minimum?\n", + " * No\n", + "* Can we assume the coin denominations are given in sorted order?\n", + " * No\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* coins: None or n: None -> Exception\n", + "* coins: [] or n: 0 -> 0\n", + "* coins: [1, 2, 3], n: 5 -> 5" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "We'll use a bottom-up dynamic programming approach.\n", + "\n", + "
\n",
+    "The rows (i) represent the coin values.\n",
+    "The columns (j) represent the totals.\n",
+    "\n",
+    "  -------------------------\n",
+    "  | 0 | 1 | 2 | 3 | 4 | 5 |\n",
+    "  -------------------------\n",
+    "0 | 1 | 0 | 0 | 0 | 0 | 0 |\n",
+    "1 | 1 | 1 | 1 | 1 | 1 | 1 |\n",
+    "2 | 1 | 1 | 2 | 2 | 3 | 3 |\n",
+    "3 | 1 | 1 | 2 | 3 | 4 | 5 |\n",
+    "  -------------------------\n",
+    "\n",
+    "Number of ways to get total n with coin[n] equals:\n",
+    "* Number of ways to get total n with coin[n - 1] plus\n",
+    "* Number of ways to get total n - coin[n]\n",
+    "\n",
+    "if j == 0:\n",
+    "    T[i][j] = 1\n",
+    "if row == 0:\n",
+    "    T[i][j] = 0\n",
+    "if coins[i] >= j\n",
+    "    T[i][j] = T[i - 1][j] + T[i][j - coins[i]]\n",
+    "else:\n",
+    "    T[i][j] = T[i - 1][j]\n",
+    "\n",
+    "The answer will be in the bottom right corner of the matrix.\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(i * j)\n", + "* Space: O(i * j)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class CoinChanger(object):\n", + "\n", + " def make_change(self, coins, total):\n", + " if coins is None or total is None:\n", + " return None\n", + " if not coins or total == 0:\n", + " return 0\n", + " coins = [0] + coins\n", + " num_rows = len(coins)\n", + " num_cols = total + 1\n", + " T = [[None] * num_cols for _ in range(num_rows)]\n", + " for i in range(num_rows):\n", + " for j in range(num_cols):\n", + " if i == 0:\n", + " T[i][j] = 0\n", + " continue\n", + " if j == 0:\n", + " T[i][j] = 1\n", + " continue\n", + " if coins[i] <= j:\n", + " T[i][j] = T[i - 1][j] + T[i][j - coins[i]]\n", + " else:\n", + " T[i][j] = T[i - 1][j]\n", + " return T[num_rows - 1][num_cols - 1]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Unit Test\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting test_coin_change.py\n" + ] + } + ], + "source": [ + "%%writefile test_coin_change.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class Challenge(object):\n", + "\n", + " def test_coin_change(self):\n", + " coin_changer = CoinChanger()\n", + " assert_equal(coin_changer.make_change([1, 2], 0), 0)\n", + " assert_equal(coin_changer.make_change([1, 2, 3], 5), 5)\n", + " assert_equal(coin_changer.make_change([1, 5, 25, 50], 10), 3)\n", + " print('Success: test_coin_change')\n", + "\n", + "\n", + "def main():\n", + " test = Challenge()\n", + " test.test_coin_change()\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_coin_change\n" + ] + } + ], + "source": [ + "%run -i test_coin_change.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/coin_change/test_coin_change.py b/recursion_dynamic/coin_change/test_coin_change.py new file mode 100644 index 0000000..8d8efb2 --- /dev/null +++ b/recursion_dynamic/coin_change/test_coin_change.py @@ -0,0 +1,20 @@ +from nose.tools import assert_equal + + +class Challenge(object): + + def test_coin_change(self): + coin_changer = CoinChanger() + assert_equal(coin_changer.make_change([1, 2], 0), 0) + assert_equal(coin_changer.make_change([1, 2, 3], 5), 5) + assert_equal(coin_changer.make_change([1, 5, 25, 50], 10), 3) + print('Success: test_coin_change') + + +def main(): + test = Challenge() + test.test_coin_change() + + +if __name__ == '__main__': + main() \ No newline at end of file From f469ccd0888f5c79e456d9eb1f7cc6475b56de8a Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Tue, 28 Mar 2017 05:10:39 -0400 Subject: [PATCH 22/90] Add sum two challenge --- online_judges/sum_two/sum_two_challenge.ipynb | 164 +++++++++++++ online_judges/sum_two/sum_two_solution.ipynb | 225 ++++++++++++++++++ online_judges/sum_two/test_sum_two.py | 21 ++ 3 files changed, 410 insertions(+) create mode 100644 online_judges/sum_two/sum_two_challenge.ipynb create mode 100644 online_judges/sum_two/sum_two_solution.ipynb create mode 100644 online_judges/sum_two/test_sum_two.py diff --git a/online_judges/sum_two/sum_two_challenge.ipynb b/online_judges/sum_two/sum_two_challenge.ipynb new file mode 100644 index 0000000..bf48578 --- /dev/null +++ b/online_judges/sum_two/sum_two_challenge.ipynb @@ -0,0 +1,164 @@ +{ + "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: Sum of Two Integers.\n", + "\n", + "See the [LeetCode](https://leetcode.com/problems/sum-of-two-integers/) problem page.\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 assume we're working with 32 bit ints?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * No, check for None\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "See the [LeetCode](https://leetcode.com/problems/sum-of-two-integers/) problem page." + ] + }, + { + "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 Solution(object):\n", + "\n", + " def sum_two(self, val):\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_sum_two.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSumTwo(object):\n", + "\n", + " def test_sum_two(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.sum_two, None)\n", + " assert_equal(solution.sum_two(5, 7), 12)\n", + " assert_equal(solution.sum_two(-5, -7), -12)\n", + " assert_equal(solution.sum_two(5, -7), -2)\n", + " print('Success: test_sum_two')\n", + "\n", + "\n", + "def main():\n", + " test = TestSumTwo()\n", + " test.test_sum_two()\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/online_judges/sum_two/sum_two_solution.ipynb b/online_judges/sum_two/sum_two_solution.ipynb new file mode 100644 index 0000000..e25fe50 --- /dev/null +++ b/online_judges/sum_two/sum_two_solution.ipynb @@ -0,0 +1,225 @@ +{ + "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: Sum of Two Integers.\n", + "\n", + "See the [LeetCode](https://leetcode.com/problems/sum-of-two-integers/) problem page.\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 assume we're working with 32 bit ints?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * No, check for None\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "
\n",
+    "* None input -> TypeError\n",
+    "* 5, 7 -> 12\n",
+    "* -5, -7 -> -12\n",
+    "* 5, -7 -> -2\n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "We'll look at the following example, adding a and b:\n", + "\n", + "
\n",
+    "a 0111 \n",
+    "b 0101\n",
+    "
\n", + "\n", + "First, add a and b, without worrying about the carry (0+0=0, 0+1=1, 1+1=0):\n", + "\n", + "result = a ^ b = 0010\n", + "\n", + "Next, calculate the carry (1+1=2). We'll need to left shift one to prepare for the next iteration when we move to the next most significant bit:\n", + "\n", + "carry = (a&b) << 1 = 1010\n", + "\n", + "If the carry is not zero, we'll need to add the carry to the result. Recusively call the function, passing in result and carry.\n", + "\n", + "Below are the values of a, b, and the carry of a = 7 and b = 5, producing the result of 12.\n", + "\n", + "
\n",
+    "a 0111 \n",
+    "b 0101 \n",
+    "----- \n",
+    "c 0101 \n",
+    "a 0010 \n",
+    "b 1010 \n",
+    "----- \n",
+    "c 0010 \n",
+    "a 1000 \n",
+    "b 0100 \n",
+    "----- \n",
+    "c 0000 \n",
+    "a 1100 \n",
+    "b 0000\n",
+    "\n",
+    "c = carry = 0, return the result 1100\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(b), where b is the number of bits\n", + "* Space: O(b), where b is the number of bits" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def sum_two(self, a, b):\n", + " if a is None or b is None:\n", + " raise TypeError('a or b cannot be None')\n", + " result = a ^ b;\n", + " carry = (a&b) << 1\n", + " if carry != 0:\n", + " return self.sum_two(result, carry)\n", + " return result;" + ] + }, + { + "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_sum_two.py\n" + ] + } + ], + "source": [ + "%%writefile test_sum_two.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSumTwo(object):\n", + "\n", + " def test_sum_two(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.sum_two, None)\n", + " assert_equal(solution.sum_two(5, 7), 12)\n", + " assert_equal(solution.sum_two(-5, -7), -12)\n", + " assert_equal(solution.sum_two(5, -7), -2)\n", + " print('Success: test_sum_two')\n", + "\n", + "\n", + "def main():\n", + " test = TestSumTwo()\n", + " test.test_sum_two()\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " main()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false, + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Success: test_sum_two\n" + ] + } + ], + "source": [ + "%run -i test_sum_two.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/online_judges/sum_two/test_sum_two.py b/online_judges/sum_two/test_sum_two.py new file mode 100644 index 0000000..82a5ada --- /dev/null +++ b/online_judges/sum_two/test_sum_two.py @@ -0,0 +1,21 @@ +from nose.tools import assert_equal, assert_raises + + +class TestSumTwo(object): + + def test_sum_two(self): + solution = Solution() + assert_raises(TypeError, solution.sum_two, None) + assert_equal(solution.sum_two(5, 7), 12) + assert_equal(solution.sum_two(-5, -7), -12) + assert_equal(solution.sum_two(5, -7), -2) + print('Success: test_sum_two') + + +def main(): + test = TestSumTwo() + test.test_sum_two() + + +if __name__ == '__main__': + main() \ No newline at end of file From c9574ae7fd01ab0103dfca26f14df0ccc130edda Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Tue, 28 Mar 2017 05:11:10 -0400 Subject: [PATCH 23/90] Add sub two challenge --- online_judges/sub_two/sub_two_challenge.ipynb | 171 ++++++++++++++ online_judges/sub_two/sub_two_solution.ipynb | 210 ++++++++++++++++++ online_judges/sub_two/test_sub_two.py | 22 ++ 3 files changed, 403 insertions(+) create mode 100644 online_judges/sub_two/sub_two_challenge.ipynb create mode 100644 online_judges/sub_two/sub_two_solution.ipynb create mode 100644 online_judges/sub_two/test_sub_two.py diff --git a/online_judges/sub_two/sub_two_challenge.ipynb b/online_judges/sub_two/sub_two_challenge.ipynb new file mode 100644 index 0000000..e69b3d9 --- /dev/null +++ b/online_judges/sub_two/sub_two_challenge.ipynb @@ -0,0 +1,171 @@ +{ + "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: Sum of Two Integers (Subtraction Variant).\n", + "\n", + "See the [LeetCode](https://leetcode.com/problems/sum-of-two-integers/) problem page.\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 assume we're working with 32 bit ints?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * No, check for None\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "
\n",
+    "* None input -> TypeError\n",
+    "* 7, 5 -> 2\n",
+    "* -5, -7 -> 2\n",
+    "* -5, 7 -> -12\n",
+    "* 5, -7 -> 12\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": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def sub_two(self, val):\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_sub_two.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSubTwo(object):\n", + "\n", + " def test_sub_two(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.sub_two, None)\n", + " assert_equal(solution.sub_two(7, 5), 2)\n", + " assert_equal(solution.sub_two(-5, -7), 2)\n", + " assert_equal(solution.sub_two(-5, 7), -12)\n", + " assert_equal(solution.sub_two(5, -7), 12)\n", + " print('Success: test_sub_two')\n", + "\n", + "\n", + "def main():\n", + " test = TestSubTwo()\n", + " test.test_sub_two()\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/online_judges/sub_two/sub_two_solution.ipynb b/online_judges/sub_two/sub_two_solution.ipynb new file mode 100644 index 0000000..bc67a3e --- /dev/null +++ b/online_judges/sub_two/sub_two_solution.ipynb @@ -0,0 +1,210 @@ +{ + "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: Sum of Two Integers (Subtraction Variant).\n", + "\n", + "See the [LeetCode](https://leetcode.com/problems/sum-of-two-integers/) problem page.\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 assume we're working with 32 bit ints?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * No, check for None\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "
\n",
+    "* None input -> TypeError\n",
+    "* 7, 5 -> 2\n",
+    "* -5, -7 -> 2\n",
+    "* -5, 7 -> -12\n",
+    "* 5, -7 -> 12\n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "We'll look at the following example, subtracting a and b:\n", + "\n", + "
\n",
+    "a 0110 = 6 \n",
+    "b 0101 = 5\n",
+    "
\n", + "\n", + "First, subtract a and b, without worrying about the borrow (0-0=0, 0-1=1, 1-1=0):\n", + "\n", + "result = a ^ b = 0011\n", + "\n", + "Next, calculate the borrow (0-1=1). We'll need to left shift one to prepare for the next iteration when we move to the next most significant bit:\n", + "\n", + "~a = 1001\n", + " b = 0101\n", + "~a & b = 0001\n", + "\n", + "borrow = (~a&b) << 1 = 0010\n", + "\n", + "If the borrow is not zero, we'll need to subtract the borrow from the result. Recusively call the function, passing in result and borrow.\n", + "\n", + "Complexity:\n", + "* Time: O(b), where b is the number of bits\n", + "* Space: O(b), where b is the number of bits" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def sub_two(self, a, b):\n", + " if a is None or b is None:\n", + " raise TypeError('a or b cannot be None')\n", + " result = a ^ b;\n", + " borrow = (~a&b) << 1\n", + " if borrow != 0:\n", + " return self.sub_two(result, borrow)\n", + " return result;" + ] + }, + { + "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_sub_two.py\n" + ] + } + ], + "source": [ + "%%writefile test_sub_two.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSubTwo(object):\n", + "\n", + " def test_sub_two(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.sub_two, None)\n", + " assert_equal(solution.sub_two(7, 5), 2)\n", + " assert_equal(solution.sub_two(-5, -7), 2)\n", + " assert_equal(solution.sub_two(-5, 7), -12)\n", + " assert_equal(solution.sub_two(5, -7), 12)\n", + " print('Success: test_sub_two')\n", + "\n", + "\n", + "def main():\n", + " test = TestSubTwo()\n", + " test.test_sub_two()\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " main()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false, + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Success: test_sub_two\n" + ] + } + ], + "source": [ + "%run -i test_sub_two.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/online_judges/sub_two/test_sub_two.py b/online_judges/sub_two/test_sub_two.py new file mode 100644 index 0000000..f022a3f --- /dev/null +++ b/online_judges/sub_two/test_sub_two.py @@ -0,0 +1,22 @@ +from nose.tools import assert_equal, assert_raises + + +class TestSubTwo(object): + + def test_sub_two(self): + solution = Solution() + assert_raises(TypeError, solution.sub_two, None) + assert_equal(solution.sub_two(7, 5), 2) + assert_equal(solution.sub_two(-5, -7), 2) + assert_equal(solution.sub_two(-5, 7), -12) + assert_equal(solution.sub_two(5, -7), 12) + print('Success: test_sub_two') + + +def main(): + test = TestSubTwo() + test.test_sub_two() + + +if __name__ == '__main__': + main() \ No newline at end of file From 4ebb379eea49206029172ac8837d29c923459f16 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Tue, 28 Mar 2017 05:11:26 -0400 Subject: [PATCH 24/90] Add str diff challenge --- online_judges/str_diff/__init__.py | 0 .../str_diff/str_diff_challenge.ipynb | 165 +++++++++++++++ .../str_diff/str_diff_solution.ipynb | 195 ++++++++++++++++++ online_judges/str_diff/test_str_diff.py | 19 ++ 4 files changed, 379 insertions(+) create mode 100644 online_judges/str_diff/__init__.py create mode 100644 online_judges/str_diff/str_diff_challenge.ipynb create mode 100644 online_judges/str_diff/str_diff_solution.ipynb create mode 100644 online_judges/str_diff/test_str_diff.py diff --git a/online_judges/str_diff/__init__.py b/online_judges/str_diff/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/online_judges/str_diff/str_diff_challenge.ipynb b/online_judges/str_diff/str_diff_challenge.ipynb new file mode 100644 index 0000000..85b3055 --- /dev/null +++ b/online_judges/str_diff/str_diff_challenge.ipynb @@ -0,0 +1,165 @@ +{ + "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: Find the Difference.\n", + "\n", + "See the [LeetCode](https://leetcode.com/problems/find-the-difference/) problem page.\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 assume the strings are ASCII?\n", + " * Yes\n", + "* Is case important?\n", + " * The strings are lower case\n", + "* Can we assume the inputs are valid?\n", + " * No, check for None\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None input -> TypeError\n", + "* 'aaabbcdd', 'abdbacade' -> 'e'" + ] + }, + { + "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 Solution(object):\n", + "\n", + " def find_diff(self, s, t):\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_str_diff.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestFindDiff(object):\n", + "\n", + " def test_find_diff(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.find_diff, None)\n", + " assert_equal(solution.find_diff('aaabbcdd', 'abdbacade'), 'e')\n", + " print('Success: test_find_diff')\n", + "\n", + "\n", + "def main():\n", + " test = TestFindDiff()\n", + " test.test_find_diff()\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/online_judges/str_diff/str_diff_solution.ipynb b/online_judges/str_diff/str_diff_solution.ipynb new file mode 100644 index 0000000..1d738da --- /dev/null +++ b/online_judges/str_diff/str_diff_solution.ipynb @@ -0,0 +1,195 @@ +{ + "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: Find the Difference.\n", + "\n", + "See the [LeetCode](https://leetcode.com/problems/find-the-difference/) problem page.\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 assume the strings are ASCII?\n", + " * Yes\n", + "* Is case important?\n", + " * The strings are lower case\n", + "* Can we assume the inputs are valid?\n", + " * No, check for None\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None input -> TypeError\n", + "* 'aaabbcdd', 'abdbacade' -> 'e'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "* Keep a dictionary of seen values in s\n", + "* Loop through t, decrementing the seen values\n", + " * If the char is not there or if the decrement results in a negative value, return the char\n", + "\n", + "Complexity:\n", + "* Time: O(m+n), where m and n are the lengths of s, t\n", + "* Space: O(h), for the dict, where h is the unique chars in s" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def find_diff(self, s, t):\n", + " if s is None or t is None:\n", + " raise TypeError('s or t cannot be None')\n", + " seen = {}\n", + " for char in s:\n", + " if char in seen:\n", + " seen[char] += 1\n", + " else:\n", + " seen[char] = 1\n", + " for char in t:\n", + " try:\n", + " seen[char] -= 1\n", + " except KeyError:\n", + " return char\n", + " if seen[char] < 0:\n", + " return char\n", + " return None" + ] + }, + { + "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_str_diff.py\n" + ] + } + ], + "source": [ + "%%writefile test_str_diff.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestFindDiff(object):\n", + "\n", + " def test_find_diff(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.find_diff, None)\n", + " assert_equal(solution.find_diff('aaabbcdd', 'abdbacade'), 'e')\n", + " print('Success: test_find_diff')\n", + "\n", + "\n", + "def main():\n", + " test = TestFindDiff()\n", + " test.test_find_diff()\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_find_diff\n" + ] + } + ], + "source": [ + "%run -i test_str_diff.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/online_judges/str_diff/test_str_diff.py b/online_judges/str_diff/test_str_diff.py new file mode 100644 index 0000000..eba7f7d --- /dev/null +++ b/online_judges/str_diff/test_str_diff.py @@ -0,0 +1,19 @@ +from nose.tools import assert_equal, assert_raises + + +class TestFindDiff(object): + + def test_find_diff(self): + solution = Solution() + assert_raises(TypeError, solution.find_diff, None) + assert_equal(solution.find_diff('aaabbcdd', 'abdbacade'), 'e') + print('Success: test_find_diff') + + +def main(): + test = TestFindDiff() + test.test_find_diff() + + +if __name__ == '__main__': + main() \ No newline at end of file From 923e98e4cf8263a0202f640fe7d87959c4c5aeaf Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Tue, 28 Mar 2017 05:12:27 -0400 Subject: [PATCH 25/90] Add sentence screen fit challenge --- online_judges/sentence_screen_fit/__init__.py | 0 .../sentence_screen_fit_challenge.ipynb | 239 +++++++++++++ .../sentence_screen_fit_solution.ipynb | 321 ++++++++++++++++++ .../test_count_sentence_fit.py | 33 ++ 4 files changed, 593 insertions(+) create mode 100644 online_judges/sentence_screen_fit/__init__.py create mode 100644 online_judges/sentence_screen_fit/sentence_screen_fit_challenge.ipynb create mode 100644 online_judges/sentence_screen_fit/sentence_screen_fit_solution.ipynb create mode 100644 online_judges/sentence_screen_fit/test_count_sentence_fit.py diff --git a/online_judges/sentence_screen_fit/__init__.py b/online_judges/sentence_screen_fit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/online_judges/sentence_screen_fit/sentence_screen_fit_challenge.ipynb b/online_judges/sentence_screen_fit/sentence_screen_fit_challenge.ipynb new file mode 100644 index 0000000..eb42f7d --- /dev/null +++ b/online_judges/sentence_screen_fit/sentence_screen_fit_challenge.ipynb @@ -0,0 +1,239 @@ +{ + "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: Find how many times a sentence can fit on a screen.\n", + "\n", + "See the [LeetCode](https://leetcode.com/problems/sentence-screen-fitting/) problem page.\n", + "\n", + "
\n",
+    "Given a rows x cols screen and a sentence represented by a list of non-empty words, find how many times the given sentence can be fitted on the screen.\n",
+    "\n",
+    "Note:\n",
+    "\n",
+    "A word cannot be split into two lines.\n",
+    "The order of words in the sentence must remain unchanged.\n",
+    "Two consecutive words in a line must be separated by a single space.\n",
+    "Total words in the sentence won't exceed 100.\n",
+    "Length of each word is greater than 0 and won't exceed 10.\n",
+    "1 ≤ rows, cols ≤ 20,000.\n",
+    "Example 1:\n",
+    "\n",
+    "Input:\n",
+    "rows = 2, cols = 8, sentence = [\"hello\", \"world\"]\n",
+    "\n",
+    "Output: \n",
+    "1\n",
+    "\n",
+    "Explanation:\n",
+    "hello---\n",
+    "world---\n",
+    "\n",
+    "The character '-' signifies an empty space on the screen.\n",
+    "Example 2:\n",
+    "\n",
+    "Input:\n",
+    "rows = 3, cols = 6, sentence = [\"a\", \"bcd\", \"e\"]\n",
+    "\n",
+    "Output: \n",
+    "2\n",
+    "\n",
+    "Explanation:\n",
+    "a-bcd- \n",
+    "e-a---\n",
+    "bcd-e-\n",
+    "\n",
+    "The character '-' signifies an empty space on the screen.\n",
+    "Example 3:\n",
+    "\n",
+    "Input:\n",
+    "rows = 4, cols = 5, sentence = [\"I\", \"had\", \"apple\", \"pie\"]\n",
+    "\n",
+    "Output: \n",
+    "1\n",
+    "\n",
+    "Explanation:\n",
+    "I-had\n",
+    "apple\n",
+    "pie-I\n",
+    "had--\n",
+    "\n",
+    "The character '-' signifies an empty space on the screen.\n",
+    "
\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 assume sentence is ASCII?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * No\n", + "* Is the output an integer?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> TypeError\n", + "* rows < 0 or cols < 0 -> ValueError\n", + "* cols = 0 -> 0\n", + "* sentence = '' -> 0\n", + "* rows = 2, cols = 8, sentence = [\"hello\", \"world\"] -> 1\n", + "* rows = 3, cols = 6, sentence = [\"a\", \"bcd\", \"e\"] -> 2\n", + "* rows = 4, cols = 5, sentence = [\"I\", \"had\", \"apple\", \"pie\"] -> 1" + ] + }, + { + "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 Solution(object):\n", + "\n", + " def count_sentence_fit(self, sentence, rows, cols):\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_count_sentence_fit.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSolution(object):\n", + "\n", + " def test_count_sentence_fit(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.count_sentence_fit, \n", + " None, None, None)\n", + " assert_raises(ValueError, solution.count_sentence_fit, \n", + " 'abc', rows=-1, cols=-1)\n", + " sentence = [\"hello\", \"world\"]\n", + " expected = 1\n", + " assert_equal(solution.count_sentence_fit(sentence, rows=2, cols=8),\n", + " expected)\n", + " sentence = [\"a\", \"bcd\", \"e\"]\n", + " expected = 2\n", + " assert_equal(solution.count_sentence_fit(sentence, rows=3, cols=6),\n", + " expected)\n", + " sentence = [\"I\", \"had\", \"apple\", \"pie\"]\n", + " expected = 1\n", + " assert_equal(solution.count_sentence_fit(sentence, rows=4, cols=5),\n", + " expected)\n", + " print('Success: test_count_sentence_fit')\n", + "\n", + "\n", + "def main():\n", + " test = TestSolution()\n", + " test.test_count_sentence_fit()\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/online_judges/sentence_screen_fit/sentence_screen_fit_solution.ipynb b/online_judges/sentence_screen_fit/sentence_screen_fit_solution.ipynb new file mode 100644 index 0000000..49829e7 --- /dev/null +++ b/online_judges/sentence_screen_fit/sentence_screen_fit_solution.ipynb @@ -0,0 +1,321 @@ +{ + "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: Find how many times a sentence can fit on a screen.\n", + "\n", + "See the [LeetCode](https://leetcode.com/problems/sentence-screen-fitting/) problem page.\n", + "\n", + "
\n",
+    "Given a rows x cols screen and a sentence represented by a list of non-empty words, find how many times the given sentence can be fitted on the screen.\n",
+    "\n",
+    "Note:\n",
+    "\n",
+    "A word cannot be split into two lines.\n",
+    "The order of words in the sentence must remain unchanged.\n",
+    "Two consecutive words in a line must be separated by a single space.\n",
+    "Total words in the sentence won't exceed 100.\n",
+    "Length of each word is greater than 0 and won't exceed 10.\n",
+    "1 ≤ rows, cols ≤ 20,000.\n",
+    "Example 1:\n",
+    "\n",
+    "Input:\n",
+    "rows = 2, cols = 8, sentence = [\"hello\", \"world\"]\n",
+    "\n",
+    "Output: \n",
+    "1\n",
+    "\n",
+    "Explanation:\n",
+    "hello---\n",
+    "world---\n",
+    "\n",
+    "The character '-' signifies an empty space on the screen.\n",
+    "Example 2:\n",
+    "\n",
+    "Input:\n",
+    "rows = 3, cols = 6, sentence = [\"a\", \"bcd\", \"e\"]\n",
+    "\n",
+    "Output: \n",
+    "2\n",
+    "\n",
+    "Explanation:\n",
+    "a-bcd- \n",
+    "e-a---\n",
+    "bcd-e-\n",
+    "\n",
+    "The character '-' signifies an empty space on the screen.\n",
+    "Example 3:\n",
+    "\n",
+    "Input:\n",
+    "rows = 4, cols = 5, sentence = [\"I\", \"had\", \"apple\", \"pie\"]\n",
+    "\n",
+    "Output: \n",
+    "1\n",
+    "\n",
+    "Explanation:\n",
+    "I-had\n",
+    "apple\n",
+    "pie-I\n",
+    "had--\n",
+    "\n",
+    "The character '-' signifies an empty space on the screen.\n",
+    "
\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 assume sentence is ASCII?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * No\n", + "* Is the output an integer?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> TypeError\n", + "* rows < 0 or cols < 0 -> ValueError\n", + "* cols = 0 -> 0\n", + "* sentence = '' -> 0\n", + "* rows = 2, cols = 8, sentence = [\"hello\", \"world\"] -> 1\n", + "* rows = 3, cols = 6, sentence = [\"a\", \"bcd\", \"e\"] -> 2\n", + "* rows = 4, cols = 5, sentence = [\"I\", \"had\", \"apple\", \"pie\"] -> 1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "It can be relatively straightforward to come up with the brute force solution, check out the method `count_sentence_fit_brute_force` below. \n", + "\n", + "The optimized solutions is discussed in more depth [here](https://discuss.leetcode.com/topic/62455/21ms-18-lines-java-solution/25).\n", + "\n", + "
\n",
+    "rows = 4\n",
+    "cols = 6\n",
+    "sentence = ['abc', 'de', 'f']\n",
+    "\n",
+    "\"abc de f abc de f abc de f ...\" // start=0\n",
+    " 012345                          // start=start+cols+adjustment=0+6+1=7 (1 space removed in screen string)\n",
+    "        012345                   // start=7+6+0=13\n",
+    "              012345             // start=13+6-1=18 (1 space added)\n",
+    "                   012345        // start=18+6+1=25 (1 space added)\n",
+    "                          012345\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(1)\n", + "* Space: O(1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def count_sentence_fit_brute_force(self, sentence, rows, cols):\n", + " if sentence is None:\n", + " raise TypeError('sentence cannot be None')\n", + " if rows is None or cols is None:\n", + " raise TypeError('rows and cols cannot be None')\n", + " if rows < 0 or cols < 0:\n", + " raise ValueError('rows and cols cannot be negative')\n", + " if cols == 0 or not sentence:\n", + " return 0\n", + " curr_row = 0\n", + " curr_col = 0\n", + " count = 0\n", + " while curr_row < cols:\n", + " for word in sentence:\n", + " # If the current word doesn't fit on the current line,\n", + " # move to the next line\n", + " if len(word) > cols - curr_col:\n", + " curr_col = 0\n", + " curr_row += 1\n", + " # If we are beyond the number of rows, return\n", + " if curr_row >= rows:\n", + " return count\n", + " # If the current word fits on the current line,\n", + " # 'insert' it here\n", + " if len(word) <= cols - curr_col:\n", + " curr_col += len(word) + 1\n", + " # If it still doesn't fit, then the word is too long\n", + " # and we should just return the current count\n", + " else:\n", + " return count\n", + " count += 1\n", + " return count\n", + "\n", + " def count_sentence_fit(self, sentence, rows, cols):\n", + " if sentence is None:\n", + " raise TypeError('sentence cannot be None')\n", + " if rows is None or cols is None:\n", + " raise TypeError('rows and cols cannot be None')\n", + " if rows < 0 or cols < 0:\n", + " raise ValueError('rows and cols cannot be negative')\n", + " if cols == 0 or not sentence:\n", + " return 0\n", + " string = ' '.join(sentence) + ' '\n", + " start = 0\n", + " str_len = len(string)\n", + " for row in range(rows):\n", + " start += cols\n", + " # We don't need extra space for the current row\n", + " if string[start % str_len] == ' ':\n", + " start += 1\n", + " # The current row can't fit, so we'll need to \n", + " # remove characters from the next word\n", + " else:\n", + " while (start > 0 and string[(start - 1) % str_len] != ' '):\n", + " start -= 1\n", + " return start // str_len" + ] + }, + { + "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_count_sentence_fit.py\n" + ] + } + ], + "source": [ + "%%writefile test_count_sentence_fit.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSolution(object):\n", + "\n", + " def test_count_sentence_fit(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.count_sentence_fit, \n", + " None, None, None)\n", + " assert_raises(ValueError, solution.count_sentence_fit, \n", + " 'abc', rows=-1, cols=-1)\n", + " sentence = [\"hello\", \"world\"]\n", + " expected = 1\n", + " assert_equal(solution.count_sentence_fit(sentence, rows=2, cols=8),\n", + " expected)\n", + " sentence = [\"a\", \"bcd\", \"e\"]\n", + " expected = 2\n", + " assert_equal(solution.count_sentence_fit(sentence, rows=3, cols=6),\n", + " expected)\n", + " sentence = [\"I\", \"had\", \"apple\", \"pie\"]\n", + " expected = 1\n", + " assert_equal(solution.count_sentence_fit(sentence, rows=4, cols=5),\n", + " expected)\n", + " print('Success: test_count_sentence_fit')\n", + "\n", + "\n", + "def main():\n", + " test = TestSolution()\n", + " test.test_count_sentence_fit()\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_count_sentence_fit\n" + ] + } + ], + "source": [ + "%run -i test_count_sentence_fit.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/online_judges/sentence_screen_fit/test_count_sentence_fit.py b/online_judges/sentence_screen_fit/test_count_sentence_fit.py new file mode 100644 index 0000000..96db218 --- /dev/null +++ b/online_judges/sentence_screen_fit/test_count_sentence_fit.py @@ -0,0 +1,33 @@ +from nose.tools import assert_equal, assert_raises + + +class TestSolution(object): + + def test_count_sentence_fit(self): + solution = Solution() + assert_raises(TypeError, solution.count_sentence_fit, + None, None, None) + assert_raises(ValueError, solution.count_sentence_fit, + 'abc', rows=-1, cols=-1) + sentence = ["hello", "world"] + expected = 1 + assert_equal(solution.count_sentence_fit(sentence, rows=2, cols=8), + expected) + sentence = ["a", "bcd", "e"] + expected = 2 + assert_equal(solution.count_sentence_fit(sentence, rows=3, cols=6), + expected) + sentence = ["I", "had", "apple", "pie"] + expected = 1 + assert_equal(solution.count_sentence_fit(sentence, rows=4, cols=5), + expected) + print('Success: test_count_sentence_fit') + + +def main(): + test = TestSolution() + test.test_count_sentence_fit() + + +if __name__ == '__main__': + main() \ No newline at end of file From c3b6ef037663bc994afdc12f9a3d57e4262ec95b Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Tue, 28 Mar 2017 05:13:14 -0400 Subject: [PATCH 26/90] Add ransom note challenge --- online_judges/ransom_note/__init__.py | 0 .../ransom_note/ransom_note_challenge.ipynb | 171 +++++++++++++++ .../ransom_note/ransom_note_solution.ipynb | 201 ++++++++++++++++++ online_judges/ransom_note/test_ransom_note.py | 22 ++ 4 files changed, 394 insertions(+) create mode 100644 online_judges/ransom_note/__init__.py create mode 100644 online_judges/ransom_note/ransom_note_challenge.ipynb create mode 100644 online_judges/ransom_note/ransom_note_solution.ipynb create mode 100644 online_judges/ransom_note/test_ransom_note.py diff --git a/online_judges/ransom_note/__init__.py b/online_judges/ransom_note/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/online_judges/ransom_note/ransom_note_challenge.ipynb b/online_judges/ransom_note/ransom_note_challenge.ipynb new file mode 100644 index 0000000..fbffabe --- /dev/null +++ b/online_judges/ransom_note/ransom_note_challenge.ipynb @@ -0,0 +1,171 @@ +{ + "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 magazine, see if a ransom note could have been written using the letters in the magazine.\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", + "* Is this case sensitive?\n", + " * Yes\n", + "* Can we assume we're working with ASCII characters?\n", + " * Yes\n", + "* Can we scan the entire magazine, or should we scan only when necessary?\n", + " * You can scan the entire magazine\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 -> Exception\n", + "* '', '' -> Exception\n", + "* 'a', 'b' -> False\n", + "* 'aa', 'ab' -> False\n", + "* 'aa', 'aab' -> True" + ] + }, + { + "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 Solution(object):\n", + "\n", + " def match_note_to_magazine(self, ransom_note, magazine):\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_ransom_note.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestRansomNote(object):\n", + "\n", + " def test_ransom_note(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.match_note_to_magazine, None, None)\n", + " assert_equal(solution.match_note_to_magazine('', ''), True)\n", + " assert_equal(solution.match_note_to_magazine('a', 'b'), False)\n", + " assert_equal(solution.match_note_to_magazine('aa', 'ab'), False)\n", + " assert_equal(solution.match_note_to_magazine('aa', 'aab'), True)\n", + " print('Success: test_ransom_note')\n", + "\n", + "\n", + "def main():\n", + " test = TestRansomNote()\n", + " test.test_ransom_note()\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/online_judges/ransom_note/ransom_note_solution.ipynb b/online_judges/ransom_note/ransom_note_solution.ipynb new file mode 100644 index 0000000..345e0a0 --- /dev/null +++ b/online_judges/ransom_note/ransom_note_solution.ipynb @@ -0,0 +1,201 @@ +{ + "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 magazine, see if a ransom note could have been written using the letters in the magazine.\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", + "* Is this case sensitive?\n", + " * Yes\n", + "* Can we assume we're working with ASCII characters?\n", + " * Yes\n", + "* Can we scan the entire magazine, or should we scan only when necessary?\n", + " * You can scan the entire magazine\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 -> Exception\n", + "* '', '' -> Exception\n", + "* 'a', 'b' -> False\n", + "* 'aa', 'ab' -> False\n", + "* 'aa', 'aab' -> True" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "* Build a dictionary of the magazine characters and counts.\n", + "* Loop through each letter in the ransom note and see if there are enough letters in the magazine's dictionary.\n", + "* Note: You could make this more efficient by not scanning the entire magazine all at once, but instead scan just in time as you run out of letters in the dictionary.\n", + "\n", + "Complexity:\n", + "* Time: O(n+m), where n is the length of the ransom note and m is the length of the magazine\n", + "* Space: O(n+m)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def match_note_to_magazine(self, ransom_note, magazine):\n", + " if ransom_note is None or magazine is None:\n", + " raise TypeError('ransom_note or magazine cannot be None')\n", + " seen_chars = {}\n", + " for char in magazine:\n", + " if char in seen_chars:\n", + " seen_chars[char] += 1\n", + " else:\n", + " seen_chars[char] = 1\n", + " for char in ransom_note:\n", + " try:\n", + " seen_chars[char] -= 1\n", + " except KeyError:\n", + " return False\n", + " if seen_chars[char] < 0:\n", + " return False\n", + " return True" + ] + }, + { + "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_ransom_note.py\n" + ] + } + ], + "source": [ + "%%writefile test_ransom_note.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestRansomNote(object):\n", + "\n", + " def test_ransom_note(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.match_note_to_magazine, None, None)\n", + " assert_equal(solution.match_note_to_magazine('', ''), True)\n", + " assert_equal(solution.match_note_to_magazine('a', 'b'), False)\n", + " assert_equal(solution.match_note_to_magazine('aa', 'ab'), False)\n", + " assert_equal(solution.match_note_to_magazine('aa', 'aab'), True)\n", + " print('Success: test_ransom_note')\n", + "\n", + "\n", + "def main():\n", + " test = TestRansomNote()\n", + " test.test_ransom_note()\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_ransom_note\n" + ] + } + ], + "source": [ + "%run -i test_ransom_note.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/online_judges/ransom_note/test_ransom_note.py b/online_judges/ransom_note/test_ransom_note.py new file mode 100644 index 0000000..1966b9e --- /dev/null +++ b/online_judges/ransom_note/test_ransom_note.py @@ -0,0 +1,22 @@ +from nose.tools import assert_equal, assert_raises + + +class TestRansomNote(object): + + def test_ransom_note(self): + solution = Solution() + assert_raises(TypeError, solution.match_note_to_magazine, None, None) + assert_equal(solution.match_note_to_magazine('', ''), True) + assert_equal(solution.match_note_to_magazine('a', 'b'), False) + assert_equal(solution.match_note_to_magazine('aa', 'ab'), False) + assert_equal(solution.match_note_to_magazine('aa', 'aab'), True) + print('Success: test_ransom_note') + + +def main(): + test = TestRansomNote() + test.test_ransom_note() + + +if __name__ == '__main__': + main() \ No newline at end of file From 01b0d949a9514957c92739452615322ee4b7288f Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Tue, 28 Mar 2017 05:13:55 -0400 Subject: [PATCH 27/90] Add prod three challenge --- online_judges/prod_three/__init__.py | 0 .../prod_three/prod_three_challenge.ipynb | 171 +++++++++++ .../prod_three/prod_three_solution.ipynb | 279 ++++++++++++++++++ online_judges/prod_three/test_prod_three.py | 21 ++ 4 files changed, 471 insertions(+) create mode 100644 online_judges/prod_three/__init__.py create mode 100644 online_judges/prod_three/prod_three_challenge.ipynb create mode 100644 online_judges/prod_three/prod_three_solution.ipynb create mode 100644 online_judges/prod_three/test_prod_three.py diff --git a/online_judges/prod_three/__init__.py b/online_judges/prod_three/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/online_judges/prod_three/prod_three_challenge.ipynb b/online_judges/prod_three/prod_three_challenge.ipynb new file mode 100644 index 0000000..3c5f157 --- /dev/null +++ b/online_judges/prod_three/prod_three_challenge.ipynb @@ -0,0 +1,171 @@ +{ + "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: Find the highest product of three numbers in a list.\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", + "* Is the input a list of integers?\n", + " * Yes\n", + "* Can we get negative inputs?\n", + " * Yes\n", + "* Can there be duplicate entires in the input?\n", + " * Yes\n", + "* Will there always be at least three integers?\n", + " * No\n", + "* Can we assume the inputs are valid?\n", + " * No, check for None input\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> TypeError\n", + "* Less than three ints -> ValueError\n", + "* [5, -2, 3] -> -30\n", + "* [5, -2, 3, 1, -1, 4] -> 60" + ] + }, + { + "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 Solution(object):\n", + "\n", + " def max_prod_three(self, array):\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_prod_three.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestProdThree(object):\n", + "\n", + " def test_prod_three(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.max_prod_three, None)\n", + " assert_raises(ValueError, solution.max_prod_three, [1, 2])\n", + " assert_equal(solution.max_prod_three([5, -2, 3]), -30)\n", + " assert_equal(solution.max_prod_three([5, -2, 3, 1, -1, 4]), 60)\n", + " print('Success: test_prod_three')\n", + "\n", + "\n", + "def main():\n", + " test = TestProdThree()\n", + " test.test_prod_three()\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/online_judges/prod_three/prod_three_solution.ipynb b/online_judges/prod_three/prod_three_solution.ipynb new file mode 100644 index 0000000..e79c333 --- /dev/null +++ b/online_judges/prod_three/prod_three_solution.ipynb @@ -0,0 +1,279 @@ +{ + "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: Find the highest product of three numbers in a list.\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", + "* Is the input a list of integers?\n", + " * Yes\n", + "* Can we get negative inputs?\n", + " * Yes\n", + "* Can there be duplicate entires in the input?\n", + " * Yes\n", + "* Will there always be at least three integers?\n", + " * No\n", + "* Can we assume the inputs are valid?\n", + " * No, check for None input\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> TypeError\n", + "* Less than three ints -> ValueError\n", + "* [5, -2, 3] -> -30\n", + "* [5, -2, 3, 1, -1, 4] -> 60" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "### Brute force:\n", + "\n", + "Use three loops and multiple each numbers.\n", + "\n", + "Complexity:\n", + "* Time: O(n^3)\n", + "* Space: O(1)\n", + "\n", + "### Sorting:\n", + "\n", + "Sort the list, multiply the last three elements.\n", + "\n", + "Complexity:\n", + "* Time: O(n log(n))\n", + "* Space: O(1)\n", + "\n", + "### Greedy:\n", + "\n", + "
\n",
+    " 0   1  2  3   4  5\n",
+    "[5, -2, 3, 1, -1, 4] -> 60\n",
+    "\n",
+    "max_prod_of_three = -30\n",
+    "max_prod_of_two = -10\n",
+    "max_num = 5\n",
+    "min_prod_of_two = -10\n",
+    "min_num = -2\n",
+    "\n",
+    " 0   1  2  3   4  5\n",
+    "[5, -2, 3, 1, -1, 4] -> 60\n",
+    "        ^\n",
+    "max_prod_of_three = -30\n",
+    "max_prod_of_two = 15\n",
+    "max_num = 5\n",
+    "min_prod_of_two = -10\n",
+    "min_num = -2\n",
+    "\n",
+    " 0   1  2  3   4  5\n",
+    "[5, -2, 3, 1, -1, 4] -> 60\n",
+    "           ^\n",
+    "max_prod_of_three = 15\n",
+    "max_prod_of_two = 15\n",
+    "max_num = 5\n",
+    "min_prod_of_two = -10\n",
+    "min_num = -2\n",
+    "\n",
+    " 0   1  2  3   4  5\n",
+    "[5, -2, 3, 1, -1, 4] -> 60\n",
+    "               ^\n",
+    "max_prod_of_three = 15\n",
+    "max_prod_of_two = 15\n",
+    "max_num = 5\n",
+    "min_prod_of_two = -10\n",
+    "min_num = -2\n",
+    "\n",
+    " 0   1  2  3   4  5\n",
+    "[5, -2, 3, 1, -1, 4] -> 60\n",
+    "                  ^\n",
+    "max_prod_of_three = 60\n",
+    "max_prod_of_two = 15\n",
+    "max_num = 5\n",
+    "min_prod_of_two = -10\n",
+    "min_num = -2\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(n)\n", + "* Space: O(1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def max_prod_three_nlogn(self, array):\n", + " if array is None:\n", + " raise TypeError('array cannot be None')\n", + " if len(array) < 3:\n", + " raise ValueError('array must have 3 or more ints')\n", + " array.sort()\n", + " product = 1\n", + " for item in array[-3:]:\n", + " product *= item\n", + " return product\n", + "\n", + " def max_prod_three(self, array):\n", + " if array is None:\n", + " raise TypeError('array cannot be None')\n", + " if len(array) < 3:\n", + " raise ValueError('array must have 3 or more ints')\n", + " curr_max_prod_three = array[0] * array[1] * array[2]\n", + " max_prod_two = array[0] * array[1]\n", + " min_prod_two = array[0] * array[1]\n", + " max_num = max(array[0], array[1])\n", + " min_num = min(array[0], array[1])\n", + " for i in range(2, len(array)):\n", + " curr_max_prod_three = max(curr_max_prod_three,\n", + " max_prod_two * array[i],\n", + " min_prod_two * array[i])\n", + " max_prod_two = max(max_prod_two,\n", + " max_num * array[i],\n", + " min_num * array[i])\n", + " min_prod_two = min(min_prod_two,\n", + " max_num * array[i],\n", + " min_num * array[i])\n", + " max_num = max(max_num, array[i])\n", + " min_num = min(min_num, array[i])\n", + " return curr_max_prod_three" + ] + }, + { + "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_prod_three.py\n" + ] + } + ], + "source": [ + "%%writefile test_prod_three.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestProdThree(object):\n", + "\n", + " def test_prod_three(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.max_prod_three, None)\n", + " assert_raises(ValueError, solution.max_prod_three, [1, 2])\n", + " assert_equal(solution.max_prod_three([5, -2, 3]), -30)\n", + " assert_equal(solution.max_prod_three([5, -2, 3, 1, -1, 4]), 60)\n", + " print('Success: test_prod_three')\n", + "\n", + "\n", + "def main():\n", + " test = TestProdThree()\n", + " test.test_prod_three()\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_prod_three\n" + ] + } + ], + "source": [ + "%run -i test_prod_three.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/online_judges/prod_three/test_prod_three.py b/online_judges/prod_three/test_prod_three.py new file mode 100644 index 0000000..bbe5ccd --- /dev/null +++ b/online_judges/prod_three/test_prod_three.py @@ -0,0 +1,21 @@ +from nose.tools import assert_equal, assert_raises + + +class TestProdThree(object): + + def test_prod_three(self): + solution = Solution() + assert_raises(TypeError, solution.max_prod_three, None) + assert_raises(ValueError, solution.max_prod_three, [1, 2]) + assert_equal(solution.max_prod_three([5, -2, 3]), -30) + assert_equal(solution.max_prod_three([5, -2, 3, 1, -1, 4]), 60) + print('Success: test_prod_three') + + +def main(): + test = TestProdThree() + test.test_prod_three() + + +if __name__ == '__main__': + main() \ No newline at end of file From fbe2fcc167752edaf42bc5afe4d5fd1fab6a87fe Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Tue, 28 Mar 2017 05:14:21 -0400 Subject: [PATCH 28/90] Add nim challenge --- online_judges/nim/__init__.py | 0 online_judges/nim/nim_challenge.ipynb | 179 ++++++++++++++++++++++++ online_judges/nim/nim_solution.ipynb | 192 ++++++++++++++++++++++++++ online_judges/nim/test_can_win_nim.py | 24 ++++ 4 files changed, 395 insertions(+) create mode 100644 online_judges/nim/__init__.py create mode 100644 online_judges/nim/nim_challenge.ipynb create mode 100644 online_judges/nim/nim_solution.ipynb create mode 100644 online_judges/nim/test_can_win_nim.py diff --git a/online_judges/nim/__init__.py b/online_judges/nim/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/online_judges/nim/nim_challenge.ipynb b/online_judges/nim/nim_challenge.ipynb new file mode 100644 index 0000000..068e7bc --- /dev/null +++ b/online_judges/nim/nim_challenge.ipynb @@ -0,0 +1,179 @@ +{ + "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: Determine whether you can win the Nim game given the remaining stones.\n", + "\n", + "See the [LeetCode](https://leetcode.com/problems/nim-game/) problem page.\n", + "\n", + "You are playing the following Nim Game with your friend: There is a heap of stones on the table, each time one of you take turns to remove 1 to 3 stones. The one who removes the last stone will be the winner. You will take the first turn to remove the stones.\n", + "\n", + "Both of you are very clever and have optimal strategies for the game. Write a function to determine whether you can win the game given the number of stones in the heap.\n", + "\n", + "For example, if there are 4 stones in the heap, then you will never win the game: no matter 1, 2, or 3 stones you remove, the last stone will always be removed by your friend.\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", + "* Is the input an int?\n", + " * Yes\n", + "* Is the output a boolean?\n", + " * Yes\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 -> TypeError\n", + "* 1, 2, or 3 -> True\n", + "* 4 -> False\n", + "* 7 -> True\n", + "* 40 -> False" + ] + }, + { + "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 Solution(object):\n", + "\n", + " def can_win_nim(self, num_stones_left):\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_can_win_nim.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSolution(object):\n", + "\n", + " def test_can_win_nim(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.can_win_nim, None)\n", + " assert_equal(solution.can_win_nim(1), True)\n", + " assert_equal(solution.can_win_nim(2), True)\n", + " assert_equal(solution.can_win_nim(3), True)\n", + " assert_equal(solution.can_win_nim(4), False)\n", + " assert_equal(solution.can_win_nim(7), True)\n", + " assert_equal(solution.can_win_nim(40), False)\n", + " print('Success: test_can_win_nim')\n", + "\n", + "\n", + "def main():\n", + " test = TestSolution()\n", + " test.test_can_win_nim()\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/online_judges/nim/nim_solution.ipynb b/online_judges/nim/nim_solution.ipynb new file mode 100644 index 0000000..0db4ec5 --- /dev/null +++ b/online_judges/nim/nim_solution.ipynb @@ -0,0 +1,192 @@ +{ + "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: Determine whether you can win the Nim game given the remaining stones.\n", + "\n", + "See the [LeetCode](https://leetcode.com/problems/nim-game/) problem page.\n", + "\n", + "You are playing the following Nim Game with your friend: There is a heap of stones on the table, each time one of you take turns to remove 1 to 3 stones. The one who removes the last stone will be the winner. You will take the first turn to remove the stones.\n", + "\n", + "Both of you are very clever and have optimal strategies for the game. Write a function to determine whether you can win the game given the number of stones in the heap.\n", + "\n", + "For example, if there are 4 stones in the heap, then you will never win the game: no matter 1, 2, or 3 stones you remove, the last stone will always be removed by your friend.\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", + "* Is the input an int?\n", + " * Yes\n", + "* Is the output a boolean?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> TypeError\n", + "* 1, 2, or 3 -> True\n", + "* 4 -> False\n", + "* 7 -> True\n", + "* 40 -> False" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "This is somewhat of a one-trick puzzle, where the only way you can lose if you take the first stone while playing optimally is if the number of remaining stones is divisible by 4.\n", + "\n", + "Complexity:\n", + "* Time: O(1)\n", + "* Space: O(1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def can_win_nim(self, num_stones_left):\n", + " return num_stones_left % 4 != 0" + ] + }, + { + "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_can_win_nim.py\n" + ] + } + ], + "source": [ + "%%writefile test_can_win_nim.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSolution(object):\n", + "\n", + " def test_can_win_nim(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.can_win_nim, None)\n", + " assert_equal(solution.can_win_nim(1), True)\n", + " assert_equal(solution.can_win_nim(2), True)\n", + " assert_equal(solution.can_win_nim(3), True)\n", + " assert_equal(solution.can_win_nim(4), False)\n", + " assert_equal(solution.can_win_nim(7), True)\n", + " assert_equal(solution.can_win_nim(40), False)\n", + " print('Success: test_can_win_nim')\n", + "\n", + "\n", + "def main():\n", + " test = TestSolution()\n", + " test.test_can_win_nim()\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_can_win_nim\n" + ] + } + ], + "source": [ + "%run -i test_can_win_nim.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/online_judges/nim/test_can_win_nim.py b/online_judges/nim/test_can_win_nim.py new file mode 100644 index 0000000..92de456 --- /dev/null +++ b/online_judges/nim/test_can_win_nim.py @@ -0,0 +1,24 @@ +from nose.tools import assert_equal, assert_raises + + +class TestSolution(object): + + def test_can_win_nim(self): + solution = Solution() + assert_raises(TypeError, solution.can_win_nim, None) + assert_equal(solution.can_win_nim(1), True) + assert_equal(solution.can_win_nim(2), True) + assert_equal(solution.can_win_nim(3), True) + assert_equal(solution.can_win_nim(4), False) + assert_equal(solution.can_win_nim(7), True) + assert_equal(solution.can_win_nim(40), False) + print('Success: test_can_win_nim') + + +def main(): + test = TestSolution() + test.test_can_win_nim() + + +if __name__ == '__main__': + main() \ No newline at end of file From 5b65a8a955fac5ea347635d3492be9eddfdad936 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Wed, 29 Mar 2017 04:31:17 -0400 Subject: [PATCH 29/90] Add mult other numbers challenge --- online_judges/mult_other_numbers/__init__.py | 0 .../mult_other_numbers_challenge.ipynb | 172 ++++++++++++ .../mult_other_numbers_solution.ipynb | 265 ++++++++++++++++++ .../test_mult_other_numbers.py | 22 ++ 4 files changed, 459 insertions(+) create mode 100644 online_judges/mult_other_numbers/__init__.py create mode 100644 online_judges/mult_other_numbers/mult_other_numbers_challenge.ipynb create mode 100644 online_judges/mult_other_numbers/mult_other_numbers_solution.ipynb create mode 100644 online_judges/mult_other_numbers/test_mult_other_numbers.py diff --git a/online_judges/mult_other_numbers/__init__.py b/online_judges/mult_other_numbers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/online_judges/mult_other_numbers/mult_other_numbers_challenge.ipynb b/online_judges/mult_other_numbers/mult_other_numbers_challenge.ipynb new file mode 100644 index 0000000..020b44c --- /dev/null +++ b/online_judges/mult_other_numbers/mult_other_numbers_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: Given a list of ints, find the products of every other int for each index.\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 use division?\n", + " * No\n", + "* Is the output a list of ints?\n", + " * Yes\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",
+    "* None -> TypeError\n",
+    "* [] -> []\n",
+    "* [0] -> []\n",
+    "* [0, 1] -> [1, 0]\n",
+    "* [0, 1, 2] -> [2, 0, 0]\n",
+    "* [1, 2, 3, 4] -> [24, 12, 8, 6]\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": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def mult_other_numbers(self, array):\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_mult_other_numbers.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestMultOtherNumbers(object):\n", + "\n", + " def test_mult_other_numbers(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.mult_other_numbers, None)\n", + " assert_equal(solution.mult_other_numbers([0]), [])\n", + " assert_equal(solution.mult_other_numbers([0, 1]), [1, 0])\n", + " assert_equal(solution.mult_other_numbers([0, 1, 2]), [2, 0, 0])\n", + " assert_equal(solution.mult_other_numbers([1, 2, 3, 4]), [24, 12, 8, 6])\n", + " print('Success: test_mult_other_numbers')\n", + "\n", + "\n", + "def main():\n", + " test = TestMultOtherNumbers()\n", + " test.test_mult_other_numbers()\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/online_judges/mult_other_numbers/mult_other_numbers_solution.ipynb b/online_judges/mult_other_numbers/mult_other_numbers_solution.ipynb new file mode 100644 index 0000000..74d857c --- /dev/null +++ b/online_judges/mult_other_numbers/mult_other_numbers_solution.ipynb @@ -0,0 +1,265 @@ +{ + "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 list of ints, find the products of every other int for each index.\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 use division?\n", + " * No\n", + "* Is the output a list of ints?\n", + " * Yes\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",
+    "* None -> TypeError\n",
+    "* [] -> []\n",
+    "* [0] -> []\n",
+    "* [0, 1] -> [1, 0]\n",
+    "* [0, 1, 2] -> [2, 0, 0]\n",
+    "* [1, 2, 3, 4] -> [24, 12, 8, 6]\n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "### Brute force:\n", + "\n", + "
\n",
+    "sum = 1\n",
+    " |\n",
+    "[1, 2, 3, 4]\n",
+    " ^\n",
+    "skip if both pointers are pointing to the same spot\n",
+    "    |\n",
+    "[1, 2, 3, 4]\n",
+    " ^\n",
+    "sum *= 2\n",
+    "       |\n",
+    "[1, 2, 3, 4]\n",
+    " ^\n",
+    "sum *= 3\n",
+    "          |\n",
+    "[1, 2, 3, 4]\n",
+    " ^\n",
+    "sum *= 4\n",
+    "results.append(sum)\n",
+    "results = [24]\n",
+    "\n",
+    "repeat for every element in the input list to obtain:\n",
+    "\n",
+    "[24, 12, 8, 6]\n",
+    " \n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(n^2)\n", + "* Space: O(n)\n", + "\n", + "### Greedy\n", + "\n", + "
\n",
+    "input  = [1, 2, 3, 4]\n",
+    "result = [2*3*4, 1*3*4, 1*2*4, 1*2*3]\n",
+    "\n",
+    "Note we are duplicating multiplications with the brute force approach.\n",
+    "\n",
+    "We'll calculate all products before an index, and all products after an index.\n",
+    "We'll then multiple these two together to form the result.\n",
+    "\n",
+    "input  = [1,         2,     3,     4]\n",
+    "before = [1,         1,   1*2, 1*2*3]\n",
+    "after  = [2*3*4, 1*3*4, 1*2*4,     1]\n",
+    "result = [   24,    12,     8,     6] \n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(n)\n", + "* Space: O(n)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def mult_other_numbers_brute(self, array):\n", + " if array is None:\n", + " raise TypeError('array cannot be None')\n", + " if not array:\n", + " return array\n", + " if len(array) == 1:\n", + " return []\n", + " result = []\n", + " for i in range(len(array)):\n", + " curr_sum = 1\n", + " for j in range(len(array)):\n", + " if i == j:\n", + " continue\n", + " curr_sum *= array[j]\n", + " result.append(curr_sum)\n", + " return result\n", + "\n", + " def mult_other_numbers(self, array):\n", + " if array is None:\n", + " raise TypeError('array cannot be None')\n", + " if not array:\n", + " return array\n", + " if len(array) == 1:\n", + " return []\n", + " result = [None] * len(array)\n", + " curr_product = 1\n", + " for i in range(len(array)):\n", + " result[i] = curr_product\n", + " curr_product *= array[i]\n", + " curr_product = 1\n", + " for i in range(len(array))[::-1]:\n", + " result[i] *= curr_product\n", + " curr_product *= array[i]\n", + " return result" + ] + }, + { + "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_mult_other_numbers.py\n" + ] + } + ], + "source": [ + "%%writefile test_mult_other_numbers.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestMultOtherNumbers(object):\n", + "\n", + " def test_mult_other_numbers(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.mult_other_numbers, None)\n", + " assert_equal(solution.mult_other_numbers([0]), [])\n", + " assert_equal(solution.mult_other_numbers([0, 1]), [1, 0])\n", + " assert_equal(solution.mult_other_numbers([0, 1, 2]), [2, 0, 0])\n", + " assert_equal(solution.mult_other_numbers([1, 2, 3, 4]), [24, 12, 8, 6])\n", + " print('Success: test_mult_other_numbers')\n", + "\n", + "\n", + "def main():\n", + " test = TestMultOtherNumbers()\n", + " test.test_mult_other_numbers()\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_mult_other_numbers\n" + ] + } + ], + "source": [ + "%run -i test_mult_other_numbers.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/online_judges/mult_other_numbers/test_mult_other_numbers.py b/online_judges/mult_other_numbers/test_mult_other_numbers.py new file mode 100644 index 0000000..c1c5cca --- /dev/null +++ b/online_judges/mult_other_numbers/test_mult_other_numbers.py @@ -0,0 +1,22 @@ +from nose.tools import assert_equal, assert_raises + + +class TestMultOtherNumbers(object): + + def test_mult_other_numbers(self): + solution = Solution() + assert_raises(TypeError, solution.mult_other_numbers, None) + assert_equal(solution.mult_other_numbers([0]), []) + assert_equal(solution.mult_other_numbers([0, 1]), [1, 0]) + assert_equal(solution.mult_other_numbers([0, 1, 2]), [2, 0, 0]) + assert_equal(solution.mult_other_numbers([1, 2, 3, 4]), [24, 12, 8, 6]) + print('Success: test_mult_other_numbers') + + +def main(): + test = TestMultOtherNumbers() + test.test_mult_other_numbers() + + +if __name__ == '__main__': + main() \ No newline at end of file From 22971b5005d33f7b8f79f61089640e3e3b6575ee Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Wed, 29 Mar 2017 04:31:39 -0400 Subject: [PATCH 30/90] Add move zeroes challenge --- online_judges/move_zeroes/__init__.py | 0 .../move_zeroes/move_zeroes_challenge.ipynb | 189 +++++++++++++ .../move_zeroes/move_zeroes_solution.ipynb | 250 ++++++++++++++++++ online_judges/move_zeroes/test_move_zeroes.py | 36 +++ 4 files changed, 475 insertions(+) create mode 100644 online_judges/move_zeroes/__init__.py create mode 100644 online_judges/move_zeroes/move_zeroes_challenge.ipynb create mode 100644 online_judges/move_zeroes/move_zeroes_solution.ipynb create mode 100644 online_judges/move_zeroes/test_move_zeroes.py diff --git a/online_judges/move_zeroes/__init__.py b/online_judges/move_zeroes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/online_judges/move_zeroes/move_zeroes_challenge.ipynb b/online_judges/move_zeroes/move_zeroes_challenge.ipynb new file mode 100644 index 0000000..d9b223c --- /dev/null +++ b/online_judges/move_zeroes/move_zeroes_challenge.ipynb @@ -0,0 +1,189 @@ +{ + "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: Move all zeroes in a list to the end.\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", + "* Is the input an array of ints?\n", + " * Yes\n", + "* Is the output a new array of ints?\n", + " * No, do this in-place\n", + "* Do we need to maintain ordering of non-zero values?\n", + " * Yes\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",
+    "* None -> TypeError\n",
+    "* [0, 1, 0, 3, 12]\n",
+    "* [1, 0] -> [1, 0]\n",
+    "* [0, 1] -> [1, 0]\n",
+    "* [0] -> [0]\n",
+    "* [1] -> [1]\n",
+    "* [1, 1] -> [1, 1]\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": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def move_zeroes(self, nums):\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_move_zeroes.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestMoveZeroes(object):\n", + "\n", + " def test_move_zeroes(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.move_zeroes, None)\n", + " array = [0, 1, 0, 3, 12]\n", + " solution.move_zeroes(array)\n", + " assert_equal(array, [1, 3, 12, 0, 0])\n", + " array = [1, 0]\n", + " solution.move_zeroes(array)\n", + " assert_equal(array, [1, 0])\n", + " array = [0, 1]\n", + " solution.move_zeroes(array)\n", + " assert_equal(array, [1, 0])\n", + " array = [0]\n", + " solution.move_zeroes(array)\n", + " assert_equal(array, [0])\n", + " array = [1]\n", + " solution.move_zeroes(array)\n", + " assert_equal(array, [1])\n", + " array = [1, 1]\n", + " solution.move_zeroes(array)\n", + " assert_equal(array, [1, 1])\n", + " print('Success: test_move_zeroes')\n", + "\n", + "\n", + "def main():\n", + " test = TestMoveZeroes()\n", + " test.test_move_zeroes()\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/online_judges/move_zeroes/move_zeroes_solution.ipynb b/online_judges/move_zeroes/move_zeroes_solution.ipynb new file mode 100644 index 0000000..7fabe45 --- /dev/null +++ b/online_judges/move_zeroes/move_zeroes_solution.ipynb @@ -0,0 +1,250 @@ +{ + "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: Move all zeroes in a list to the end.\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", + "* Is the input an array of ints?\n", + " * Yes\n", + "* Is the output a new array of ints?\n", + " * No, do this in-place\n", + "* Do we need to maintain ordering of non-zero values?\n", + " * Yes\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",
+    "* None -> TypeError\n",
+    "* [0, 1, 0, 3, 12] -> [1, 3, 12, 0, 0]\n",
+    "* [1, 0] -> [1, 0]\n",
+    "* [0, 1] -> [1, 0]\n",
+    "* [0] -> [0]\n",
+    "* [1] -> [1]\n",
+    "* [1, 1] -> [1, 1]\n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "* pos = 0\n", + "* Loop through each item in the input\n", + " * If the item != 0, set input[pos] = item\n", + " * pos++\n", + "* Fill input[pos:] with zeroes\n", + "\n", + "
\n",
+    " |\n",
+    "[0, 1, 0, 3, 12]\n",
+    " ^\n",
+    "    |\n",
+    "[0, 1, 0, 3, 12]\n",
+    " ^\n",
+    "    |\n",
+    "[1, 1, 0, 3, 12]\n",
+    " ^\n",
+    "       |\n",
+    "[1, 1, 0, 3, 12]\n",
+    "    ^\n",
+    "          |\n",
+    "[1, 1, 0, 3, 12]\n",
+    "    ^\n",
+    "          |\n",
+    "[1, 3, 0, 3, 12]\n",
+    "    ^\n",
+    "              |\n",
+    "[1, 3, 0, 3, 12]\n",
+    "       ^\n",
+    "              |\n",
+    "[1, 3, 12, 3, 12]\n",
+    "       ^\n",
+    "\n",
+    "Fill right with zeroes:\n",
+    "\n",
+    "[1, 3, 12, 3, 12]\n",
+    "           ^\n",
+    "[1, 3, 12, 0, 12]\n",
+    "           ^\n",
+    "[1, 3, 12, 0, 0]\n",
+    "              ^\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(n)\n", + "* Space: O(1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def move_zeroes(self, nums):\n", + " if nums is None:\n", + " raise TypeError('nums cannot be None')\n", + " pos = 0\n", + " for num in nums:\n", + " if num != 0:\n", + " nums[pos] = num\n", + " pos += 1\n", + " if pos < len(nums):\n", + " nums[pos:] = [0] * (len(nums) - pos)" + ] + }, + { + "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_move_zeroes.py\n" + ] + } + ], + "source": [ + "%%writefile test_move_zeroes.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestMoveZeroes(object):\n", + "\n", + " def test_move_zeroes(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.move_zeroes, None)\n", + " array = [0, 1, 0, 3, 12]\n", + " solution.move_zeroes(array)\n", + " assert_equal(array, [1, 3, 12, 0, 0])\n", + " array = [1, 0]\n", + " solution.move_zeroes(array)\n", + " assert_equal(array, [1, 0])\n", + " array = [0, 1]\n", + " solution.move_zeroes(array)\n", + " assert_equal(array, [1, 0])\n", + " array = [0]\n", + " solution.move_zeroes(array)\n", + " assert_equal(array, [0])\n", + " array = [1]\n", + " solution.move_zeroes(array)\n", + " assert_equal(array, [1])\n", + " array = [1, 1]\n", + " solution.move_zeroes(array)\n", + " assert_equal(array, [1, 1])\n", + " print('Success: test_move_zeroes')\n", + "\n", + "\n", + "def main():\n", + " test = TestMoveZeroes()\n", + " test.test_move_zeroes()\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_move_zeroes\n" + ] + } + ], + "source": [ + "%run -i test_move_zeroes.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/online_judges/move_zeroes/test_move_zeroes.py b/online_judges/move_zeroes/test_move_zeroes.py new file mode 100644 index 0000000..f968699 --- /dev/null +++ b/online_judges/move_zeroes/test_move_zeroes.py @@ -0,0 +1,36 @@ +from nose.tools import assert_equal, assert_raises + + +class TestMoveZeroes(object): + + def test_move_zeroes(self): + solution = Solution() + assert_raises(TypeError, solution.move_zeroes, None) + array = [0, 1, 0, 3, 12] + solution.move_zeroes(array) + assert_equal(array, [1, 3, 12, 0, 0]) + array = [1, 0] + solution.move_zeroes(array) + assert_equal(array, [1, 0]) + array = [0, 1] + solution.move_zeroes(array) + assert_equal(array, [1, 0]) + array = [0] + solution.move_zeroes(array) + assert_equal(array, [0]) + array = [1] + solution.move_zeroes(array) + assert_equal(array, [1]) + array = [1, 1] + solution.move_zeroes(array) + assert_equal(array, [1, 1]) + print('Success: test_move_zeroes') + + +def main(): + test = TestMoveZeroes() + test.test_move_zeroes() + + +if __name__ == '__main__': + main() \ No newline at end of file From f2587dc29b26b477c4920be4c60c6486b5b33651 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Wed, 29 Mar 2017 04:31:58 -0400 Subject: [PATCH 31/90] Add merge ranges challenge --- online_judges/merge_ranges/__init__.py | 0 .../merge_ranges/merge_ranges_challenge.ipynb | 188 +++++++++++++ .../merge_ranges/merge_ranges_solution.ipynb | 256 ++++++++++++++++++ .../merge_ranges/test_merge_ranges.py | 28 ++ 4 files changed, 472 insertions(+) create mode 100644 online_judges/merge_ranges/__init__.py create mode 100644 online_judges/merge_ranges/merge_ranges_challenge.ipynb create mode 100644 online_judges/merge_ranges/merge_ranges_solution.ipynb create mode 100644 online_judges/merge_ranges/test_merge_ranges.py diff --git a/online_judges/merge_ranges/__init__.py b/online_judges/merge_ranges/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/online_judges/merge_ranges/merge_ranges_challenge.ipynb b/online_judges/merge_ranges/merge_ranges_challenge.ipynb new file mode 100644 index 0000000..8f955e4 --- /dev/null +++ b/online_judges/merge_ranges/merge_ranges_challenge.ipynb @@ -0,0 +1,188 @@ +{ + "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 list of tuples representing ranges, condense the ranges. \n", + "\n", + "Example: [(2, 3), (3, 5), (7, 9), (8, 10)] -> [(2, 5), (7, 10)]\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 the tuples in sorted order?\n", + " * No\n", + "* Are the tuples ints?\n", + " * Yes\n", + "* Will all tuples have the first element less than the second?\n", + " * Yes\n", + "* Is there an upper bound on the input range?\n", + " * No\n", + "* Is the output a list of tuples?\n", + " * Yes\n", + "* Is the output a new array?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * No, check for None\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "
\n",
+    "* None input -> TypeError\n",
+    "* [] - []\n",
+    "* [(2, 3), (7, 9)] -> [(2, 3), (7, 9)]\n",
+    "* [(2, 3), (3, 5), (7, 9), (8, 10)] -> [(2, 5), (7, 10)]\n",
+    "* [(2, 3), (3, 5), (7, 9), (8, 10), (1, 11)] -> [(1, 11)]\n",
+    "* [(2, 3), (3, 8), (7, 9), (8, 10)] -> [(2, 10)]\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": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def merge_ranges(self, array):\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_merge_ranges.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestMergeRanges(object):\n", + "\n", + " def test_merge_ranges(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.merge_ranges, None)\n", + " assert_equal(solution.merge_ranges([]), [])\n", + " array = [(2, 3), (7, 9)]\n", + " expected = [(2, 3), (7, 9)]\n", + " assert_equal(solution.merge_ranges(array), expected)\n", + " array = [(2, 3), (3, 5), (7, 9), (8, 10)]\n", + " expected = [(2, 5), (7, 10)]\n", + " assert_equal(solution.merge_ranges(array), expected)\n", + " array = [(2, 3), (3, 5), (7, 9), (8, 10), (1, 11)]\n", + " expected = [(1, 11)]\n", + " assert_equal(solution.merge_ranges(array), expected)\n", + " print('Success: test_merge_ranges')\n", + "\n", + "\n", + "def main():\n", + " test = TestMergeRanges()\n", + " test.test_merge_ranges()\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/online_judges/merge_ranges/merge_ranges_solution.ipynb b/online_judges/merge_ranges/merge_ranges_solution.ipynb new file mode 100644 index 0000000..584315a --- /dev/null +++ b/online_judges/merge_ranges/merge_ranges_solution.ipynb @@ -0,0 +1,256 @@ +{ + "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 list of tuples representing ranges, condense the ranges. \n", + "\n", + "Example: [(2, 3), (3, 5), (7, 9), (8, 10)] -> [(2, 5), (7, 10)]\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 the tuples in sorted order?\n", + " * No\n", + "* Are the tuples ints?\n", + " * Yes\n", + "* Will all tuples have the first element less than the second?\n", + " * Yes\n", + "* Is there an upper bound on the input range?\n", + " * No\n", + "* Is the output a list of tuples?\n", + " * Yes\n", + "* Is the output a new array?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * No, check for None\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "
\n",
+    "* None input -> TypeError\n",
+    "* [] - []\n",
+    "* [(2, 3), (7, 9)] -> [(2, 3), (7, 9)]\n",
+    "* [(2, 3), (3, 5), (7, 9), (8, 10)] -> [(2, 5), (7, 10)]\n",
+    "* [(2, 3), (3, 5), (7, 9), (8, 10), (1, 11)] -> [(1, 11)]\n",
+    "* [(2, 3), (3, 8), (7, 9), (8, 10)] -> [(2, 10)]\n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "* Sort the tuples based on start time\n", + "* Check each adjacent tuple to see if they can be merged\n", + "\n", + "
\n",
+    "Case: * [(2, 3), (3, 8), (7, 9), (8, 10)] -> [(2, 10)]\n",
+    "\n",
+    "* Sort by start time (already sorted)\n",
+    "* Add the first tuple to the merged_array\n",
+    "* Loop through each item in sorted_array starting at index 1\n",
+    "    * If there is no overlap\n",
+    "        * Add the current item to merged_array\n",
+    "    * Else\n",
+    "        * Update the last item in merged_array\n",
+    "            * The end time will be the max of merged_array[-1][1] and sorted_array[i][1]\n",
+    "\n",
+    "Start:\n",
+    "                           i\n",
+    "                   0       1       2       3\n",
+    "sorted_array = [(2, 3), (3, 8), (7, 9), (8, 10)]\n",
+    "merged_array = [(2, 3)]\n",
+    "\n",
+    "Overlap with (2, 3), (3, 8):\n",
+    "                           i\n",
+    "                   0       1       2       3\n",
+    "sorted_array = [(2, 3), (3, 8), (7, 9), (8, 10)]\n",
+    "merged_array = [(2, 8)]\n",
+    "\n",
+    "Overlap with (2, 8), (7, 9):\n",
+    "                                   i\n",
+    "                   0       1       2       3\n",
+    "sorted_array = [(2, 3), (3, 8), (7, 9), (8, 10)]\n",
+    "merged_array = [(2, 9)]\n",
+    "\n",
+    "Overlap with (2, 9) (8, 10):\n",
+    "                                   i\n",
+    "                   0       1       2       3\n",
+    "sorted_array = [(2, 3), (3, 8), (7, 9), (8, 10)]\n",
+    "merged_array = [(2, 10)]\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(n log(n))\n", + "* Space: O(n)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def merge_ranges(self, array):\n", + " if array is None:\n", + " raise TypeError('array cannot be None')\n", + " if not array:\n", + " return array\n", + " sorted_array = sorted(array)\n", + " merged_array = [sorted_array[0]]\n", + " for index, item in enumerate(sorted_array):\n", + " if index == 0:\n", + " continue\n", + " start_prev, end_prev = merged_array[-1]\n", + " start_curr, end_curr = item\n", + " if end_prev < start_curr:\n", + " # No overlap, add the entry\n", + " merged_array.append(item)\n", + " else:\n", + " # Overlap, update the previous entry's end value\n", + " merged_array[-1] = (start_prev, max(end_prev, end_curr))\n", + " return merged_array" + ] + }, + { + "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_merge_ranges.py\n" + ] + } + ], + "source": [ + "%%writefile test_merge_ranges.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestMergeRanges(object):\n", + "\n", + " def test_merge_ranges(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.merge_ranges, None)\n", + " assert_equal(solution.merge_ranges([]), [])\n", + " array = [(2, 3), (7, 9)]\n", + " expected = [(2, 3), (7, 9)]\n", + " assert_equal(solution.merge_ranges(array), expected)\n", + " array = [(3, 5), (2, 3), (7, 9), (8, 10)]\n", + " expected = [(2, 5), (7, 10)]\n", + " assert_equal(solution.merge_ranges(array), expected)\n", + " array = [(2, 3), (3, 5), (7, 9), (8, 10), (1, 11)]\n", + " expected = [(1, 11)]\n", + " assert_equal(solution.merge_ranges(array), expected)\n", + " print('Success: test_merge_ranges')\n", + "\n", + "\n", + "def main():\n", + " test = TestMergeRanges()\n", + " test.test_merge_ranges()\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_merge_ranges\n" + ] + } + ], + "source": [ + "%run -i test_merge_ranges.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/online_judges/merge_ranges/test_merge_ranges.py b/online_judges/merge_ranges/test_merge_ranges.py new file mode 100644 index 0000000..5d39cf2 --- /dev/null +++ b/online_judges/merge_ranges/test_merge_ranges.py @@ -0,0 +1,28 @@ +from nose.tools import assert_equal, assert_raises + + +class TestMergeRanges(object): + + def test_merge_ranges(self): + solution = Solution() + assert_raises(TypeError, solution.merge_ranges, None) + assert_equal(solution.merge_ranges([]), []) + array = [(2, 3), (7, 9)] + expected = [(2, 3), (7, 9)] + assert_equal(solution.merge_ranges(array), expected) + array = [(3, 5), (2, 3), (7, 9), (8, 10)] + expected = [(2, 5), (7, 10)] + assert_equal(solution.merge_ranges(array), expected) + array = [(2, 3), (3, 5), (7, 9), (8, 10), (1, 11)] + expected = [(1, 11)] + assert_equal(solution.merge_ranges(array), expected) + print('Success: test_merge_ranges') + + +def main(): + test = TestMergeRanges() + test.test_merge_ranges() + + +if __name__ == '__main__': + main() \ No newline at end of file From d28febfded6336e71ac15016b07e5f07c6b1bbb5 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Wed, 29 Mar 2017 04:32:22 -0400 Subject: [PATCH 32/90] Add max profit challenge --- online_judges/max_profit/__init__.py | 0 .../max_profit/max_profit_challenge.ipynb | 175 +++++++++++++++ .../max_profit/max_profit_solution.ipynb | 207 ++++++++++++++++++ online_judges/max_profit/test_max_profit.py | 21 ++ 4 files changed, 403 insertions(+) create mode 100644 online_judges/max_profit/__init__.py create mode 100644 online_judges/max_profit/max_profit_challenge.ipynb create mode 100644 online_judges/max_profit/max_profit_solution.ipynb create mode 100644 online_judges/max_profit/test_max_profit.py diff --git a/online_judges/max_profit/__init__.py b/online_judges/max_profit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/online_judges/max_profit/max_profit_challenge.ipynb b/online_judges/max_profit/max_profit_challenge.ipynb new file mode 100644 index 0000000..3c0e540 --- /dev/null +++ b/online_judges/max_profit/max_profit_challenge.ipynb @@ -0,0 +1,175 @@ +{ + "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 list of stock prices, find the max profit from 1 buy and 1 sell.\n", + "\n", + "See the [LeetCode](https://leetcode.com/problems/) problem page.\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 all prices positive ints?\n", + " * Yes\n", + "* Is the output an int?\n", + " * Yes\n", + "* If profit is negative, do we return the smallest negative loss?\n", + " * Yes\n", + "* If there are less than two prices, what do we return?\n", + " * Exception\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 -> TypeError\n", + "* Zero or one price -> ValueError\n", + "* No profit\n", + " * [8, 5, 3, 2, 1] -> -1\n", + "* General case\n", + " * [5, 3, 7, 4, 2, 6, 9] -> 7" + ] + }, + { + "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 Solution(object):\n", + "\n", + " def find_max_profit(self, prices):\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_max_profit.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestMaxProfit(object):\n", + "\n", + " def test_max_profit(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.find_max_profit, None)\n", + " assert_raises(ValueError, solution.find_max_profit, [])\n", + " assert_equal(solution.find_max_profit([8, 5, 3, 2, 1]), -1)\n", + " assert_equal(solution.find_max_profit([5, 3, 7, 4, 2, 6, 9]), 7)\n", + " print('Success: test_max_profit')\n", + "\n", + "\n", + "def main():\n", + " test = TestMaxProfit()\n", + " test.test_max_profit()\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/online_judges/max_profit/max_profit_solution.ipynb b/online_judges/max_profit/max_profit_solution.ipynb new file mode 100644 index 0000000..933249d --- /dev/null +++ b/online_judges/max_profit/max_profit_solution.ipynb @@ -0,0 +1,207 @@ +{ + "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 list of stock prices, find the max profit from 1 buy and 1 sell.\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 all prices positive ints?\n", + " * Yes\n", + "* Is the output an int?\n", + " * Yes\n", + "* If profit is negative, do we return the smallest negative loss?\n", + " * Yes\n", + "* If there are less than two prices, what do we return?\n", + " * Exception\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 -> TypeError\n", + "* Zero or one price -> ValueError\n", + "* No profit\n", + " * [8, 5, 3, 2, 1] -> -1\n", + "* General case\n", + " * [5, 3, 7, 4, 2, 6, 9] -> 7" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "We'll use a greedy approach and iterate through the prices once.\n", + "\n", + "* Loop through the prices\n", + " * Update current profit (price = min_price)\n", + " * Update the min price\n", + " * Update the max profit\n", + "* Return max profit\n", + "\n", + "Complexity:\n", + "* Time: O(n)\n", + "* Space: O(1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\n", + "\n", + "\n", + "class Solution(object):\n", + "\n", + " def find_max_profit(self, prices):\n", + " if prices is None:\n", + " raise TypeError('prices cannot be None')\n", + " if len(prices) < 2:\n", + " raise ValueError('prices must have at least two values')\n", + " min_price = prices[0]\n", + " max_profit = -sys.maxsize\n", + " for index, price in enumerate(prices):\n", + " if index == 0:\n", + " continue\n", + " profit = price - min_price\n", + " min_price = min(price, min_price)\n", + " max_profit = max(profit, max_profit)\n", + " return max_profit" + ] + }, + { + "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_max_profit.py\n" + ] + } + ], + "source": [ + "%%writefile test_max_profit.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestMaxProfit(object):\n", + "\n", + " def test_max_profit(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.find_max_profit, None)\n", + " assert_raises(ValueError, solution.find_max_profit, [])\n", + " assert_equal(solution.find_max_profit([8, 5, 3, 2, 1]), -1)\n", + " assert_equal(solution.find_max_profit([5, 3, 7, 4, 2, 6, 9]), 7)\n", + " print('Success: test_max_profit')\n", + "\n", + "\n", + "def main():\n", + " test = TestMaxProfit()\n", + " test.test_max_profit()\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_max_profit\n" + ] + } + ], + "source": [ + "%run -i test_max_profit.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/online_judges/max_profit/test_max_profit.py b/online_judges/max_profit/test_max_profit.py new file mode 100644 index 0000000..26e9b12 --- /dev/null +++ b/online_judges/max_profit/test_max_profit.py @@ -0,0 +1,21 @@ +from nose.tools import assert_equal, assert_raises + + +class TestMaxProfit(object): + + def test_max_profit(self): + solution = Solution() + assert_raises(TypeError, solution.find_max_profit, None) + assert_raises(ValueError, solution.find_max_profit, []) + assert_equal(solution.find_max_profit([8, 5, 3, 2, 1]), -1) + assert_equal(solution.find_max_profit([5, 3, 7, 4, 2, 6, 9]), 7) + print('Success: test_max_profit') + + +def main(): + test = TestMaxProfit() + test.test_max_profit() + + +if __name__ == '__main__': + main() \ No newline at end of file From 5975d3f9a8dde114f7a856a4af10d18258dd37e9 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Wed, 29 Mar 2017 04:32:52 -0400 Subject: [PATCH 33/90] Add math ops challenge --- online_judges/math_ops/__init__.py | 0 .../math_ops/math_ops_challenge.ipynb | 190 ++++++++++++++ .../math_ops/math_ops_solution.ipynb | 235 ++++++++++++++++++ online_judges/math_ops/test_math_ops.py | 33 +++ 4 files changed, 458 insertions(+) create mode 100644 online_judges/math_ops/__init__.py create mode 100644 online_judges/math_ops/math_ops_challenge.ipynb create mode 100644 online_judges/math_ops/math_ops_solution.ipynb create mode 100644 online_judges/math_ops/test_math_ops.py diff --git a/online_judges/math_ops/__init__.py b/online_judges/math_ops/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/online_judges/math_ops/math_ops_challenge.ipynb b/online_judges/math_ops/math_ops_challenge.ipynb new file mode 100644 index 0000000..bcd19d4 --- /dev/null +++ b/online_judges/math_ops/math_ops_challenge.ipynb @@ -0,0 +1,190 @@ +{ + "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: Create a class with an insert method to insert an int to a list. It should also support calculating the max, min, mean, and mode in O(1).\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 assume the inputs are valid?\n", + " * No\n", + "* Is there a range of inputs?\n", + " * 0 <= item <= 100\n", + "* Should mean return a float?\n", + " * Yes\n", + "* Should the other results return an int?\n", + " * Yes\n", + "* If there are multiple modes, what do we return?\n", + " * Any of the modes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> TypeError\n", + "* [] -> ValueError\n", + "* [5, 2, 7, 9, 9, 2, 9, 4, 3, 3, 2]\n", + " * max: 9\n", + " * min: 2\n", + " * mean: 55\n", + " * mode: 9 or 2" + ] + }, + { + "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 Solution(object):\n", + "\n", + " def __init__(self, upper_limit=100):\n", + " # TODO: Implement me\n", + " pass\n", + "\n", + " def insert(self, val):\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_math_ops.py\n", + "from nose.tools import assert_equal, assert_true, assert_raises\n", + "\n", + "\n", + "class TestMathOps(object):\n", + "\n", + " def test_math_ops(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.insert, None)\n", + " solution.insert(5)\n", + " solution.insert(2)\n", + " solution.insert(7)\n", + " solution.insert(9)\n", + " solution.insert(9)\n", + " solution.insert(2)\n", + " solution.insert(9)\n", + " solution.insert(4)\n", + " solution.insert(3)\n", + " solution.insert(3)\n", + " solution.insert(2)\n", + " assert_equal(solution.max, 9)\n", + " assert_equal(solution.min, 2)\n", + " assert_equal(solution.mean, 5)\n", + " assert_true(solution.mode in (2, 92))\n", + " print('Success: test_math_ops')\n", + "\n", + "\n", + "def main():\n", + " test = TestMathOps()\n", + " test.test_math_ops()\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/online_judges/math_ops/math_ops_solution.ipynb b/online_judges/math_ops/math_ops_solution.ipynb new file mode 100644 index 0000000..801f90c --- /dev/null +++ b/online_judges/math_ops/math_ops_solution.ipynb @@ -0,0 +1,235 @@ +{ + "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: Create a class with an insert method to insert an int to a list. It should also support calculating the max, min, mean, and mode in O(1).\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 assume the inputs are valid?\n", + " * No\n", + "* Is there a range of inputs?\n", + " * 0 <= item <= 100\n", + "* Should mean return a float?\n", + " * Yes\n", + "* Should the other results return an int?\n", + " * Yes\n", + "* If there are multiple modes, what do we return?\n", + " * Any of the modes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> TypeError\n", + "* [] -> ValueError\n", + "* [5, 2, 7, 9, 9, 2, 9, 4, 3, 3, 2]\n", + " * max: 9\n", + " * min: 2\n", + " * mean: 55\n", + " * mode: 9 or 2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "Return the input, val\n", + "\n", + "Complexity:\n", + "* Time: O(1)\n", + "* Space: O(1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from __future__ import division\n", + "\n", + "\n", + "class Solution(object):\n", + "\n", + " def __init__(self, upper_limit=100):\n", + " self.max = None\n", + " self.min = None\n", + " # Mean\n", + " self.num_items = 0\n", + " self.running_sum = 0\n", + " self.mean = None\n", + " # Mode\n", + " self.array = [0] * (upper_limit+1)\n", + " self.mode_ocurrences = 0\n", + " self.mode = None\n", + "\n", + " def insert(self, val):\n", + " if val is None:\n", + " raise TypeError('val cannot be None')\n", + " if self.max is None or val > self.max:\n", + " self.max = val\n", + " if self.min is None or val < self.min:\n", + " self.min = val\n", + " # Calculate the mean\n", + " self.num_items += 1\n", + " self.running_sum += val\n", + " self.mean = self.running_sum / self.num_items\n", + " # Calculate the mode\n", + " self.array[val] += 1\n", + " if self.array[val] > self.mode_ocurrences:\n", + " self.mode_ocurrences = self.array[val]\n", + " self.mode = val" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Unit Test" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting test_math_ops.py\n" + ] + } + ], + "source": [ + "%%writefile test_math_ops.py\n", + "from nose.tools import assert_equal, assert_true, assert_raises\n", + "\n", + "\n", + "class TestMathOps(object):\n", + "\n", + " def test_math_ops(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.insert, None)\n", + " solution.insert(5)\n", + " solution.insert(2)\n", + " solution.insert(7)\n", + " solution.insert(9)\n", + " solution.insert(9)\n", + " solution.insert(2)\n", + " solution.insert(9)\n", + " solution.insert(4)\n", + " solution.insert(3)\n", + " solution.insert(3)\n", + " solution.insert(2)\n", + " assert_equal(solution.max, 9)\n", + " assert_equal(solution.min, 2)\n", + " assert_equal(solution.mean, 5)\n", + " assert_true(solution.mode in (2, 9))\n", + " print('Success: test_math_ops')\n", + "\n", + "\n", + "def main():\n", + " test = TestMathOps()\n", + " test.test_math_ops()\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " main()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "ename": "AssertionError", + "evalue": "False is not true", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/Users/donnemartin/Dev/github/sources/interactive-coding-challenges/online_judges/math_ops/test_math_ops.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 31\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 32\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0m__name__\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m'__main__'\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 33\u001b[0;31m \u001b[0mmain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m/Users/donnemartin/Dev/github/sources/interactive-coding-challenges/online_judges/math_ops/test_math_ops.py\u001b[0m in \u001b[0;36mmain\u001b[0;34m()\u001b[0m\n\u001b[1;32m 27\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mmain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 28\u001b[0m \u001b[0mtest\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mTestMathOps\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 29\u001b[0;31m \u001b[0mtest\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtest_math_ops\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 30\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 31\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/Users/donnemartin/Dev/github/sources/interactive-coding-challenges/online_judges/math_ops/test_math_ops.py\u001b[0m in \u001b[0;36mtest_math_ops\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 21\u001b[0m \u001b[0massert_equal\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msolution\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmin\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 22\u001b[0m \u001b[0massert_equal\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msolution\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmean\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m5\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 23\u001b[0;31m \u001b[0massert_true\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msolution\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmode\u001b[0m \u001b[0;32min\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m92\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 24\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'Success: test_math_ops'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 25\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/Users/donnemartin/.pyenv/versions/3.5.0/lib/python3.5/unittest/case.py\u001b[0m in \u001b[0;36massertTrue\u001b[0;34m(self, expr, msg)\u001b[0m\n\u001b[1;32m 672\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mexpr\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 673\u001b[0m \u001b[0mmsg\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_formatMessage\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmsg\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"%s is not true\"\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0msafe_repr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mexpr\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 674\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfailureException\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmsg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 675\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 676\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_formatMessage\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmsg\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstandardMsg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mAssertionError\u001b[0m: False is not true" + ] + } + ], + "source": [ + "%run -i test_math_ops.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/online_judges/math_ops/test_math_ops.py b/online_judges/math_ops/test_math_ops.py new file mode 100644 index 0000000..b00cabf --- /dev/null +++ b/online_judges/math_ops/test_math_ops.py @@ -0,0 +1,33 @@ +from nose.tools import assert_equal, assert_true, assert_raises + + +class TestMathOps(object): + + def test_math_ops(self): + solution = Solution() + assert_raises(TypeError, solution.insert, None) + solution.insert(5) + solution.insert(2) + solution.insert(7) + solution.insert(9) + solution.insert(9) + solution.insert(2) + solution.insert(9) + solution.insert(4) + solution.insert(3) + solution.insert(3) + solution.insert(2) + assert_equal(solution.max, 9) + assert_equal(solution.min, 2) + assert_equal(solution.mean, 5) + assert_true(solution.mode in (2, 92)) + print('Success: test_math_ops') + + +def main(): + test = TestMathOps() + test.test_math_ops() + + +if __name__ == '__main__': + main() \ No newline at end of file From 0cd05ebe3618aa1be64b99530470cb386eca95e9 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Wed, 29 Mar 2017 04:33:18 -0400 Subject: [PATCH 34/90] Add longest substr k distinct challenge --- .../longest_substr_k_distinct/__init__.py | 0 .../longest_substr_challenge.ipynb | 171 ++++++++++++++ .../longest_substr_solution.ipynb | 208 ++++++++++++++++++ .../test_longest_substr.py | 21 ++ 4 files changed, 400 insertions(+) create mode 100644 online_judges/longest_substr_k_distinct/__init__.py create mode 100644 online_judges/longest_substr_k_distinct/longest_substr_challenge.ipynb create mode 100644 online_judges/longest_substr_k_distinct/longest_substr_solution.ipynb create mode 100644 online_judges/longest_substr_k_distinct/test_longest_substr.py diff --git a/online_judges/longest_substr_k_distinct/__init__.py b/online_judges/longest_substr_k_distinct/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/online_judges/longest_substr_k_distinct/longest_substr_challenge.ipynb b/online_judges/longest_substr_k_distinct/longest_substr_challenge.ipynb new file mode 100644 index 0000000..e0ca5f9 --- /dev/null +++ b/online_judges/longest_substr_k_distinct/longest_substr_challenge.ipynb @@ -0,0 +1,171 @@ +{ + "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: Find the length of the longest substring with at most k distinct characters.\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 assume the inputs are valid?\n", + " * No\n", + "* Can we assume the strings are ASCII?\n", + " * Yes\n", + "* Is this case sensitive?\n", + " * Yes\n", + "* Is a substring a contiguous block of chars?\n", + " * Yes\n", + "* Do we expect an int as a result?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> TypeError\n", + "* '', k = 3 -> 0\n", + "* 'abcabcdefgghiij', k=3 -> 6\n", + "* 'abcabcdefgghighij', k=3 -> 7" + ] + }, + { + "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 Solution(object):\n", + "\n", + " def longest_substr(self, string, k):\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_longest_substr.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSolution(object):\n", + "\n", + " def test_longest_substr(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.longest_substr, None)\n", + " assert_equal(solution.longest_substr('', k=3), 0)\n", + " assert_equal(solution.longest_substr('abcabcdefgghiij', k=3), 6)\n", + " assert_equal(solution.longest_substr('abcabcdefgghighij', k=3), 7)\n", + " print('Success: test_longest_substr')\n", + "\n", + "\n", + "def main():\n", + " test = TestSolution()\n", + " test.test_longest_substr()\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.4.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/online_judges/longest_substr_k_distinct/longest_substr_solution.ipynb b/online_judges/longest_substr_k_distinct/longest_substr_solution.ipynb new file mode 100644 index 0000000..07615b4 --- /dev/null +++ b/online_judges/longest_substr_k_distinct/longest_substr_solution.ipynb @@ -0,0 +1,208 @@ +{ + "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: Find the length of the longest substring with at most k distinct characters.\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 assume the inputs are valid?\n", + " * No\n", + "* Can we assume the strings are ASCII?\n", + " * Yes\n", + "* Is this case sensitive?\n", + " * Yes\n", + "* Is a substring a contiguous block of chars?\n", + " * Yes\n", + "* Do we expect an int as a result?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> TypeError\n", + "* '', k = 3 -> 0\n", + "* 'abcabcdefgghiij', k=3 -> 6\n", + "* 'abcabcdefgghighij', k=3 -> 7" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "We'll use a `chars_to_index_map` dictionary: char (key) to index (val) map to maintain a sliding window.\n", + "\n", + "The index (val) will keep track of the character index in the input string.\n", + "\n", + "For each character in the string:\n", + "\n", + "* Add the char (key) and index (value) to the map\n", + "* If the length of our map is greater than k, then we'll need to eliminate one item\n", + " * Scan the map to find the lowest index and remove it\n", + " * The new lowest index will therefore be incremented by 1\n", + "* The max length will be the current index minus the lower index + 1\n", + "\n", + "Complexity:\n", + "* Time: O(n * k), where n is the number of chars, k is the length of the map due to the min() call\n", + "* Space: O(n)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def longest_substr(self, string, k):\n", + " if string is None:\n", + " raise TypeError('string cannot be None')\n", + " if k is None:\n", + " raise TypeError('k cannot be None')\n", + " low_index = 0\n", + " max_length = 0\n", + " chars_to_index_map = {}\n", + " for index, char in enumerate(string):\n", + " chars_to_index_map[char] = index\n", + " if len(chars_to_index_map) > k:\n", + " low_index = min(chars_to_index_map.values())\n", + " del chars_to_index_map[string[low_index]]\n", + " low_index += 1\n", + " max_length = max(max_length, index - low_index + 1)\n", + " return max_length" + ] + }, + { + "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_longest_substr.py\n" + ] + } + ], + "source": [ + "%%writefile test_longest_substr.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSolution(object):\n", + "\n", + " def test_longest_substr(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.longest_substr, None)\n", + " assert_equal(solution.longest_substr('', k=3), 0)\n", + " assert_equal(solution.longest_substr('abcabcdefgghiij', k=3), 6)\n", + " assert_equal(solution.longest_substr('abcabcdefgghighij', k=3), 7)\n", + " print('Success: test_longest_substr')\n", + "\n", + "\n", + "def main():\n", + " test = TestSolution()\n", + " test.test_longest_substr()\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_longest_substr\n" + ] + } + ], + "source": [ + "%run -i test_longest_substr.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/online_judges/longest_substr_k_distinct/test_longest_substr.py b/online_judges/longest_substr_k_distinct/test_longest_substr.py new file mode 100644 index 0000000..6410c7f --- /dev/null +++ b/online_judges/longest_substr_k_distinct/test_longest_substr.py @@ -0,0 +1,21 @@ +from nose.tools import assert_equal, assert_raises + + +class TestSolution(object): + + def test_longest_substr(self): + solution = Solution() + assert_raises(TypeError, solution.longest_substr, None) + assert_equal(solution.longest_substr('', k=3), 0) + assert_equal(solution.longest_substr('abcabcdefgghiij', k=3), 6) + assert_equal(solution.longest_substr('abcabcdefgghighij', k=3), 7) + print('Success: test_longest_substr') + + +def main(): + test = TestSolution() + test.test_longest_substr() + + +if __name__ == '__main__': + main() \ No newline at end of file From d3b4b79aa7679a88b5df1af7b56bbf6c1259cb26 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Wed, 29 Mar 2017 04:34:04 -0400 Subject: [PATCH 35/90] Add longest abs file path challenge --- .../longest_abs_file_path/__init__.py | 0 .../longest_path_challenge.ipynb | 206 +++++++++++++++ .../longest_path_solution.ipynb | 242 ++++++++++++++++++ .../test_length_longest_path.py | 22 ++ 4 files changed, 470 insertions(+) create mode 100644 online_judges/longest_abs_file_path/__init__.py create mode 100644 online_judges/longest_abs_file_path/longest_path_challenge.ipynb create mode 100644 online_judges/longest_abs_file_path/longest_path_solution.ipynb create mode 100644 online_judges/longest_abs_file_path/test_length_longest_path.py diff --git a/online_judges/longest_abs_file_path/__init__.py b/online_judges/longest_abs_file_path/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/online_judges/longest_abs_file_path/longest_path_challenge.ipynb b/online_judges/longest_abs_file_path/longest_path_challenge.ipynb new file mode 100644 index 0000000..07956fc --- /dev/null +++ b/online_judges/longest_abs_file_path/longest_path_challenge.ipynb @@ -0,0 +1,206 @@ +{ + "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: Find the longest absolute file path.\n", + "\n", + "See the [LeetCode](https://leetcode.com/problems/longest-absolute-file-path/) problem page.\n", + "\n", + "
\n",
+    "Suppose we abstract our file system by a string in the following manner:\n",
+    "\n",
+    "The string \"dir\\n\\tsubdir1\\n\\tsubdir2\\n\\t\\tfile.ext\" represents:\n",
+    "\n",
+    "dir\n",
+    "    subdir1\n",
+    "    subdir2\n",
+    "        file.ext\n",
+    "The directory dir contains an empty sub-directory subdir1 and a sub-directory subdir2 containing a file file.ext.\n",
+    "\n",
+    "The string \"dir\\n\\tsubdir1\\n\\t\\tfile1.ext\\n\\t\\tsubsubdir1\\n\\tsubdir2\\n\\t\\tsubsubdir2\\n\\t\\t\\tfile2.ext\" represents:\n",
+    "\n",
+    "dir\n",
+    "    subdir1\n",
+    "        file1.ext\n",
+    "        subsubdir1\n",
+    "    subdir2\n",
+    "        subsubdir2\n",
+    "            file2.ext\n",
+    "\n",
+    "The directory dir contains two sub-directories subdir1 and subdir2. subdir1 contains a file file1.ext and an empty second-level sub-directory subsubdir1. subdir2 contains a second-level sub-directory subsubdir2 containing a file file2.ext.\n",
+    "\n",
+    "We are interested in finding the longest (number of characters) absolute path to a file within our file system. For example, in the second example above, the longest absolute path is \"dir/subdir2/subsubdir2/file2.ext\", and its length is 32 (not including the double quotes).\n",
+    "\n",
+    "Given a string representing the file system in the above format, return the length of the longest absolute path to file in the abstracted file system. If there is no file in the system, return 0.\n",
+    "\n",
+    "Note:\n",
+    "The name of a file contains at least a . and an extension.\n",
+    "The name of a directory or sub-directory will not contain a .\n",
+    "Time complexity required: O(n) where n is the size of the input string.\n",
+    "\n",
+    "Notice that a/aa/aaa/file1.txt is not the longest file path, if there is another path aaaaaaaaaaaaaaaaaaaaa/sth.png.\n",
+    "
\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", + "* Is the input a string?\n", + " * Yes\n", + "* Can we assume the input is valid?\n", + " * No\n", + "* Will there always be a file in the input?\n", + " * Yes\n", + "* Is the output an int?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> TypeError\n", + "* '' -> 0\n", + "* 'dir\\n\\tsubdir1\\n\\t\\tfile1.ext\\n\\t\\tsubsubdir1\\n\\tsubdir2\\n\\t\\tsubsubdir2\\n\\t\\t\\tfile2.ext' -> 32" + ] + }, + { + "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 Solution(object):\n", + "\n", + " def length_longest_path(self, file_system):\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_length_longest_path.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSolution(object):\n", + "\n", + " def test_length_longest_path(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.length_longest_path, None)\n", + " assert_equal(solution.length_longest_path(''), 0)\n", + " file_system = 'dir\\n\\tsubdir1\\n\\t\\tfile1.ext\\n\\t\\tsubsubdir1\\n\\tsubdir2\\n\\t\\tsubsubdir2\\n\\t\\t\\tfile2.ext'\n", + " expected = 32\n", + " assert_equal(solution.length_longest_path(file_system), expected)\n", + " print('Success: test_length_longest_path')\n", + "\n", + "\n", + "def main():\n", + " test = TestSolution()\n", + " test.test_length_longest_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/online_judges/longest_abs_file_path/longest_path_solution.ipynb b/online_judges/longest_abs_file_path/longest_path_solution.ipynb new file mode 100644 index 0000000..f0d1d81 --- /dev/null +++ b/online_judges/longest_abs_file_path/longest_path_solution.ipynb @@ -0,0 +1,242 @@ +{ + "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: Find the longest absolute file path.\n", + "\n", + "See the [LeetCode](https://leetcode.com/problems/longest-absolute-file-path/) problem page.\n", + "\n", + "
\n",
+    "Suppose we abstract our file system by a string in the following manner:\n",
+    "\n",
+    "The string \"dir\\n\\tsubdir1\\n\\tsubdir2\\n\\t\\tfile.ext\" represents:\n",
+    "\n",
+    "dir\n",
+    "    subdir1\n",
+    "    subdir2\n",
+    "        file.ext\n",
+    "The directory dir contains an empty sub-directory subdir1 and a sub-directory subdir2 containing a file file.ext.\n",
+    "\n",
+    "The string \"dir\\n\\tsubdir1\\n\\t\\tfile1.ext\\n\\t\\tsubsubdir1\\n\\tsubdir2\\n\\t\\tsubsubdir2\\n\\t\\t\\tfile2.ext\" represents:\n",
+    "\n",
+    "dir\n",
+    "    subdir1\n",
+    "        file1.ext\n",
+    "        subsubdir1\n",
+    "    subdir2\n",
+    "        subsubdir2\n",
+    "            file2.ext\n",
+    "\n",
+    "The directory dir contains two sub-directories subdir1 and subdir2. subdir1 contains a file file1.ext and an empty second-level sub-directory subsubdir1. subdir2 contains a second-level sub-directory subsubdir2 containing a file file2.ext.\n",
+    "\n",
+    "We are interested in finding the longest (number of characters) absolute path to a file within our file system. For example, in the second example above, the longest absolute path is \"dir/subdir2/subsubdir2/file2.ext\", and its length is 32 (not including the double quotes).\n",
+    "\n",
+    "Given a string representing the file system in the above format, return the length of the longest absolute path to file in the abstracted file system. If there is no file in the system, return 0.\n",
+    "\n",
+    "Note:\n",
+    "The name of a file contains at least a . and an extension.\n",
+    "The name of a directory or sub-directory will not contain a ..\n",
+    "Time complexity required: O(n) where n is the size of the input string.\n",
+    "\n",
+    "Notice that a/aa/aaa/file1.txt is not the longest file path, if there is another path aaaaaaaaaaaaaaaaaaaaa/sth.png.\n",
+    "
\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", + "* Is the input a string?\n", + " * Yes\n", + "* Can we assume the input is valid?\n", + " * No\n", + "* Will there always be a file in the input?\n", + " * Yes\n", + "* Is the output an int?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> TypeError\n", + "* '' -> 0\n", + "* 'dir\\n\\tsubdir1\\n\\t\\tfile1.ext\\n\\t\\tsubsubdir1\\n\\tsubdir2\\n\\t\\tsubsubdir2\\n\\t\\t\\tfile2.ext' -> 32" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "We'll use a dictionary `path_len` to keep track of the current character length (values) at each depth (keys). Depth 0 will have a length of 0.\n", + "\n", + "Initialize `max_len` to 0.\n", + "\n", + "Split the input based on the newline character. Iterate on each resulting line:\n", + "\n", + "* Extract the `name` by excluding the tab characters\n", + "* Calculate the depth length by taking into account the tab characters\n", + "* If we are dealing with a file path (there is a '.' in `name`)\n", + " * Calculate `path_len[depth] + len(name)` and update `max_len` if needed\n", + "* Else, update `path_len[depth + 1] = path_len[depth] + len(name) + 1`\n", + " * We add `+ 1` because each depth is separated by the '/' character\n", + "* Return `max_len`\n", + "\n", + "Complexity:\n", + "* Time: O(n)\n", + "* Space: O(n)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def length_longest_path(self, file_system):\n", + " if file_system is None:\n", + " raise TypeError('file_system cannot be None')\n", + " max_len = 0\n", + " path_len = {0: 0}\n", + " for line in file_system.splitlines():\n", + " name = line.lstrip('\\t')\n", + " depth = len(line) - len(name)\n", + " if '.' in name:\n", + " max_len = max(max_len, path_len[depth] + len(name))\n", + " else:\n", + " path_len[depth + 1] = path_len[depth] + len(name) + 1\n", + " return max_len" + ] + }, + { + "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_length_longest_path.py\n" + ] + } + ], + "source": [ + "%%writefile test_length_longest_path.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSolution(object):\n", + "\n", + " def test_length_longest_path(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.length_longest_path, None)\n", + " assert_equal(solution.length_longest_path(''), 0)\n", + " file_system = 'dir\\n\\tsubdir1\\n\\t\\tfile1.ext\\n\\t\\tsubsubdir1\\n\\tsubdir2\\n\\t\\tsubsubdir2\\n\\t\\t\\tfile2.ext'\n", + " expected = 32\n", + " assert_equal(solution.length_longest_path(file_system), expected)\n", + " print('Success: test_length_longest_path')\n", + "\n", + "\n", + "def main():\n", + " test = TestSolution()\n", + " test.test_length_longest_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_length_longest_path\n" + ] + } + ], + "source": [ + "%run -i test_length_longest_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/online_judges/longest_abs_file_path/test_length_longest_path.py b/online_judges/longest_abs_file_path/test_length_longest_path.py new file mode 100644 index 0000000..6ca9568 --- /dev/null +++ b/online_judges/longest_abs_file_path/test_length_longest_path.py @@ -0,0 +1,22 @@ +from nose.tools import assert_equal, assert_raises + + +class TestSolution(object): + + def test_length_longest_path(self): + solution = Solution() + assert_raises(TypeError, solution.length_longest_path, None) + assert_equal(solution.length_longest_path(''), 0) + file_system = 'dir\n\tsubdir1\n\t\tfile1.ext\n\t\tsubsubdir1\n\tsubdir2\n\t\tsubsubdir2\n\t\t\tfile2.ext' + expected = 32 + assert_equal(solution.length_longest_path(file_system), expected) + print('Success: test_length_longest_path') + + +def main(): + test = TestSolution() + test.test_length_longest_path() + + +if __name__ == '__main__': + main() \ No newline at end of file From f3aecac7786a52200de3264a3ba6fe46592fd3a6 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Wed, 29 Mar 2017 04:34:36 -0400 Subject: [PATCH 36/90] Add license key challenge --- online_judges/license_key/__init__.py | 0 .../format_license_key_challenge.ipynb | 202 +++++++++++++++ .../format_license_key_solution.ipynb | 236 ++++++++++++++++++ .../license_key/test_format_license_key.py | 29 +++ 4 files changed, 467 insertions(+) create mode 100644 online_judges/license_key/__init__.py create mode 100644 online_judges/license_key/format_license_key_challenge.ipynb create mode 100644 online_judges/license_key/format_license_key_solution.ipynb create mode 100644 online_judges/license_key/test_format_license_key.py diff --git a/online_judges/license_key/__init__.py b/online_judges/license_key/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/online_judges/license_key/format_license_key_challenge.ipynb b/online_judges/license_key/format_license_key_challenge.ipynb new file mode 100644 index 0000000..4a849a0 --- /dev/null +++ b/online_judges/license_key/format_license_key_challenge.ipynb @@ -0,0 +1,202 @@ +{ + "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: Format license keys.\n", + "\n", + "See the [LeetCode](https://leetcode.com/problems/license-key-formatting/) problem page.\n", + "\n", + "
\n",
+    "Now you are given a string S, which represents a software license key which we would like to format. The string S is composed of alphanumerical characters and dashes. The dashes split the alphanumerical characters within the string into groups. (i.e. if there are M dashes, the string is split into M+1 groups). The dashes in the given string are possibly misplaced.\n",
+    "\n",
+    "We want each group of characters to be of length K (except for possibly the first group, which could be shorter, but still must contain at least one character). To satisfy this requirement, we will reinsert dashes. Additionally, all the lower case letters in the string must be converted to upper case.\n",
+    "\n",
+    "So, you are given a non-empty string S, representing a license key to format, and an integer K. And you need to return the license key formatted according to the description above.\n",
+    "\n",
+    "Example 1:\n",
+    "Input: S = \"2-4A0r7-4k\", K = 4\n",
+    "\n",
+    "Output: \"24A0-R74K\"\n",
+    "\n",
+    "Explanation: The string S has been split into two parts, each part has 4 characters.\n",
+    "Example 2:\n",
+    "Input: S = \"2-4A0r7-4k\", K = 3\n",
+    "\n",
+    "Output: \"24-A0R-74K\"\n",
+    "\n",
+    "Explanation: The string S has been split into three parts, each part has 3 characters except the first part as it could be shorter as said above.\n",
+    "Note:\n",
+    "The length of string S will not exceed 12,000, and K is a positive integer.\n",
+    "String S consists only of alphanumerical characters (a-z and/or A-Z and/or 0-9) and dashes(-).\n",
+    "String S is non-empty.\n",
+    "
\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", + "* Is the output a string?\n", + " * Yes\n", + "* Can we change the input string?\n", + " * No, you can't modify the input string\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 -> TypeError\n", + "* '---', k=3 -> ''\n", + "* '2-4A0r7-4k', k=3 -> '24-A0R-74K'\n", + "* '2-4A0r7-4k', k=4 -> '24A0-R74K'" + ] + }, + { + "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 Solution(object):\n", + "\n", + " def format_license_key(self, license_key, k):\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_format_license_key.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSolution(object):\n", + "\n", + " def test_format_license_key(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.format_license_key, None, None)\n", + " license_key = '---'\n", + " k = 3\n", + " expected = ''\n", + " assert_equal(solution.format_license_key(license_key, k), expected)\n", + " license_key = '2-4A0r7-4k'\n", + " k = 3\n", + " expected = '24-A0R-74K'\n", + " assert_equal(solution.format_license_key(license_key, k), expected)\n", + " license_key = '2-4A0r7-4k'\n", + " k = 4\n", + " expected = '24A0-R74K'\n", + " assert_equal(solution.format_license_key(license_key, k), expected)\n", + " print('Success: test_format_license_key')\n", + "\n", + "def main():\n", + " test = TestSolution()\n", + " test.test_format_license_key()\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/online_judges/license_key/format_license_key_solution.ipynb b/online_judges/license_key/format_license_key_solution.ipynb new file mode 100644 index 0000000..e705025 --- /dev/null +++ b/online_judges/license_key/format_license_key_solution.ipynb @@ -0,0 +1,236 @@ +{ + "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: Format license keys.\n", + "\n", + "See the [LeetCode](https://leetcode.com/problems/license-key-formatting/) problem page.\n", + "\n", + "
\n",
+    "Now you are given a string S, which represents a software license key which we would like to format. The string S is composed of alphanumerical characters and dashes. The dashes split the alphanumerical characters within the string into groups. (i.e. if there are M dashes, the string is split into M+1 groups). The dashes in the given string are possibly misplaced.\n",
+    "\n",
+    "We want each group of characters to be of length K (except for possibly the first group, which could be shorter, but still must contain at least one character). To satisfy this requirement, we will reinsert dashes. Additionally, all the lower case letters in the string must be converted to upper case.\n",
+    "\n",
+    "So, you are given a non-empty string S, representing a license key to format, and an integer K. And you need to return the license key formatted according to the description above.\n",
+    "\n",
+    "Example 1:\n",
+    "Input: S = \"2-4A0r7-4k\", K = 4\n",
+    "\n",
+    "Output: \"24A0-R74K\"\n",
+    "\n",
+    "Explanation: The string S has been split into two parts, each part has 4 characters.\n",
+    "Example 2:\n",
+    "Input: S = \"2-4A0r7-4k\", K = 3\n",
+    "\n",
+    "Output: \"24-A0R-74K\"\n",
+    "\n",
+    "Explanation: The string S has been split into three parts, each part has 3 characters except the first part as it could be shorter as said above.\n",
+    "\n",
+    "Note:\n",
+    "The length of string S will not exceed 12,000, and K is a positive integer.\n",
+    "String S consists only of alphanumerical characters (a-z and/or A-Z and/or 0-9) and dashes(-).\n",
+    "String S is non-empty.\n",
+    "
\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", + "* Is the output a string?\n", + " * Yes\n", + "* Can we change the input string?\n", + " * No, you can't modify the input string\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 -> TypeError\n", + "* '---', k=3 -> ''\n", + "* '2-4A0r7-4k', k=3 -> '24-A0R-74K'\n", + "* '2-4A0r7-4k', k=4 -> '24A0-R74K'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "* Loop through each character in the license key backwards, keeping a count of the number of chars we've reached so far, while inserting each character into a result list (convert to upper case)\n", + " * If we reach a '-', skip it\n", + " * Whenever we reach a char count of k, append a '-' character to the result list, reset the char count\n", + "* Careful that we don't have a leading '-', which we might hit with test case: '2-4A0r7-4k', k=4 -> '24A0-R74K'\n", + "* Reverse the result list and return it\n", + "\n", + "Complexity:\n", + "* Time: O(n)\n", + "* Space: O(n)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def format_license_key(self, license_key, k):\n", + " if license_key is None:\n", + " raise TypeError('license_key must be a str')\n", + " if not license_key:\n", + " raise ValueError('license_key must not be empty')\n", + " formatted_license_key = []\n", + " num_chars = 0\n", + " for char in license_key[::-1]:\n", + " if char == '-':\n", + " continue\n", + " num_chars += 1\n", + " formatted_license_key.append(char.upper())\n", + " if num_chars >= k:\n", + " formatted_license_key.append('-')\n", + " num_chars = 0\n", + " if formatted_license_key and formatted_license_key[-1] == '-':\n", + " formatted_license_key.pop(-1)\n", + " return ''.join(formatted_license_key[::-1])" + ] + }, + { + "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_format_license_key.py\n" + ] + } + ], + "source": [ + "%%writefile test_format_license_key.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSolution(object):\n", + "\n", + " def test_format_license_key(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.format_license_key, None, None)\n", + " license_key = '---'\n", + " k = 3\n", + " expected = ''\n", + " assert_equal(solution.format_license_key(license_key, k), expected)\n", + " license_key = '2-4A0r7-4k'\n", + " k = 3\n", + " expected = '24-A0R-74K'\n", + " assert_equal(solution.format_license_key(license_key, k), expected)\n", + " license_key = '2-4A0r7-4k'\n", + " k = 4\n", + " expected = '24A0-R74K'\n", + " assert_equal(solution.format_license_key(license_key, k), expected)\n", + " print('Success: test_format_license_key')\n", + "\n", + "def main():\n", + " test = TestSolution()\n", + " test.test_format_license_key()\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_format_license_key\n" + ] + } + ], + "source": [ + "%run -i test_format_license_key.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/online_judges/license_key/test_format_license_key.py b/online_judges/license_key/test_format_license_key.py new file mode 100644 index 0000000..23f8999 --- /dev/null +++ b/online_judges/license_key/test_format_license_key.py @@ -0,0 +1,29 @@ +from nose.tools import assert_equal, assert_raises + + +class TestSolution(object): + + def test_format_license_key(self): + solution = Solution() + assert_raises(TypeError, solution.format_license_key, None, None) + license_key = '---' + k = 3 + expected = '' + assert_equal(solution.format_license_key(license_key, k), expected) + license_key = '2-4A0r7-4k' + k = 3 + expected = '24-A0R-74K' + assert_equal(solution.format_license_key(license_key, k), expected) + license_key = '2-4A0r7-4k' + k = 4 + expected = '24A0-R74K' + assert_equal(solution.format_license_key(license_key, k), expected) + print('Success: test_format_license_key') + +def main(): + test = TestSolution() + test.test_format_license_key() + + +if __name__ == '__main__': + main() \ No newline at end of file From 6dd27da4d675947136a72e59cf4510c573c66b64 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Wed, 29 Mar 2017 04:35:01 -0400 Subject: [PATCH 37/90] Add island perimeter challenge --- online_judges/island_perimeter/__init__.py | 0 .../island_perimeter_challenge.ipynb | 188 +++++++++++++++ .../island_perimeter_solution.ipynb | 223 ++++++++++++++++++ .../island_perimeter/test_island_perimeter.py | 27 +++ 4 files changed, 438 insertions(+) create mode 100644 online_judges/island_perimeter/__init__.py create mode 100644 online_judges/island_perimeter/island_perimeter_challenge.ipynb create mode 100644 online_judges/island_perimeter/island_perimeter_solution.ipynb create mode 100644 online_judges/island_perimeter/test_island_perimeter.py diff --git a/online_judges/island_perimeter/__init__.py b/online_judges/island_perimeter/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/online_judges/island_perimeter/island_perimeter_challenge.ipynb b/online_judges/island_perimeter/island_perimeter_challenge.ipynb new file mode 100644 index 0000000..d16d5f7 --- /dev/null +++ b/online_judges/island_perimeter/island_perimeter_challenge.ipynb @@ -0,0 +1,188 @@ +{ + "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: Island Perimeter.\n", + "\n", + "See the [LeetCode](https://leetcode.com/problems/island-perimeter/) problem page.\n", + "\n", + "You are given a map in form of a two-dimensional integer grid where 1 represents land and 0 represents water. Grid cells are connected horizontally/vertically (not diagonally). The grid is completely surrounded by water, and there is exactly one island (i.e., one or more connected land cells). The island doesn't have \"lakes\" (water inside that isn't connected to the water around the island). One cell is a square with side length 1. The grid is rectangular, width and height don't exceed 100. Determine the perimeter of the island.\n", + "\n", + "Example:\n", + "\n", + "
\n",
+    "[[0,1,0,0],\n",
+    " [1,1,1,0],\n",
+    " [0,1,0,0],\n",
+    " [1,1,0,0]]\n",
+    "
\n", + "\n", + "Answer: 16\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 assume the inputs are valid?\n", + " * No, check for None\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "
\n",
+    "* None -> TypeError\n",
+    "* [[1, 0]] -> 4\n",
+    "* [[0, 1, 0, 0],\n",
+    "   [1, 1, 1, 0],\n",
+    "   [0, 1, 0, 0],\n",
+    "   [1, 1, 0, 0]] -> 16\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": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def island_perimeter(self, grid):\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_island_perimeter.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestIslandPerimeter(object):\n", + "\n", + " def test_island_perimeter(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.island_perimeter, None)\n", + " data = [[1, 0]]\n", + " expected = 4\n", + " assert_equal(solution.island_perimeter(data), expected)\n", + " data = [[0, 1, 0, 0],\n", + " [1, 1, 1, 0],\n", + " [0, 1, 0, 0],\n", + " [1, 1, 0, 0]]\n", + " expected = 16\n", + " assert_equal(solution.island_perimeter(data), expected)\n", + " print('Success: test_island_perimeter')\n", + "\n", + "\n", + "def main():\n", + " test = TestIslandPerimeter()\n", + " test.test_island_perimeter()\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/online_judges/island_perimeter/island_perimeter_solution.ipynb b/online_judges/island_perimeter/island_perimeter_solution.ipynb new file mode 100644 index 0000000..72d4bb5 --- /dev/null +++ b/online_judges/island_perimeter/island_perimeter_solution.ipynb @@ -0,0 +1,223 @@ +{ + "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: Island Perimeter.\n", + "\n", + "See the [LeetCode](https://leetcode.com/problems/island-perimeter/) problem page.\n", + "\n", + "You are given a map in form of a two-dimensional integer grid where 1 represents land and 0 represents water. Grid cells are connected horizontally/vertically (not diagonally). The grid is completely surrounded by water, and there is exactly one island (i.e., one or more connected land cells). The island doesn't have \"lakes\" (water inside that isn't connected to the water around the island). One cell is a square with side length 1. The grid is rectangular, width and height don't exceed 100. Determine the perimeter of the island.\n", + "\n", + "Example:\n", + "\n", + "
\n",
+    "[[0,1,0,0],\n",
+    " [1,1,1,0],\n",
+    " [0,1,0,0],\n",
+    " [1,1,0,0]]\n",
+    "
\n", + "\n", + "Answer: 16\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 assume the inputs are valid?\n", + " * No, check for None\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "
\n",
+    "* None -> TypeError\n",
+    "* [[1, 0]] -> 4\n",
+    "* [[0, 1, 0, 0],\n",
+    "   [1, 1, 1, 0],\n",
+    "   [0, 1, 0, 0],\n",
+    "   [1, 1, 0, 0]] -> 16\n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "For each cell in the grid:\n", + "* Check left, right, up, down\n", + " * For each check, if we are at the edge or the cell we are checking is land, increment sides\n", + "\n", + "Complexity:\n", + "* Time: O(rows * cols)\n", + "* Space: O(1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def island_perimeter(self, grid):\n", + " if grid is None:\n", + " raise TypeError('grid cannot be None')\n", + " sides = 0\n", + " num_rows = len(grid)\n", + " num_cols = len(grid[0])\n", + " for i in range(num_rows):\n", + " for j in range(num_cols):\n", + " if grid[i][j] == 1:\n", + " # Check left\n", + " if j == 0 or grid[i][j - 1] == 0:\n", + " sides += 1\n", + " # Check right\n", + " if j == num_cols - 1 or grid[i][j + 1] == 0:\n", + " sides += 1\n", + " # Check up\n", + " if i == 0 or grid[i - 1][j] == 0:\n", + " sides += 1\n", + " # Check down\n", + " if i == num_rows - 1 or grid[i + 1][j] == 0:\n", + " sides += 1\n", + " return sides" + ] + }, + { + "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_island_perimeter.py\n" + ] + } + ], + "source": [ + "%%writefile test_island_perimeter.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestIslandPerimeter(object):\n", + "\n", + " def test_island_perimeter(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.island_perimeter, None)\n", + " data = [[1, 0]]\n", + " expected = 4\n", + " assert_equal(solution.island_perimeter(data), expected)\n", + " data = [[0, 1, 0, 0],\n", + " [1, 1, 1, 0],\n", + " [0, 1, 0, 0],\n", + " [1, 1, 0, 0]]\n", + " expected = 16\n", + " assert_equal(solution.island_perimeter(data), expected)\n", + " print('Success: test_island_perimeter')\n", + "\n", + "\n", + "def main():\n", + " test = TestIslandPerimeter()\n", + " test.test_island_perimeter()\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_island_perimeter\n" + ] + } + ], + "source": [ + "%run -i test_island_perimeter.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/online_judges/island_perimeter/test_island_perimeter.py b/online_judges/island_perimeter/test_island_perimeter.py new file mode 100644 index 0000000..95d3a96 --- /dev/null +++ b/online_judges/island_perimeter/test_island_perimeter.py @@ -0,0 +1,27 @@ +from nose.tools import assert_equal, assert_raises + + +class TestIslandPerimeter(object): + + def test_island_perimeter(self): + solution = Solution() + assert_raises(TypeError, solution.island_perimeter, None) + data = [[1, 0]] + expected = 4 + assert_equal(solution.island_perimeter(data), expected) + data = [[0, 1, 0, 0], + [1, 1, 1, 0], + [0, 1, 0, 0], + [1, 1, 0, 0]] + expected = 16 + assert_equal(solution.island_perimeter(data), expected) + print('Success: test_island_perimeter') + + +def main(): + test = TestIslandPerimeter() + test.test_island_perimeter() + + +if __name__ == '__main__': + main() \ No newline at end of file From f7c4ac0d45266ca8c59420038dcc1485d363dffc Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Wed, 29 Mar 2017 04:35:42 -0400 Subject: [PATCH 38/90] Add busiest period challenge --- online_judges/busiest_period/__init__.py | 0 .../busiest_period_challenge.ipynb | 236 ++++++++++++++ .../busiest_period_solution.ipynb | 294 ++++++++++++++++++ .../test_find_busiest_period.py | 28 ++ 4 files changed, 558 insertions(+) create mode 100644 online_judges/busiest_period/__init__.py create mode 100644 online_judges/busiest_period/busiest_period_challenge.ipynb create mode 100644 online_judges/busiest_period/busiest_period_solution.ipynb create mode 100644 online_judges/busiest_period/test_find_busiest_period.py diff --git a/online_judges/busiest_period/__init__.py b/online_judges/busiest_period/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/online_judges/busiest_period/busiest_period_challenge.ipynb b/online_judges/busiest_period/busiest_period_challenge.ipynb new file mode 100644 index 0000000..6a0c77c --- /dev/null +++ b/online_judges/busiest_period/busiest_period_challenge.ipynb @@ -0,0 +1,236 @@ +{ + "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 an array of (unix_timestamp, num_people, EventType.ENTER or EventType.EXIT), find the busiest period.\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 assume the input array is valid?\n", + " * Check for None\n", + "* Can we assume the elements of the input array are valid?\n", + " * Yes\n", + "* Is the input sorted by time?\n", + " * No\n", + "* Can you have enter and exit elements for the same timestamp?\n", + " * Yes you can, order of enter and exit is not guaranteed\n", + "* Could we have multiple enter events (or multiple exit events) for the same timestamp?\n", + " * No\n", + "* What is the format of the output?\n", + " * An array of timestamps [t1, t2]\n", + "* Can we assume the starting number of people is zero?\n", + " * Yes\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 -> TypeError\n", + "* [] -> None\n", + "* General case\n", + "\n", + "
\n",
+    "timestamp  num_people  event_type\n",
+    "1          2           EventType.ENTER\n",
+    "3          1           EventType.ENTER\n",
+    "3          2           EventType.EXIT\n",
+    "7          3           EventType.ENTER\n",
+    "8          2           EventType.EXIT\n",
+    "9          2           EventType.EXIT\n",
+    "\n",
+    "result = Period(7, 8)\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": [ + "from enum import Enum\n", + "\n", + "\n", + "class Data(object):\n", + "\n", + " def __init__(self, timestamp, num_people, event_type):\n", + " self.timestamp = timestamp\n", + " self.num_people = num_people\n", + " self.event_type = event_type\n", + "\n", + " def __lt__(self, other):\n", + " return self.timestamp < other.timestamp\n", + "\n", + "\n", + "class Period(object):\n", + "\n", + " def __init__(self, start, end):\n", + " self.start = start\n", + " self.end = end\n", + "\n", + " def __eq__(self, other):\n", + " return self.start == other.start and self.end == other.end\n", + "\n", + " def __repr__(self):\n", + " return str(self.start) + ', ' + str(self.end)\n", + "\n", + "\n", + "class EventType(Enum):\n", + "\n", + " ENTER = 0\n", + " EXIT = 1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def find_busiest_period(self, data):\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_find_busiest_period.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSolution(object):\n", + "\n", + " def test_find_busiest_period(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.find_busiest_period, None)\n", + " assert_equal(solution.find_busiest_period([]), None)\n", + " data = [\n", + " Data(3, 2, EventType.EXIT),\n", + " Data(1, 2, EventType.ENTER),\n", + " Data(3, 1, EventType.ENTER),\n", + " Data(7, 3, EventType.ENTER),\n", + " Data(9, 2, EventType.EXIT),\n", + " Data(8, 2, EventType.EXIT),\n", + " ]\n", + " assert_equal(solution.find_busiest_period(data), Period(7, 8))\n", + " print('Success: test_find_busiest_period')\n", + "\n", + "\n", + "def main():\n", + " test = TestSolution()\n", + " test.test_find_busiest_period()\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/online_judges/busiest_period/busiest_period_solution.ipynb b/online_judges/busiest_period/busiest_period_solution.ipynb new file mode 100644 index 0000000..21fa455 --- /dev/null +++ b/online_judges/busiest_period/busiest_period_solution.ipynb @@ -0,0 +1,294 @@ +{ + "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 an array of (unix_timestamp, num_people, EventType.ENTER or EventType.EXIT), find the busiest period.\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 assume the input array is valid?\n", + " * Check for None\n", + "* Can we assume the elements of the input array are valid?\n", + " * Yes\n", + "* Is the input sorted by time?\n", + " * No\n", + "* Can you have enter and exit elements for the same timestamp?\n", + " * Yes you can, order of enter and exit is not guaranteed\n", + "* Could we have multiple enter events (or multiple exit events) for the same timestamp?\n", + " * No\n", + "* What is the format of the output?\n", + " * An array of timestamps [t1, t2]\n", + "* Can we assume the starting number of people is zero?\n", + " * Yes\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 -> TypeError\n", + "* [] -> None\n", + "* General case\n", + "\n", + "
\n",
+    "timestamp  num_people  event_type\n",
+    "3          2           EventType.EXIT\n",
+    "1          2           EventType.ENTER\n",
+    "3          1           EventType.ENTER\n",
+    "7          3           EventType.ENTER\n",
+    "9          2           EventType.EXIT\n",
+    "8          2           EventType.EXIT\n",
+    "\n",
+    "result = Period(7, 8)\n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "Since the input is not sorted, we'll need to sort it first by timestamp, ascending.\n", + "\n", + "For each interval in the data set:\n", + "\n", + "* If this is an \"enter\" event, increment `curr_people`, else, decrement\n", + "* Since we can have an \"enter\" and \"exit\" event for the same timestamp, we'll need to look ahead one\n", + " * If the next element has the same timestamp, hold off (continue) on updating `max_people` and `max_period`\n", + " * Watch out for indexing out-of-bounds at the end of the array\n", + "* Update `max_people` and `max_period`\n", + "\n", + "Sorted:\n", + "\n", + "
\n",
+    "timestamp  num_people  event_type       curr_people  max_people       max_period\n",
+    "1          2           EventType.ENTER  2            2                [1, 3]\n",
+    "3          1           EventType.ENTER  3            2 (not updated)  [1, 3]\n",
+    "3          2           EventType.EXIT   1            2                [3, 7]\n",
+    "7          3           EventType.ENTER  4            4                [7, 8]\n",
+    "8          2           EventType.EXIT   2            4                [7, 8]\n",
+    "9          2           EventType.EXIT   0            4                [7, 8]\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(nlog(n)) for the sort\n", + "* Space: O(1), assuming the sort is in-place" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from enum import Enum\n", + "\n", + "\n", + "class Data(object):\n", + "\n", + " def __init__(self, timestamp, num_people, event_type):\n", + " self.timestamp = timestamp\n", + " self.num_people = num_people\n", + " self.event_type = event_type\n", + "\n", + " def __lt__(self, other):\n", + " return self.timestamp < other.timestamp\n", + "\n", + "\n", + "class Period(object):\n", + "\n", + " def __init__(self, start, end):\n", + " self.start = start\n", + " self.end = end\n", + "\n", + " def __eq__(self, other):\n", + " return self.start == other.start and self.end == other.end\n", + "\n", + " def __repr__(self):\n", + " return str(self.start) + ', ' + str(self.end)\n", + "\n", + "\n", + "class EventType(Enum):\n", + "\n", + " ENTER = 0\n", + " EXIT = 1" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def find_busiest_period(self, data):\n", + " if data is None:\n", + " raise TypeError('data cannot be None')\n", + " if not data:\n", + " return None\n", + " data.sort()\n", + " max_period = Period(0, 0)\n", + " max_people = 0\n", + " curr_people = 0\n", + " for index, interval in enumerate(data):\n", + " if interval.event_type == EventType.ENTER:\n", + " curr_people += interval.num_people\n", + " elif interval.event_type == EventType.EXIT:\n", + " curr_people -= interval.num_people\n", + " else:\n", + " raise ValueError('Invalid event type')\n", + " if (index < len(data) - 1 and \n", + " data[index].timestamp == data[index + 1].timestamp):\n", + " continue\n", + " if curr_people > max_people:\n", + " max_people = curr_people\n", + " max_period.start = data[index].timestamp\n", + " if index < len(data) - 1:\n", + " max_period.end = data[index + 1].timestamp\n", + " else:\n", + " max_period.end = data[index].timestamp\n", + " return max_period" + ] + }, + { + "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_find_busiest_period.py\n" + ] + } + ], + "source": [ + "%%writefile test_find_busiest_period.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSolution(object):\n", + "\n", + " def test_find_busiest_period(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.find_busiest_period, None)\n", + " assert_equal(solution.find_busiest_period([]), None)\n", + " data = [\n", + " Data(3, 2, EventType.EXIT),\n", + " Data(1, 2, EventType.ENTER),\n", + " Data(3, 1, EventType.ENTER),\n", + " Data(7, 3, EventType.ENTER),\n", + " Data(9, 2, EventType.EXIT),\n", + " Data(8, 2, EventType.EXIT),\n", + " ]\n", + " assert_equal(solution.find_busiest_period(data), Period(7, 8))\n", + " print('Success: test_find_busiest_period')\n", + "\n", + "\n", + "def main():\n", + " test = TestSolution()\n", + " test.test_find_busiest_period()\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_find_busiest_period\n" + ] + } + ], + "source": [ + "%run -i test_find_busiest_period.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/online_judges/busiest_period/test_find_busiest_period.py b/online_judges/busiest_period/test_find_busiest_period.py new file mode 100644 index 0000000..f7c4ed0 --- /dev/null +++ b/online_judges/busiest_period/test_find_busiest_period.py @@ -0,0 +1,28 @@ +from nose.tools import assert_equal, assert_raises + + +class TestSolution(object): + + def test_find_busiest_period(self): + solution = Solution() + assert_raises(TypeError, solution.find_busiest_period, None) + assert_equal(solution.find_busiest_period([]), None) + data = [ + Data(3, 2, EventType.EXIT), + Data(1, 2, EventType.ENTER), + Data(3, 1, EventType.ENTER), + Data(7, 3, EventType.ENTER), + Data(9, 2, EventType.EXIT), + Data(8, 2, EventType.EXIT), + ] + assert_equal(solution.find_busiest_period(data), Period(7, 8)) + print('Success: test_find_busiest_period') + + +def main(): + test = TestSolution() + test.test_find_busiest_period() + + +if __name__ == '__main__': + main() \ No newline at end of file From d8514831153c5153eec75581d8ebec520877854a Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Wed, 29 Mar 2017 04:36:40 -0400 Subject: [PATCH 39/90] Add assign cookies challenge --- online_judges/assign_cookies/__init__.py | 0 .../assign_cookies_challenge.ipynb | 203 +++++++++++++++ .../assign_cookies_solution.ipynb | 237 ++++++++++++++++++ .../assign_cookies/test_assign_cookie.py | 24 ++ 4 files changed, 464 insertions(+) create mode 100644 online_judges/assign_cookies/__init__.py create mode 100644 online_judges/assign_cookies/assign_cookies_challenge.ipynb create mode 100644 online_judges/assign_cookies/assign_cookies_solution.ipynb create mode 100644 online_judges/assign_cookies/test_assign_cookie.py diff --git a/online_judges/assign_cookies/__init__.py b/online_judges/assign_cookies/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/online_judges/assign_cookies/assign_cookies_challenge.ipynb b/online_judges/assign_cookies/assign_cookies_challenge.ipynb new file mode 100644 index 0000000..d4c2649 --- /dev/null +++ b/online_judges/assign_cookies/assign_cookies_challenge.ipynb @@ -0,0 +1,203 @@ +{ + "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: Assign Cookies.\n", + "\n", + "See the [LeetCode](https://leetcode.com/problems/assign-cookies/) problem page.\n", + "\n", + "Assume you are an awesome parent and want to give your children some cookies. But, you should give each child at most one cookie. Each child i has a greed factor gi, which is the minimum size of a cookie that the child will be content with; and each cookie j has a size sj. If sj >= gi, we can assign the cookie j to the child i, and the child i will be content. Your goal is to maximize the number of your content children and output the maximum number.\n", + "\n", + "Note:\n", + "You may assume the greed factor is always positive. \n", + "You cannot assign more than one cookie to one child.\n", + "\n", + "Example 1:\n", + "Input: [1,2,3], [1,1]\n", + "\n", + "Output: 1\n", + "\n", + "Explanation: You have 3 children and 2 cookies. The greed factors of 3 children are 1, 2, 3. \n", + "And even though you have 2 cookies, since their size is both 1, you could only make the child whose greed factor is 1 content.\n", + "You need to output 1.\n", + "Example 2:\n", + "Input: [1,2], [1,2,3]\n", + "\n", + "Output: 2\n", + "\n", + "Explanation: You have 2 children and 3 cookies. The greed factors of 2 children are 1, 2. \n", + "You have 3 cookies and their sizes are big enough to gratify all of the children, \n", + "You need to output 2.\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 the inputs two list(int), one for greed factor and the other for cookie size?\n", + " * Yes\n", + "* Are the inputs are sorted increasing order?\n", + " * No\n", + "* Can we change inputs themselves, or do we need to make a copy?\n", + " * You can change them\n", + "* Is the output an int?\n", + " * Yes\n", + "* Is the greed factor always >= 1?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * No, check for None\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "
\n",
+    "* None input -> TypeError\n",
+    "[1, 2, 3], [1, 1] -> 1\n",
+    "[1, 2], [1, 2, 3] -> 2\n",
+    "[7, 8, 9, 10], [5, 6, 7, 8] -> 2\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": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def find_content_children(self, g, s):\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_assign_cookie.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestAssignCookie(object):\n", + "\n", + " def test_assign_cookie(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.find_content_children, None, None)\n", + " assert_equal(solution.find_content_children([1, 2, 3], \n", + " [1, 1]), 1)\n", + " assert_equal(solution.find_content_children([1, 2], \n", + " [1, 2, 3]), 2)\n", + " assert_equal(solution.find_content_children([7, 8, 9, 10], \n", + " [5, 6, 7, 8]), 2)\n", + " print('Success: test_find_content_children')\n", + "\n", + "\n", + "def main():\n", + " test = TestAssignCookie()\n", + " test.test_assign_cookie()\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.4.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/online_judges/assign_cookies/assign_cookies_solution.ipynb b/online_judges/assign_cookies/assign_cookies_solution.ipynb new file mode 100644 index 0000000..a303d47 --- /dev/null +++ b/online_judges/assign_cookies/assign_cookies_solution.ipynb @@ -0,0 +1,237 @@ +{ + "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: Assign Cookies.\n", + "\n", + "See the [LeetCode](https://leetcode.com/problems/assign-cookies/) problem page.\n", + "\n", + "Assume you are an awesome parent and want to give your children some cookies. But, you should give each child at most one cookie. Each child i has a greed factor gi, which is the minimum size of a cookie that the child will be content with; and each cookie j has a size sj. If sj >= gi, we can assign the cookie j to the child i, and the child i will be content. Your goal is to maximize the number of your content children and output the maximum number.\n", + "\n", + "Note:\n", + "You may assume the greed factor is always positive. \n", + "You cannot assign more than one cookie to one child.\n", + "\n", + "Example 1:\n", + "Input: [1,2,3], [1,1]\n", + "\n", + "Output: 1\n", + "\n", + "Explanation: You have 3 children and 2 cookies. The greed factors of 3 children are 1, 2, 3. \n", + "And even though you have 2 cookies, since their size is both 1, you could only make the child whose greed factor is 1 content.\n", + "You need to output 1.\n", + "Example 2:\n", + "Input: [1,2], [1,2,3]\n", + "\n", + "Output: 2\n", + "\n", + "Explanation: You have 2 children and 3 cookies. The greed factors of 2 children are 1, 2. \n", + "You have 3 cookies and their sizes are big enough to gratify all of the children, \n", + "You need to output 2.\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 the inputs two list(int), one for greed factor and the other for cookie size?\n", + " * Yes\n", + "* Are the inputs are sorted increasing order?\n", + " * No\n", + "* Can we change inputs themselves, or do we need to make a copy?\n", + " * You can change them\n", + "* Is the output an int?\n", + " * Yes\n", + "* Is the greed factor always >= 1?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * No, check for None\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "
\n",
+    "* None input -> TypeError\n",
+    "[1, 2, 3], [1, 1] -> 1\n",
+    "[1, 2], [1, 2, 3] -> 2\n",
+    "[7, 8, 9, 10], [5, 6, 7, 8] -> 2\n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "* Sort the inputs\n", + "* We'll keep an index to the current greed factor\n", + "* For each cookie\n", + " * Assign it to a child if its size >= the child's greed factor\n", + " * Increment result counter\n", + " * Increment the index to the greed factor\n", + " * Careful of this index going out of bounds\n", + "* Return the result counter\n", + "\n", + "Complexity:\n", + "* Time: O(n log n) for the sort\n", + "* Space: O(1), assuming the sort is in-place" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def find_content_children(self, greed_indices, cookie_sizes):\n", + " if greed_indices is None or cookie_sizes is None:\n", + " raise TypeError('greed_indices or cookie_sizes cannot be None')\n", + " if not greed_indices or not cookie_sizes:\n", + " return 0\n", + " greed_indices.sort()\n", + " cookie_sizes.sort()\n", + " greed_index = 0\n", + " num_children = 0\n", + " for size in cookie_sizes:\n", + " if greed_index >= len(greed_indices):\n", + " break\n", + " if size >= greed_indices[greed_index]:\n", + " num_children += 1\n", + " greed_index += 1\n", + " return num_children" + ] + }, + { + "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_assign_cookie.py\n" + ] + } + ], + "source": [ + "%%writefile test_assign_cookie.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestAssignCookie(object):\n", + "\n", + " def test_assign_cookie(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.find_content_children, None, None)\n", + " assert_equal(solution.find_content_children([1, 2, 3], \n", + " [1, 1]), 1)\n", + " assert_equal(solution.find_content_children([1, 2], \n", + " [1, 2, 3]), 2)\n", + " assert_equal(solution.find_content_children([7, 8, 9, 10], \n", + " [5, 6, 7, 8]), 2)\n", + " print('Success: test_find_content_children')\n", + "\n", + "\n", + "def main():\n", + " test = TestAssignCookie()\n", + " test.test_assign_cookie()\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_find_content_children\n" + ] + } + ], + "source": [ + "%run -i test_assign_cookie.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/online_judges/assign_cookies/test_assign_cookie.py b/online_judges/assign_cookies/test_assign_cookie.py new file mode 100644 index 0000000..625fd74 --- /dev/null +++ b/online_judges/assign_cookies/test_assign_cookie.py @@ -0,0 +1,24 @@ +from nose.tools import assert_equal, assert_raises + + +class TestAssignCookie(object): + + def test_assign_cookie(self): + solution = Solution() + assert_raises(TypeError, solution.find_content_children, None, None) + assert_equal(solution.find_content_children([1, 2, 3], + [1, 1]), 1) + assert_equal(solution.find_content_children([1, 2], + [1, 2, 3]), 2) + assert_equal(solution.find_content_children([7, 8, 9, 10], + [5, 6, 7, 8]), 2) + print('Success: test_find_content_children') + + +def main(): + test = TestAssignCookie() + test.test_assign_cookie() + + +if __name__ == '__main__': + main() \ No newline at end of file From b5eff084077dc876805d009656286c2fc7d6c4ec Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Wed, 29 Mar 2017 04:39:17 -0400 Subject: [PATCH 40/90] Add add digits challenge --- math_probability/add_digits/__init__.py | 0 .../add_digits/add_digits_challenge.ipynb | 176 ++++++++++++++ .../add_digits/add_digits_solution.ipynb | 224 ++++++++++++++++++ .../add_digits/test_add_digits.py | 29 +++ 4 files changed, 429 insertions(+) create mode 100644 math_probability/add_digits/__init__.py create mode 100644 math_probability/add_digits/add_digits_challenge.ipynb create mode 100644 math_probability/add_digits/add_digits_solution.ipynb create mode 100644 math_probability/add_digits/test_add_digits.py diff --git a/math_probability/add_digits/__init__.py b/math_probability/add_digits/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/math_probability/add_digits/add_digits_challenge.ipynb b/math_probability/add_digits/add_digits_challenge.ipynb new file mode 100644 index 0000000..faee03a --- /dev/null +++ b/math_probability/add_digits/add_digits_challenge.ipynb @@ -0,0 +1,176 @@ +{ + "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 an int, repeatedly add its digits until the result is one digit.\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 assume num is not negative?\n", + " * Yes\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",
+    "* None input -> TypeError\n",
+    "* negative input -> ValueError\n",
+    "* 9 -> 9\n",
+    "* 138 -> 3\n",
+    "* 65536 -> 7\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": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def add_digits(self, val):\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_add_digits.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestAddDigits(object):\n", + "\n", + " def test_add_digits(self, func):\n", + " assert_raises(TypeError, func, None)\n", + " assert_raises(ValueError, func, -1)\n", + " assert_equal(func(0), 0)\n", + " assert_equal(func(9), 9)\n", + " assert_equal(func(138), 3)\n", + " assert_equal(func(65536), 7) \n", + " print('Success: test_add_digits')\n", + "\n", + "\n", + "def main():\n", + " test = TestAddDigits()\n", + " solution = Solution()\n", + " test.test_add_digits(solution.add_digits)\n", + " try:\n", + " test.test_add_digits(solution.add_digits_optimized)\n", + " except NameError:\n", + " # Alternate solutions are only defined\n", + " # in the solutions file\n", + " pass\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/math_probability/add_digits/add_digits_solution.ipynb b/math_probability/add_digits/add_digits_solution.ipynb new file mode 100644 index 0000000..a0c7781 --- /dev/null +++ b/math_probability/add_digits/add_digits_solution.ipynb @@ -0,0 +1,224 @@ +{ + "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 an int, repeatedly add its digits until the result is one digit.\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 assume num is not negative?\n", + " * Yes\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",
+    "* None input -> TypeError\n",
+    "* negative input -> ValueError\n",
+    "* 9 -> 9\n",
+    "* 138 -> 3\n",
+    "* 65536 -> 7\n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "The naive solution simply isolates each digit with with modulo and integer division. We'll add each isolated digit to a list and sum the values.\n", + "\n", + "
\n",
+    "138 % 10 = 8 -> isolated\n",
+    "138 // 10 = 13\n",
+    "13 % 10 = 3 -> isolated\n",
+    "13 // 10 = 1\n",
+    "1 % 10 = 1 -> isolated\n",
+    "
\n", + "\n", + "A more optimal solution exists, by recognizing this is a digital root. See the [Wikipedia article](https://en.wikipedia.org/wiki/Digital_root) for more information.\n", + "\n", + "Complexity:\n", + "* Time: O(1)\n", + "* Space: O(1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def add_digits(self, num):\n", + " if num is None:\n", + " raise TypeError('num cannot be None')\n", + " if num < 0:\n", + " raise ValueError('num cannot be negative')\n", + " digits = []\n", + " while num != 0:\n", + " digits.append(num % 10)\n", + " num //= 10\n", + " digits_sum = sum(digits)\n", + " if digits_sum >= 10:\n", + " return self.add_digits(digits_sum)\n", + " else:\n", + " return digits_sum\n", + "\n", + " def add_digits_optimized(self, num):\n", + " if num is None:\n", + " raise TypeError('num cannot be None')\n", + " if num < 0:\n", + " raise ValueError('num cannot be negative')\n", + " if num == 0:\n", + " return 0\n", + " elif num % 9 == 0:\n", + " return 9\n", + " else:\n", + " return num % 9" + ] + }, + { + "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_add_digits.py\n" + ] + } + ], + "source": [ + "%%writefile test_add_digits.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestAddDigits(object):\n", + "\n", + " def test_add_digits(self, func):\n", + " assert_raises(TypeError, func, None)\n", + " assert_raises(ValueError, func, -1)\n", + " assert_equal(func(0), 0)\n", + " assert_equal(func(9), 9)\n", + " assert_equal(func(138), 3)\n", + " assert_equal(func(65536), 7) \n", + " print('Success: test_add_digits')\n", + "\n", + "\n", + "def main():\n", + " test = TestAddDigits()\n", + " solution = Solution()\n", + " test.test_add_digits(solution.add_digits)\n", + " try:\n", + " test.test_add_digits(solution.add_digits_optimized)\n", + " except NameError:\n", + " # Alternate solutions are only defined\n", + " # in the solutions file\n", + " pass\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_add_digits\n", + "Success: test_add_digits\n" + ] + } + ], + "source": [ + "%run -i test_add_digits.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/math_probability/add_digits/test_add_digits.py b/math_probability/add_digits/test_add_digits.py new file mode 100644 index 0000000..dd3d289 --- /dev/null +++ b/math_probability/add_digits/test_add_digits.py @@ -0,0 +1,29 @@ +from nose.tools import assert_equal, assert_raises + + +class TestAddDigits(object): + + def test_add_digits(self, func): + assert_raises(TypeError, func, None) + assert_raises(ValueError, func, -1) + assert_equal(func(0), 0) + assert_equal(func(9), 9) + assert_equal(func(138), 3) + assert_equal(func(65536), 7) + print('Success: test_add_digits') + + +def main(): + test = TestAddDigits() + solution = Solution() + test.test_add_digits(solution.add_digits) + try: + test.test_add_digits(solution.add_digits_optimized) + except NameError: + # Alternate solutions are only defined + # in the solutions file + pass + + +if __name__ == '__main__': + main() \ No newline at end of file From 93e1558d4390abd39abd08513605baa56c10df88 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Wed, 29 Mar 2017 04:40:49 -0400 Subject: [PATCH 41/90] Add sum two challenge --- math_probability/sum_two/__init__.py | 0 .../sum_two/sum_two_challenge.ipynb | 160 +++++++++++++ .../sum_two/sum_two_solution.ipynb | 221 ++++++++++++++++++ math_probability/sum_two/test_sum_two.py | 21 ++ 4 files changed, 402 insertions(+) create mode 100644 math_probability/sum_two/__init__.py create mode 100644 math_probability/sum_two/sum_two_challenge.ipynb create mode 100644 math_probability/sum_two/sum_two_solution.ipynb create mode 100644 math_probability/sum_two/test_sum_two.py diff --git a/math_probability/sum_two/__init__.py b/math_probability/sum_two/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/math_probability/sum_two/sum_two_challenge.ipynb b/math_probability/sum_two/sum_two_challenge.ipynb new file mode 100644 index 0000000..1346c49 --- /dev/null +++ b/math_probability/sum_two/sum_two_challenge.ipynb @@ -0,0 +1,160 @@ +{ + "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: Find the sum of two integers without using the + or - sign.\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 assume the inputs are valid?\n", + " * No, check for None\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "See the [LeetCode](https://leetcode.com/problems/sum-of-two-integers/) problem page." + ] + }, + { + "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 Solution(object):\n", + "\n", + " def sum_two(self, val):\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_sum_two.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSumTwo(object):\n", + "\n", + " def test_sum_two(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.sum_two, None)\n", + " assert_equal(solution.sum_two(5, 7), 12)\n", + " assert_equal(solution.sum_two(-5, -7), -12)\n", + " assert_equal(solution.sum_two(5, -7), -2)\n", + " print('Success: test_sum_two')\n", + "\n", + "\n", + "def main():\n", + " test = TestSumTwo()\n", + " test.test_sum_two()\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/math_probability/sum_two/sum_two_solution.ipynb b/math_probability/sum_two/sum_two_solution.ipynb new file mode 100644 index 0000000..ae989cd --- /dev/null +++ b/math_probability/sum_two/sum_two_solution.ipynb @@ -0,0 +1,221 @@ +{ + "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: Find the sum of two integers without using the + or - sign.\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 assume the inputs are valid?\n", + " * No, check for None\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "
\n",
+    "* None input -> TypeError\n",
+    "* 5, 7 -> 12\n",
+    "* -5, -7 -> -12\n",
+    "* 5, -7 -> -2\n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "We'll look at the following example, adding a and b:\n", + "\n", + "
\n",
+    "a 0111 \n",
+    "b 0101\n",
+    "
\n", + "\n", + "First, add a and b, without worrying about the carry (0+0=0, 0+1=1, 1+1=0):\n", + "\n", + "result = a ^ b = 0010\n", + "\n", + "Next, calculate the carry (1+1=2). We'll need to left shift one to prepare for the next iteration when we move to the next most significant bit:\n", + "\n", + "carry = (a&b) << 1 = 1010\n", + "\n", + "If the carry is not zero, we'll need to add the carry to the result. Recusively call the function, passing in result and carry.\n", + "\n", + "Below are the values of a, b, and the carry of a = 7 and b = 5, producing the result of 12.\n", + "\n", + "
\n",
+    "a 0111 \n",
+    "b 0101 \n",
+    "----- \n",
+    "c 0101 \n",
+    "a 0010 \n",
+    "b 1010 \n",
+    "----- \n",
+    "c 0010 \n",
+    "a 1000 \n",
+    "b 0100 \n",
+    "----- \n",
+    "c 0000 \n",
+    "a 1100 \n",
+    "b 0000\n",
+    "\n",
+    "c = carry = 0, return the result 1100\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(b), where b is the number of bits\n", + "* Space: O(b), where b is the number of bits" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def sum_two(self, a, b):\n", + " if a is None or b is None:\n", + " raise TypeError('a or b cannot be None')\n", + " result = a ^ b;\n", + " carry = (a&b) << 1\n", + " if carry != 0:\n", + " return self.sum_two(result, carry)\n", + " return result;" + ] + }, + { + "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_sum_two.py\n" + ] + } + ], + "source": [ + "%%writefile test_sum_two.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSumTwo(object):\n", + "\n", + " def test_sum_two(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.sum_two, None)\n", + " assert_equal(solution.sum_two(5, 7), 12)\n", + " assert_equal(solution.sum_two(-5, -7), -12)\n", + " assert_equal(solution.sum_two(5, -7), -2)\n", + " print('Success: test_sum_two')\n", + "\n", + "\n", + "def main():\n", + " test = TestSumTwo()\n", + " test.test_sum_two()\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " main()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false, + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Success: test_sum_two\n" + ] + } + ], + "source": [ + "%run -i test_sum_two.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/math_probability/sum_two/test_sum_two.py b/math_probability/sum_two/test_sum_two.py new file mode 100644 index 0000000..82a5ada --- /dev/null +++ b/math_probability/sum_two/test_sum_two.py @@ -0,0 +1,21 @@ +from nose.tools import assert_equal, assert_raises + + +class TestSumTwo(object): + + def test_sum_two(self): + solution = Solution() + assert_raises(TypeError, solution.sum_two, None) + assert_equal(solution.sum_two(5, 7), 12) + assert_equal(solution.sum_two(-5, -7), -12) + assert_equal(solution.sum_two(5, -7), -2) + print('Success: test_sum_two') + + +def main(): + test = TestSumTwo() + test.test_sum_two() + + +if __name__ == '__main__': + main() \ No newline at end of file From fed90d0fd86a02dc41e69c1807c229bce85dd4aa Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Wed, 29 Mar 2017 04:41:24 -0400 Subject: [PATCH 42/90] Add sub two challenge --- math_probability/sub_two/__init__.py | 0 .../sub_two/sub_two_challenge.ipynb | 167 ++++++++++++++ .../sub_two/sub_two_solution.ipynb | 206 ++++++++++++++++++ math_probability/sub_two/test_sub_two.py | 22 ++ 4 files changed, 395 insertions(+) create mode 100644 math_probability/sub_two/__init__.py create mode 100644 math_probability/sub_two/sub_two_challenge.ipynb create mode 100644 math_probability/sub_two/sub_two_solution.ipynb create mode 100644 math_probability/sub_two/test_sub_two.py diff --git a/math_probability/sub_two/__init__.py b/math_probability/sub_two/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/math_probability/sub_two/sub_two_challenge.ipynb b/math_probability/sub_two/sub_two_challenge.ipynb new file mode 100644 index 0000000..704e278 --- /dev/null +++ b/math_probability/sub_two/sub_two_challenge.ipynb @@ -0,0 +1,167 @@ +{ + "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: Find the difference of two integers without using the + or - sign.\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 assume the inputs are valid?\n", + " * No, check for None\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "
\n",
+    "* None input -> TypeError\n",
+    "* 7, 5 -> 2\n",
+    "* -5, -7 -> 2\n",
+    "* -5, 7 -> -12\n",
+    "* 5, -7 -> 12\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": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def sub_two(self, val):\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_sub_two.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSubTwo(object):\n", + "\n", + " def test_sub_two(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.sub_two, None)\n", + " assert_equal(solution.sub_two(7, 5), 2)\n", + " assert_equal(solution.sub_two(-5, -7), 2)\n", + " assert_equal(solution.sub_two(-5, 7), -12)\n", + " assert_equal(solution.sub_two(5, -7), 12)\n", + " print('Success: test_sub_two')\n", + "\n", + "\n", + "def main():\n", + " test = TestSubTwo()\n", + " test.test_sub_two()\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/math_probability/sub_two/sub_two_solution.ipynb b/math_probability/sub_two/sub_two_solution.ipynb new file mode 100644 index 0000000..4593615 --- /dev/null +++ b/math_probability/sub_two/sub_two_solution.ipynb @@ -0,0 +1,206 @@ +{ + "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: Find the difference of two integers without using the + or - sign.\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 assume the inputs are valid?\n", + " * No, check for None\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "
\n",
+    "* None input -> TypeError\n",
+    "* 7, 5 -> 2\n",
+    "* -5, -7 -> 2\n",
+    "* -5, 7 -> -12\n",
+    "* 5, -7 -> 12\n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "We'll look at the following example, subtracting a and b:\n", + "\n", + "
\n",
+    "a 0110 = 6 \n",
+    "b 0101 = 5\n",
+    "
\n", + "\n", + "First, subtract a and b, without worrying about the borrow (0-0=0, 0-1=1, 1-1=0):\n", + "\n", + "result = a ^ b = 0011\n", + "\n", + "Next, calculate the borrow (0-1=1). We'll need to left shift one to prepare for the next iteration when we move to the next most significant bit:\n", + "\n", + "~a = 1001\n", + " b = 0101\n", + "~a & b = 0001\n", + "\n", + "borrow = (~a&b) << 1 = 0010\n", + "\n", + "If the borrow is not zero, we'll need to subtract the borrow from the result. Recusively call the function, passing in result and borrow.\n", + "\n", + "Complexity:\n", + "* Time: O(b), where b is the number of bits\n", + "* Space: O(b), where b is the number of bits" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def sub_two(self, a, b):\n", + " if a is None or b is None:\n", + " raise TypeError('a or b cannot be None')\n", + " result = a ^ b;\n", + " borrow = (~a & b) << 1\n", + " if borrow != 0:\n", + " return self.sub_two(result, borrow)\n", + " return result;" + ] + }, + { + "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_sub_two.py\n" + ] + } + ], + "source": [ + "%%writefile test_sub_two.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSubTwo(object):\n", + "\n", + " def test_sub_two(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.sub_two, None)\n", + " assert_equal(solution.sub_two(7, 5), 2)\n", + " assert_equal(solution.sub_two(-5, -7), 2)\n", + " assert_equal(solution.sub_two(-5, 7), -12)\n", + " assert_equal(solution.sub_two(5, -7), 12)\n", + " print('Success: test_sub_two')\n", + "\n", + "\n", + "def main():\n", + " test = TestSubTwo()\n", + " test.test_sub_two()\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " main()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false, + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Success: test_sub_two\n" + ] + } + ], + "source": [ + "%run -i test_sub_two.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/math_probability/sub_two/test_sub_two.py b/math_probability/sub_two/test_sub_two.py new file mode 100644 index 0000000..f022a3f --- /dev/null +++ b/math_probability/sub_two/test_sub_two.py @@ -0,0 +1,22 @@ +from nose.tools import assert_equal, assert_raises + + +class TestSubTwo(object): + + def test_sub_two(self): + solution = Solution() + assert_raises(TypeError, solution.sub_two, None) + assert_equal(solution.sub_two(7, 5), 2) + assert_equal(solution.sub_two(-5, -7), 2) + assert_equal(solution.sub_two(-5, 7), -12) + assert_equal(solution.sub_two(5, -7), 12) + print('Success: test_sub_two') + + +def main(): + test = TestSubTwo() + test.test_sub_two() + + +if __name__ == '__main__': + main() \ No newline at end of file From 3395619d6ed05ae72ba3f576c95eb2e85ba6414d Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Wed, 29 Mar 2017 04:41:58 -0400 Subject: [PATCH 43/90] Add power two challenge --- math_probability/power_two/__init__.py | 0 .../power_two/power_two_challenge.ipynb | 171 +++++++++++++++ .../power_two/power_two_solution.ipynb | 206 ++++++++++++++++++ .../power_two/test_is_power_of_two.py | 23 ++ 4 files changed, 400 insertions(+) create mode 100644 math_probability/power_two/__init__.py create mode 100644 math_probability/power_two/power_two_challenge.ipynb create mode 100644 math_probability/power_two/power_two_solution.ipynb create mode 100644 math_probability/power_two/test_is_power_of_two.py diff --git a/math_probability/power_two/__init__.py b/math_probability/power_two/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/math_probability/power_two/power_two_challenge.ipynb b/math_probability/power_two/power_two_challenge.ipynb new file mode 100644 index 0000000..4815a68 --- /dev/null +++ b/math_probability/power_two/power_two_challenge.ipynb @@ -0,0 +1,171 @@ +{ + "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: Determine if a number is a power of two.\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", + "* Is the input number an int?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * No\n", + "* Is the output a boolean?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> TypeError\n", + "* 0 -> False\n", + "* 1 -> True\n", + "* 2 -> True\n", + "* 15 -> False\n", + "* 16 -> True" + ] + }, + { + "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 Solution(object):\n", + "\n", + " def is_power_of_two(self, val):\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_is_power_of_two.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSolution(object):\n", + "\n", + " def test_is_power_of_two(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.is_power_of_two, None)\n", + " assert_equal(solution.is_power_of_two(0), False)\n", + " assert_equal(solution.is_power_of_two(1), True)\n", + " assert_equal(solution.is_power_of_two(2), True)\n", + " assert_equal(solution.is_power_of_two(15), False)\n", + " assert_equal(solution.is_power_of_two(16), True)\n", + " print('Success: test_is_power_of_two')\n", + "\n", + "\n", + "def main():\n", + " test = TestSolution()\n", + " test.test_is_power_of_two()\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/math_probability/power_two/power_two_solution.ipynb b/math_probability/power_two/power_two_solution.ipynb new file mode 100644 index 0000000..9e67bf4 --- /dev/null +++ b/math_probability/power_two/power_two_solution.ipynb @@ -0,0 +1,206 @@ +{ + "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: Determine if a number is a power of two.\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", + "* Is the input number an int?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * No\n", + "* Is the output a boolean?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> TypeError\n", + "* 0 -> False\n", + "* 1 -> True\n", + "* 2 -> True\n", + "* 15 -> False\n", + "* 16 -> True" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "We can use bit manipulation to determine if a number is a power of two. \n", + "\n", + "For a number to be a power of two, there must only be one bit that is a `1`. \n", + "\n", + "We can use the following bit manipulation trick to determine this:\n", + "\n", + "`n & (n - 1)`\n", + "\n", + "Here's an example why:\n", + "\n", + "
\n",
+    "0000 1000 = n\n",
+    "0000 0001 = 1\n",
+    "0000 0111 = n-1\n",
+    "\n",
+    "0000 1000 = n\n",
+    "0000 0111 = n-1\n",
+    "0000 0000 = n & n-1, result = 0\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(1)\n", + "* Space: O(1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def is_power_of_two(self, n):\n", + " if n is None:\n", + " raise TypeError('n cannot be None')\n", + " if n <= 0:\n", + " return False\n", + " return (n & (n - 1)) == 0" + ] + }, + { + "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_is_power_of_two.py\n" + ] + } + ], + "source": [ + "%%writefile test_is_power_of_two.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestSolution(object):\n", + "\n", + " def test_is_power_of_two(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.is_power_of_two, None)\n", + " assert_equal(solution.is_power_of_two(0), False)\n", + " assert_equal(solution.is_power_of_two(1), True)\n", + " assert_equal(solution.is_power_of_two(2), True)\n", + " assert_equal(solution.is_power_of_two(15), False)\n", + " assert_equal(solution.is_power_of_two(16), True)\n", + " print('Success: test_is_power_of_two')\n", + "\n", + "\n", + "def main():\n", + " test = TestSolution()\n", + " test.test_is_power_of_two()\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_is_power_of_two\n" + ] + } + ], + "source": [ + "%run -i test_is_power_of_two.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/math_probability/power_two/test_is_power_of_two.py b/math_probability/power_two/test_is_power_of_two.py new file mode 100644 index 0000000..bdc3ab2 --- /dev/null +++ b/math_probability/power_two/test_is_power_of_two.py @@ -0,0 +1,23 @@ +from nose.tools import assert_equal, assert_raises + + +class TestSolution(object): + + def test_is_power_of_two(self): + solution = Solution() + assert_raises(TypeError, solution.is_power_of_two, None) + assert_equal(solution.is_power_of_two(0), False) + assert_equal(solution.is_power_of_two(1), True) + assert_equal(solution.is_power_of_two(2), True) + assert_equal(solution.is_power_of_two(15), False) + assert_equal(solution.is_power_of_two(16), True) + print('Success: test_is_power_of_two') + + +def main(): + test = TestSolution() + test.test_is_power_of_two() + + +if __name__ == '__main__': + main() \ No newline at end of file From 260ad2edc225ca20f6ed09b1e16bc09fce8be17d Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Wed, 29 Mar 2017 04:44:38 -0400 Subject: [PATCH 44/90] Add math ops challenge --- math_probability/math_ops/__init__.py | 0 .../math_ops/math_ops_challenge.ipynb | 190 ++++++++++++++ .../math_ops/math_ops_solution.ipynb | 236 ++++++++++++++++++ math_probability/math_ops/test_math_ops.py | 33 +++ 4 files changed, 459 insertions(+) create mode 100644 math_probability/math_ops/__init__.py create mode 100644 math_probability/math_ops/math_ops_challenge.ipynb create mode 100644 math_probability/math_ops/math_ops_solution.ipynb create mode 100644 math_probability/math_ops/test_math_ops.py diff --git a/math_probability/math_ops/__init__.py b/math_probability/math_ops/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/math_probability/math_ops/math_ops_challenge.ipynb b/math_probability/math_ops/math_ops_challenge.ipynb new file mode 100644 index 0000000..bcd19d4 --- /dev/null +++ b/math_probability/math_ops/math_ops_challenge.ipynb @@ -0,0 +1,190 @@ +{ + "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: Create a class with an insert method to insert an int to a list. It should also support calculating the max, min, mean, and mode in O(1).\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 assume the inputs are valid?\n", + " * No\n", + "* Is there a range of inputs?\n", + " * 0 <= item <= 100\n", + "* Should mean return a float?\n", + " * Yes\n", + "* Should the other results return an int?\n", + " * Yes\n", + "* If there are multiple modes, what do we return?\n", + " * Any of the modes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> TypeError\n", + "* [] -> ValueError\n", + "* [5, 2, 7, 9, 9, 2, 9, 4, 3, 3, 2]\n", + " * max: 9\n", + " * min: 2\n", + " * mean: 55\n", + " * mode: 9 or 2" + ] + }, + { + "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 Solution(object):\n", + "\n", + " def __init__(self, upper_limit=100):\n", + " # TODO: Implement me\n", + " pass\n", + "\n", + " def insert(self, val):\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_math_ops.py\n", + "from nose.tools import assert_equal, assert_true, assert_raises\n", + "\n", + "\n", + "class TestMathOps(object):\n", + "\n", + " def test_math_ops(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.insert, None)\n", + " solution.insert(5)\n", + " solution.insert(2)\n", + " solution.insert(7)\n", + " solution.insert(9)\n", + " solution.insert(9)\n", + " solution.insert(2)\n", + " solution.insert(9)\n", + " solution.insert(4)\n", + " solution.insert(3)\n", + " solution.insert(3)\n", + " solution.insert(2)\n", + " assert_equal(solution.max, 9)\n", + " assert_equal(solution.min, 2)\n", + " assert_equal(solution.mean, 5)\n", + " assert_true(solution.mode in (2, 92))\n", + " print('Success: test_math_ops')\n", + "\n", + "\n", + "def main():\n", + " test = TestMathOps()\n", + " test.test_math_ops()\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/math_probability/math_ops/math_ops_solution.ipynb b/math_probability/math_ops/math_ops_solution.ipynb new file mode 100644 index 0000000..8569371 --- /dev/null +++ b/math_probability/math_ops/math_ops_solution.ipynb @@ -0,0 +1,236 @@ +{ + "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: Create a class with an insert method to insert an int to a list. It should also support calculating the max, min, mean, and mode in O(1).\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 assume the inputs are valid?\n", + " * No\n", + "* Is there a range of inputs?\n", + " * 0 <= item <= 100\n", + "* Should mean return a float?\n", + " * Yes\n", + "* Should the other results return an int?\n", + " * Yes\n", + "* If there are multiple modes, what do we return?\n", + " * Any of the modes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> TypeError\n", + "* [] -> ValueError\n", + "* [5, 2, 7, 9, 9, 2, 9, 4, 3, 3, 2]\n", + " * max: 9\n", + " * min: 2\n", + " * mean: 55\n", + " * mode: 9 or 2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "* We'll init our max and min to None. Alternatively, we can init them to -sys.maxsize and sys.maxsize, respectively.\n", + "* For mean, we'll keep track of the number of items we have inserted so far, as well as the running sum.\n", + "* For mode, we'll keep track of the current mode and an array with the size of the given upper limit\n", + " * Each element in the array will be init to 0\n", + " * Each time we insert, we'll increment the element corresponding to the inserted item's value\n", + "* On each insert:\n", + " * Update the max and min\n", + " * Update the mean by calculating running_sum / num_items\n", + " * Update the mode by comparing the mode array's value with the current mode\n", + "\n", + "Complexity:\n", + "* Time: O(1)\n", + "* Space: O(1), we are treating the 101 element array as a constant O(1), we could also see this as O(k)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from __future__ import division\n", + "\n", + "\n", + "class Solution(object):\n", + "\n", + " def __init__(self, upper_limit=100):\n", + " self.max = None\n", + " self.min = None\n", + " # Mean\n", + " self.num_items = 0\n", + " self.running_sum = 0\n", + " self.mean = None\n", + " # Mode\n", + " self.array = [0] * (upper_limit + 1)\n", + " self.mode_ocurrences = 0\n", + " self.mode = None\n", + "\n", + " def insert(self, val):\n", + " if val is None:\n", + " raise TypeError('val cannot be None')\n", + " if self.max is None or val > self.max:\n", + " self.max = val\n", + " if self.min is None or val < self.min:\n", + " self.min = val\n", + " # Calculate the mean\n", + " self.num_items += 1\n", + " self.running_sum += val\n", + " self.mean = self.running_sum / self.num_items\n", + " # Calculate the mode\n", + " self.array[val] += 1\n", + " if self.array[val] > self.mode_ocurrences:\n", + " self.mode_ocurrences = self.array[val]\n", + " self.mode = val" + ] + }, + { + "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_math_ops.py\n" + ] + } + ], + "source": [ + "%%writefile test_math_ops.py\n", + "from nose.tools import assert_equal, assert_true, assert_raises\n", + "\n", + "\n", + "class TestMathOps(object):\n", + "\n", + " def test_math_ops(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.insert, None)\n", + " solution.insert(5)\n", + " solution.insert(2)\n", + " solution.insert(7)\n", + " solution.insert(9)\n", + " solution.insert(9)\n", + " solution.insert(2)\n", + " solution.insert(9)\n", + " solution.insert(4)\n", + " solution.insert(3)\n", + " solution.insert(3)\n", + " solution.insert(2)\n", + " assert_equal(solution.max, 9)\n", + " assert_equal(solution.min, 2)\n", + " assert_equal(solution.mean, 5)\n", + " assert_true(solution.mode in (2, 9))\n", + " print('Success: test_math_ops')\n", + "\n", + "\n", + "def main():\n", + " test = TestMathOps()\n", + " test.test_math_ops()\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_math_ops\n" + ] + } + ], + "source": [ + "%run -i test_math_ops.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/math_probability/math_ops/test_math_ops.py b/math_probability/math_ops/test_math_ops.py new file mode 100644 index 0000000..1e1c18a --- /dev/null +++ b/math_probability/math_ops/test_math_ops.py @@ -0,0 +1,33 @@ +from nose.tools import assert_equal, assert_true, assert_raises + + +class TestMathOps(object): + + def test_math_ops(self): + solution = Solution() + assert_raises(TypeError, solution.insert, None) + solution.insert(5) + solution.insert(2) + solution.insert(7) + solution.insert(9) + solution.insert(9) + solution.insert(2) + solution.insert(9) + solution.insert(4) + solution.insert(3) + solution.insert(3) + solution.insert(2) + assert_equal(solution.max, 9) + assert_equal(solution.min, 2) + assert_equal(solution.mean, 5) + assert_true(solution.mode in (2, 9)) + print('Success: test_math_ops') + + +def main(): + test = TestMathOps() + test.test_math_ops() + + +if __name__ == '__main__': + main() \ No newline at end of file From 4f93526ae5f63382794f82a16f68c4ca0ca3e686 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Wed, 29 Mar 2017 04:45:04 -0400 Subject: [PATCH 45/90] Add generate primes challenge --- math_probability/generate_primes/__init__.py | 0 .../check_prime_challenge.ipynb | 169 ++++++++++++++ .../check_prime_solution.ipynb | 220 ++++++++++++++++++ .../generate_primes/test_generate_primes.py | 26 +++ 4 files changed, 415 insertions(+) create mode 100644 math_probability/generate_primes/__init__.py create mode 100644 math_probability/generate_primes/check_prime_challenge.ipynb create mode 100644 math_probability/generate_primes/check_prime_solution.ipynb create mode 100644 math_probability/generate_primes/test_generate_primes.py diff --git a/math_probability/generate_primes/__init__.py b/math_probability/generate_primes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/math_probability/generate_primes/check_prime_challenge.ipynb b/math_probability/generate_primes/check_prime_challenge.ipynb new file mode 100644 index 0000000..e2b748a --- /dev/null +++ b/math_probability/generate_primes/check_prime_challenge.ipynb @@ -0,0 +1,169 @@ +{ + "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: Generate a list of primes.\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", + "* Is it correct that 1 is not considered a prime number?\n", + " * Yes\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 -> Exception\n", + "* Not an int -> Exception\n", + "* 20 -> 2, 3, 5, 7, 11, 13, 17, 19" + ] + }, + { + "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": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class PrimeGenerator(object):\n", + "\n", + " def generate_primes(self, max_num):\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_generate_primes.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestMath(object):\n", + "\n", + " def test_generate_primes(self):\n", + " prime_generator = PrimeGenerator()\n", + " assert_raises(TypeError, prime_generator.generate_primes, None)\n", + " assert_raises(TypeError, prime_generator.generate_primes, 98.6)\n", + " assert_equal(prime_generator.generate_primes(20), [False, False, True, \n", + " True, False, True, \n", + " False, True, False, \n", + " False, False, True, \n", + " False, True, False, \n", + " False, False, True, \n", + " False, True])\n", + " print('Success: generate_primes')\n", + "\n", + "\n", + "def main():\n", + " test = TestMath()\n", + " test.test_generate_primes()\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/math_probability/generate_primes/check_prime_solution.ipynb b/math_probability/generate_primes/check_prime_solution.ipynb new file mode 100644 index 0000000..6f7a58b --- /dev/null +++ b/math_probability/generate_primes/check_prime_solution.ipynb @@ -0,0 +1,220 @@ +{ + "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: Generate a list of primes.\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", + "* Is it correct that 1 is not considered a prime number?\n", + " * Yes\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 -> Exception\n", + "* Not an int -> Exception\n", + "* 20 -> 2, 3, 5, 7, 11, 13, 17, 19" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "For a number to be prime, it must be 2 or greater and cannot be divisible by another number other than itself (and 1).\n", + "\n", + "We'll use the Sieve of Eratosthenes. All non-prime numbers are divisible by a prime number.\n", + "\n", + "* Use an array (or bit array, bit vector) to keep track of each integer up to the max\n", + "* Start at 2, end at sqrt(max)\n", + " * We can use sqrt(max) instead of max because:\n", + " * For each value that divides the input number evenly, there is a complement b where a * b = n\n", + " * If a > sqrt(n) then b < sqrt(n) because sqrt(n^2) = n\n", + " * \"Cross off\" all numbers divisible by 2, 3, 5, 7, ... by setting array[index] to False\n", + "\n", + "Complexity:\n", + "* Time: O(n log log n)\n", + "* Space: O(n)\n", + "\n", + "Wikipedia's animation:\n", + "\n", + "![alt text](https://upload.wikimedia.org/wikipedia/commons/b/b9/Sieve_of_Eratosthenes_animation.gif)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import math\n", + "\n", + "\n", + "class PrimeGenerator(object):\n", + "\n", + " def generate_primes(self, max_num):\n", + " if max_num is None:\n", + " raise TypeError('max_num cannot be None')\n", + " array = [True] * max_num\n", + " array[0] = False\n", + " array[1] = False\n", + " prime = 2\n", + " while prime <= math.sqrt(max_num):\n", + " self._cross_off(array, prime)\n", + " prime = self._next_prime(array, prime)\n", + " return array\n", + "\n", + " def _cross_off(self, array, prime):\n", + " for index in range(prime*prime, len(array), prime):\n", + " # Start with prime*prime because if we have a k*prime\n", + " # where k < prime, this value would have already been\n", + " # previously crossed off\n", + " array[index] = False\n", + "\n", + " def _next_prime(self, array, prime):\n", + " next = prime + 1\n", + " while next < len(array) and not array[next]:\n", + " next += 1\n", + " return next" + ] + }, + { + "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_generate_primes.py\n" + ] + } + ], + "source": [ + "%%writefile test_generate_primes.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestMath(object):\n", + "\n", + " def test_generate_primes(self):\n", + " prime_generator = PrimeGenerator()\n", + " assert_raises(TypeError, prime_generator.generate_primes, None)\n", + " assert_raises(TypeError, prime_generator.generate_primes, 98.6)\n", + " assert_equal(prime_generator.generate_primes(20), [False, False, True, \n", + " True, False, True, \n", + " False, True, False, \n", + " False, False, True, \n", + " False, True, False, \n", + " False, False, True, \n", + " False, True])\n", + " print('Success: generate_primes')\n", + "\n", + "\n", + "def main():\n", + " test = TestMath()\n", + " test.test_generate_primes()\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: generate_primes\n" + ] + } + ], + "source": [ + "%run -i test_generate_primes.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/math_probability/generate_primes/test_generate_primes.py b/math_probability/generate_primes/test_generate_primes.py new file mode 100644 index 0000000..85ed132 --- /dev/null +++ b/math_probability/generate_primes/test_generate_primes.py @@ -0,0 +1,26 @@ +from nose.tools import assert_equal, assert_raises + + +class TestMath(object): + + def test_generate_primes(self): + prime_generator = PrimeGenerator() + assert_raises(TypeError, prime_generator.generate_primes, None) + assert_raises(TypeError, prime_generator.generate_primes, 98.6) + assert_equal(prime_generator.generate_primes(20), [False, False, True, + True, False, True, + False, True, False, + False, False, True, + False, True, False, + False, False, True, + False, True]) + print('Success: generate_primes') + + +def main(): + test = TestMath() + test.test_generate_primes() + + +if __name__ == '__main__': + main() \ No newline at end of file From 4216c91afc8be96cd88f386e75b137e3684460e9 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Thu, 30 Mar 2017 05:38:16 -0400 Subject: [PATCH 46/90] Add trie challenge --- graphs_trees/trie/__init__.py | 0 graphs_trees/trie/test_trie.py | 74 +++++ graphs_trees/trie/trie.py | 76 +++++ graphs_trees/trie/trie_challenge.ipynb | 279 +++++++++++++++++ graphs_trees/trie/trie_solution.ipynb | 418 +++++++++++++++++++++++++ 5 files changed, 847 insertions(+) create mode 100644 graphs_trees/trie/__init__.py create mode 100644 graphs_trees/trie/test_trie.py create mode 100644 graphs_trees/trie/trie.py create mode 100644 graphs_trees/trie/trie_challenge.ipynb create mode 100644 graphs_trees/trie/trie_solution.ipynb diff --git a/graphs_trees/trie/__init__.py b/graphs_trees/trie/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/graphs_trees/trie/test_trie.py b/graphs_trees/trie/test_trie.py new file mode 100644 index 0000000..a0c1bdf --- /dev/null +++ b/graphs_trees/trie/test_trie.py @@ -0,0 +1,74 @@ +from nose.tools import assert_true +from nose.tools import raises + + +class TestTrie(object): + + def test_trie(self): + trie = Trie() + + print('Test: Insert') + words = ['a', 'at', 'has', 'hat', 'he', + 'me', 'men', 'mens', 'met'] + for word in words: + trie.insert(word) + for word in trie.list_words(): + assert_true(trie.find(word) is not None) + + print('Test: Remove me') + trie.remove('me') + words_removed = ['me'] + words = ['a', 'at', 'has', 'hat', 'he', + 'men', 'mens', 'met'] + for word in words: + assert_true(trie.find(word) is not None) + for word in words_removed: + assert_true(trie.find(word) is None) + + print('Test: Remove mens') + trie.remove('mens') + words_removed = ['me', 'mens'] + words = ['a', 'at', 'has', 'hat', 'he', + 'men', 'met'] + for word in words: + assert_true(trie.find(word) is not None) + for word in words_removed: + assert_true(trie.find(word) is None) + + print('Test: Remove a') + trie.remove('a') + words_removed = ['a', 'me', 'mens'] + words = ['at', 'has', 'hat', 'he', + 'men', 'met'] + for word in words: + assert_true(trie.find(word) is not None) + for word in words_removed: + assert_true(trie.find(word) is None) + + print('Test: Remove has') + trie.remove('has') + words_removed = ['a', 'has', 'me', 'mens'] + words = ['at', 'hat', 'he', + 'men', 'met'] + for word in words: + assert_true(trie.find(word) is not None) + for word in words_removed: + assert_true(trie.find(word) is None) + + print('Success: test_trie') + + @raises(Exception) + def test_trie_remove_invalid(self): + print('Test: Remove from empty trie') + trie = Trie() + assert_true(trie.remove('foo') is None) + + +def main(): + test = TestTrie() + test.test_trie() + test.test_trie_remove_invalid() + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/graphs_trees/trie/trie.py b/graphs_trees/trie/trie.py new file mode 100644 index 0000000..7cfe0fa --- /dev/null +++ b/graphs_trees/trie/trie.py @@ -0,0 +1,76 @@ +from collections import OrderedDict + + +class Node(object): + + def __init__(self, key, parent=None, terminates=False): + self.key = key + self.terminates = False + self.parent = parent + self.children = {} + + +class Trie(object): + + def __init__(self): + self.root = Node('') + + def find(self, word): + if word is None: + raise TypeError('word cannot be None') + node = self.root + for char in word: + if char in node.children: + node = node.children[char] + else: + return None + return node if node.terminates else None + + def insert(self, word): + if word is None: + raise TypeError('word cannot be None') + node = self.root + parent = None + for char in word: + if char in node.children: + node = node.children[char] + else: + node.children[char] = Node(char, parent=node) + node = node.children[char] + node.terminates = True + + def remove(self, word): + if word is None: + raise TypeError('word cannot be None') + node = self.find(word) + if node is None: + raise KeyError('word does not exist') + node.terminates = False + parent = node.parent + while parent is not None: + # As we are propagating the delete up the + # parents, if this node has children, stop + # here to prevent orphaning its children. + # Or + # if this node is a terminating node that is + # not the terminating node of the input word, + # stop to prevent removing the associated word. + if node.children or node.terminates: + return + del parent.children[node.key] + node = parent + parent = parent.parent + + def list_words(self): + result = [] + curr_word = '' + self._list_words(self.root, curr_word, result) + return result + + def _list_words(self, node, curr_word, result): + if node is None: + return + for key, child in node.children.items(): + if child.terminates: + result.append(curr_word+key) + self._list_words(child, curr_word+key, result) \ No newline at end of file diff --git a/graphs_trees/trie/trie_challenge.ipynb b/graphs_trees/trie/trie_challenge.ipynb new file mode 100644 index 0000000..8889146 --- /dev/null +++ b/graphs_trees/trie/trie_challenge.ipynb @@ -0,0 +1,279 @@ +{ + "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 a trie with find, insert, remove, and list_words methods.\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 assume we are working with strings?\n", + " * Yes\n", + "* Are the strings in ASCII?\n", + " * Yes\n", + "* Should `find` only match exact words with a terminating character?\n", + " * Yes\n", + "* Should `list_words` only return words with a terminating character?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "
\n",
+    "\n",
+    "         root\n",
+    "       /  |  \\\n",
+    "      h   a*  m\n",
+    "     / \\   \\   \\\n",
+    "    a   e*  t*  e*\n",
+    "   / \\         / \\\n",
+    "  s*  t*      n*  t*\n",
+    "             /\n",
+    "            s*\n",
+    "\n",
+    "find\n",
+    "\n",
+    "* Find on an empty trie\n",
+    "* Find non-matching\n",
+    "* Find matching\n",
+    "\n",
+    "insert\n",
+    "\n",
+    "* Insert on empty trie\n",
+    "* Insert to make a leaf terminator char\n",
+    "* Insert to extend an existing terminator char\n",
+    "\n",
+    "remove\n",
+    "\n",
+    "* Remove me\n",
+    "* Remove mens\n",
+    "* Remove a\n",
+    "* Remove has\n",
+    "\n",
+    "list_words\n",
+    "\n",
+    "* List empty\n",
+    "* List general case\n",
+    "
\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "Refer to the [Solution Notebook](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/trie/trie_solution.ipynb). 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": [ + "from collections import OrderedDict\n", + "\n", + "\n", + "class Node(object):\n", + "\n", + " def __init__(self, data, parent=None, terminates=False):\n", + " # TODO: Implement me\n", + " pass\n", + "\n", + "\n", + "class Trie(object):\n", + "\n", + " def __init__(self):\n", + " # TODO: Implement me\n", + " pass\n", + "\n", + " def find(self, word):\n", + " # TODO: Implement me\n", + " pass\n", + "\n", + " def insert(self, word):\n", + " # TODO: Implement me\n", + " pass\n", + "\n", + " def remove(self, word):\n", + " # TODO: Implement me\n", + " pass\n", + "\n", + " def list_words(self):\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_trie.py\n", + "from nose.tools import assert_true\n", + "\n", + "\n", + "class TestTrie(object):\n", + "\n", + " def test_trie(self):\n", + " print('Test: Remove from empty trie')\n", + " trie = Trie()\n", + " assert_true(trie.remove('foo') is None)\n", + "\n", + " print('Test: Insert')\n", + " words = ['a', 'at', 'has', 'hat', 'he',\n", + " 'me', 'men', 'mens', 'met']\n", + " for word in words:\n", + " trie.insert(word)\n", + " for word in trie.list_words():\n", + " assert_true(trie.find(word) is not None)\n", + "\n", + " # Remove me\n", + " # Remove mens\n", + " # Remove a\n", + " \n", + " print('Test: Remove me')\n", + " trie.remove('me')\n", + " words_removed = ['me']\n", + " words = ['a', 'at', 'has', 'hat', 'he',\n", + " 'men', 'mens', 'met']\n", + " for word in words:\n", + " assert_true(trie.find(word) is not None)\n", + " for word in words_removed:\n", + " assert_true(trie.find(word) is None)\n", + "\n", + " print('Test: Remove mens')\n", + " trie.remove('mens')\n", + " words_removed = ['me', 'mens']\n", + " words = ['a', 'at', 'has', 'hat', 'he',\n", + " 'men', 'met']\n", + " for word in words:\n", + " assert_true(trie.find(word) is not None)\n", + " for word in words_removed:\n", + " assert_true(trie.find(word) is None)\n", + "\n", + " print('Test: Remove a')\n", + " trie.remove('a')\n", + " words_removed = ['a', 'me', 'mens']\n", + " words = ['at', 'has', 'hat', 'he',\n", + " 'men', 'met']\n", + " for word in words:\n", + " assert_true(trie.find(word) is not None)\n", + " for word in words_removed:\n", + " assert_true(trie.find(word) is None)\n", + "\n", + " print('Test: Remove has')\n", + " trie.remove('has')\n", + " words_removed = ['a', 'has', 'me', 'mens']\n", + " words = ['at', 'hat', 'he',\n", + " 'men', 'met']\n", + " for word in words:\n", + " assert_true(trie.find(word) is not None)\n", + " for word in words_removed:\n", + " assert_true(trie.find(word) is None)\n", + "\n", + " print('Success: test_trie')\n", + "\n", + "\n", + "def main():\n", + " test = TestTrie()\n", + " test.test_trie()\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " main()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Solution Notebook\n", + "\n", + "Review the [Solution Notebook](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/trie/trie_solution.ipynb) 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/graphs_trees/trie/trie_solution.ipynb b/graphs_trees/trie/trie_solution.ipynb new file mode 100644 index 0000000..aec6ba2 --- /dev/null +++ b/graphs_trees/trie/trie_solution.ipynb @@ -0,0 +1,418 @@ +{ + "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 a trie with find, insert, remove, and list_words methods.\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 assume we are working with strings?\n", + " * Yes\n", + "* Are the strings in ASCII?\n", + " * Yes\n", + "* Should `find` only match exact words with a terminating character?\n", + " * Yes\n", + "* Should `list_words` only return words with a terminating character?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "
\n",
+    "\n",
+    "root node is denoted by ''\n",
+    "\n",
+    "         ''\n",
+    "       /  |  \\\n",
+    "      h   a*  m\n",
+    "     / \\   \\   \\\n",
+    "    a   e*  t*  e*\n",
+    "   / \\         / \\\n",
+    "  s*  t*      n*  t*\n",
+    "             /\n",
+    "            s*\n",
+    "\n",
+    "find\n",
+    "\n",
+    "* Find on an empty trie\n",
+    "* Find non-matching\n",
+    "* Find matching\n",
+    "\n",
+    "insert\n",
+    "\n",
+    "* Insert on empty trie\n",
+    "* Insert to make a leaf terminator char\n",
+    "* Insert to extend an existing terminator char\n",
+    "\n",
+    "remove\n",
+    "\n",
+    "* Remove me\n",
+    "* Remove mens\n",
+    "* Remove a\n",
+    "* Remove has\n",
+    "\n",
+    "list_words\n",
+    "\n",
+    "* List empty\n",
+    "* List general case\n",
+    "
\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "### find\n", + "\n", + "* Set node to the root\n", + "* For each char in the input word\n", + " * Check the current node's children to see if it contains the char\n", + " * If a child has the char, set node to the child\n", + " * Else, return None\n", + "* Return the last child node if it has a terminator, else None\n", + "\n", + "Complexity:\n", + "* Time: O(m), where m is the length of the word\n", + "* Space: O(h) for the recursion depth (tree height), or O(1) if using an iterative approach\n", + "\n", + "### insert\n", + "\n", + "* set node to the root\n", + "* For each char in the input word\n", + " * Check the current node's children to see if it contains the char\n", + " * If a child has the char, set node to the child\n", + " * Else, insert a new node with the char\n", + " * Update children and parents\n", + "* Set the last node as a terminating node\n", + "\n", + "Complexity:\n", + "* Time: O(m), where m is the length of the word\n", + "* Space: O(h) for the recursion depth (tree height), or O(1) if using an iterative approach\n", + "\n", + "### remove\n", + "\n", + "* Determine the matching terminating node by calling the find method\n", + "* If the matching node has children, remove the terminator to prevent orphaning its children\n", + "* Set the parent node to the matching node's parent\n", + "* We'll be looping up the parent chain to propagate the delete\n", + "* While the parent is valid\n", + " * If the node has children\n", + " * Return to prevent orphaning its remaining children\n", + " * If the node is a terminating node and it isn't the original matching node from the find call\n", + " * Return to prevent deleting this additional valid word\n", + " * Remove the parent node's child entry matching the node\n", + " * Set the node to the parent\n", + " * Set the parent to the parent's parent\n", + "\n", + "Complexity:\n", + "* Time: O(m+h), where where m is the length of the word and h is the tree height\n", + "* Space: O(h) for the recursion depth (tree height), or O(1) if using an iterative approach\n", + "\n", + "### list_words\n", + "\n", + "* Do a pre-order traversal, passing down the current word\n", + " * When you reach a terminating node, add it to the list of results\n", + "\n", + "Complexity:\n", + "* Time: O(n)\n", + "* Space: O(h) for the recursion depth (tree height), or O(1) if using an iterative approach" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting trie.py\n" + ] + } + ], + "source": [ + "%%writefile trie.py\n", + "from collections import OrderedDict\n", + "\n", + "\n", + "class Node(object):\n", + "\n", + " def __init__(self, key, parent=None, terminates=False):\n", + " self.key = key\n", + " self.terminates = False\n", + " self.parent = parent\n", + " self.children = {}\n", + "\n", + "\n", + "class Trie(object):\n", + "\n", + " def __init__(self):\n", + " self.root = Node('')\n", + "\n", + " def find(self, word):\n", + " if word is None:\n", + " raise TypeError('word cannot be None')\n", + " node = self.root\n", + " for char in word:\n", + " if char in node.children:\n", + " node = node.children[char]\n", + " else:\n", + " return None\n", + " return node if node.terminates else None\n", + "\n", + " def insert(self, word):\n", + " if word is None:\n", + " raise TypeError('word cannot be None')\n", + " node = self.root\n", + " parent = None\n", + " for char in word:\n", + " if char in node.children:\n", + " node = node.children[char]\n", + " else:\n", + " node.children[char] = Node(char, parent=node)\n", + " node = node.children[char]\n", + " node.terminates = True\n", + "\n", + " def remove(self, word):\n", + " if word is None:\n", + " raise TypeError('word cannot be None')\n", + " node = self.find(word)\n", + " if node is None:\n", + " raise KeyError('word does not exist')\n", + " node.terminates = False\n", + " parent = node.parent\n", + " while parent is not None:\n", + " # As we are propagating the delete up the \n", + " # parents, if this node has children, stop\n", + " # here to prevent orphaning its children.\n", + " # Or\n", + " # if this node is a terminating node that is\n", + " # not the terminating node of the input word, \n", + " # stop to prevent removing the associated word.\n", + " if node.children or node.terminates:\n", + " return\n", + " del parent.children[node.key]\n", + " node = parent\n", + " parent = parent.parent\n", + "\n", + " def list_words(self):\n", + " result = []\n", + " curr_word = ''\n", + " self._list_words(self.root, curr_word, result)\n", + " return result\n", + "\n", + " def _list_words(self, node, curr_word, result):\n", + " if node is None:\n", + " return\n", + " for key, child in node.children.items():\n", + " if child.terminates:\n", + " result.append(curr_word + key)\n", + " self._list_words(child, curr_word + key, result)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%run trie.py" + ] + }, + { + "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_trie.py\n" + ] + } + ], + "source": [ + "%%writefile test_trie.py\n", + "from nose.tools import assert_true\n", + "from nose.tools import raises\n", + "\n", + "\n", + "class TestTrie(object): \n", + "\n", + " def test_trie(self):\n", + " trie = Trie()\n", + "\n", + " print('Test: Insert')\n", + " words = ['a', 'at', 'has', 'hat', 'he',\n", + " 'me', 'men', 'mens', 'met']\n", + " for word in words:\n", + " trie.insert(word)\n", + " for word in trie.list_words():\n", + " assert_true(trie.find(word) is not None)\n", + " \n", + " print('Test: Remove me')\n", + " trie.remove('me')\n", + " words_removed = ['me']\n", + " words = ['a', 'at', 'has', 'hat', 'he',\n", + " 'men', 'mens', 'met']\n", + " for word in words:\n", + " assert_true(trie.find(word) is not None)\n", + " for word in words_removed:\n", + " assert_true(trie.find(word) is None)\n", + "\n", + " print('Test: Remove mens')\n", + " trie.remove('mens')\n", + " words_removed = ['me', 'mens']\n", + " words = ['a', 'at', 'has', 'hat', 'he',\n", + " 'men', 'met']\n", + " for word in words:\n", + " assert_true(trie.find(word) is not None)\n", + " for word in words_removed:\n", + " assert_true(trie.find(word) is None)\n", + "\n", + " print('Test: Remove a')\n", + " trie.remove('a')\n", + " words_removed = ['a', 'me', 'mens']\n", + " words = ['at', 'has', 'hat', 'he',\n", + " 'men', 'met']\n", + " for word in words:\n", + " assert_true(trie.find(word) is not None)\n", + " for word in words_removed:\n", + " assert_true(trie.find(word) is None)\n", + "\n", + " print('Test: Remove has')\n", + " trie.remove('has')\n", + " words_removed = ['a', 'has', 'me', 'mens']\n", + " words = ['at', 'hat', 'he',\n", + " 'men', 'met']\n", + " for word in words:\n", + " assert_true(trie.find(word) is not None)\n", + " for word in words_removed:\n", + " assert_true(trie.find(word) is None)\n", + "\n", + " print('Success: test_trie')\n", + "\n", + " @raises(Exception)\n", + " def test_trie_remove_invalid(self):\n", + " print('Test: Remove from empty trie')\n", + " trie = Trie()\n", + " assert_true(trie.remove('foo') is None) \n", + "\n", + "\n", + "def main():\n", + " test = TestTrie()\n", + " test.test_trie()\n", + " test.test_trie_remove_invalid()\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " main()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Test: Insert\n", + "Test: Remove me\n", + "Test: Remove mens\n", + "Test: Remove a\n", + "Test: Remove has\n", + "Success: test_trie\n", + "Test: Remove from empty trie\n" + ] + } + ], + "source": [ + "%run -i test_trie.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 +} From 98b788724576ec01a3a6392b6b503ad337c2393a Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Thu, 30 Mar 2017 05:38:53 -0400 Subject: [PATCH 47/90] Add tree lca challenge --- graphs_trees/tree_lca/__init__.py | 0 graphs_trees/tree_lca/test_lca.py | 45 +++ .../tree_lca/tree_lca_challenge.ipynb | 221 ++++++++++++ graphs_trees/tree_lca/tree_lca_solution.ipynb | 314 ++++++++++++++++++ 4 files changed, 580 insertions(+) create mode 100644 graphs_trees/tree_lca/__init__.py create mode 100644 graphs_trees/tree_lca/test_lca.py create mode 100644 graphs_trees/tree_lca/tree_lca_challenge.ipynb create mode 100644 graphs_trees/tree_lca/tree_lca_solution.ipynb diff --git a/graphs_trees/tree_lca/__init__.py b/graphs_trees/tree_lca/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/graphs_trees/tree_lca/test_lca.py b/graphs_trees/tree_lca/test_lca.py new file mode 100644 index 0000000..1a25af3 --- /dev/null +++ b/graphs_trees/tree_lca/test_lca.py @@ -0,0 +1,45 @@ +from nose.tools import assert_equal + + +class TestLowestCommonAncestor(object): + + def test_lca(self): + node10 = Node(10) + node5 = Node(5) + node12 = Node(12) + node3 = Node(3) + node1 = Node(1) + node8 = Node(8) + node9 = Node(9) + node18 = Node(18) + node20 = Node(20) + node40 = Node(40) + node3.left = node1 + node3.right = node8 + node5.left = node12 + node5.right = node3 + node20.left = node40 + node9.left = node18 + node9.right = node20 + node10.left = node5 + node10.right = node9 + root = node10 + node0 = Node(0) + binary_tree = BinaryTree() + assert_equal(binary_tree.lca(root, node0, node5), None) + assert_equal(binary_tree.lca(root, node5, node0), None) + assert_equal(binary_tree.lca(root, node1, node8), node3) + assert_equal(binary_tree.lca(root, node12, node8), node5) + assert_equal(binary_tree.lca(root, node12, node40), node10) + assert_equal(binary_tree.lca(root, node9, node20), node9) + assert_equal(binary_tree.lca(root, node3, node5), node5) + print('Success: test_lca') + + +def main(): + test = TestLowestCommonAncestor() + test.test_lca() + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/graphs_trees/tree_lca/tree_lca_challenge.ipynb b/graphs_trees/tree_lca/tree_lca_challenge.ipynb new file mode 100644 index 0000000..a1443da --- /dev/null +++ b/graphs_trees/tree_lca/tree_lca_challenge.ipynb @@ -0,0 +1,221 @@ +{ + "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: Find the lowest common ancestor in a binary tree.\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", + "* Is this a binary search tree?\n", + " * No\n", + "* Can we assume the two nodes are in the tree?\n", + " * No\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "
\n",
+    "          _10_\n",
+    "        /      \\\n",
+    "       5        9\n",
+    "      / \\      / \\\n",
+    "     12  3    18  20\n",
+    "        / \\       /\n",
+    "       1   8     40\n",
+    "
\n", + "\n", + "* 0, 5 -> None\n", + "* 5, 0 -> None\n", + "* 1, 8 -> 3\n", + "* 12, 8 -> 5\n", + "* 12, 40 -> 10\n", + "* 9, 20 -> 9\n", + "* 3, 5 -> 5" + ] + }, + { + "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 Node(object):\n", + "\n", + " def __init__(self, key, left=None, right=None):\n", + " self.key = key\n", + " self.left = left\n", + " self.right = right\n", + "\n", + " def __repr__(self):\n", + " return str(self.key)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class BinaryTree(object):\n", + "\n", + " def lca(self, root, node1, node2):\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_lca.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestLowestCommonAncestor(object):\n", + "\n", + " def test_lca(self):\n", + " node10 = Node(10)\n", + " node5 = Node(5)\n", + " node12 = Node(12)\n", + " node3 = Node(3)\n", + " node1 = Node(1)\n", + " node8 = Node(8)\n", + " node9 = Node(9)\n", + " node18 = Node(18)\n", + " node20 = Node(20)\n", + " node40 = Node(40)\n", + " node3.left = node1\n", + " node3.right = node8\n", + " node5.left = node12\n", + " node5.right = node3\n", + " node20.left = node40\n", + " node9.left = node18\n", + " node9.right = node20\n", + " node10.left = node5\n", + " node10.right = node9\n", + " root = node10\n", + " node0 = Node(0)\n", + " binary_tree = BinaryTree()\n", + " assert_equal(binary_tree.lca(root, node0, node5), None)\n", + " assert_equal(binary_tree.lca(root, node5, node0), None)\n", + " assert_equal(binary_tree.lca(root, node1, node8), node3)\n", + " assert_equal(binary_tree.lca(root, node12, node8), node5)\n", + " assert_equal(binary_tree.lca(root, node12, node40), node10)\n", + " assert_equal(binary_tree.lca(root, node9, node20), node9)\n", + " assert_equal(binary_tree.lca(root, node3, node5), node5)\n", + " print('Success: test_lca')\n", + "\n", + "\n", + "def main():\n", + " test = TestLowestCommonAncestor()\n", + " test.test_lca()\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/graphs_trees/tree_lca/tree_lca_solution.ipynb b/graphs_trees/tree_lca/tree_lca_solution.ipynb new file mode 100644 index 0000000..4fc621f --- /dev/null +++ b/graphs_trees/tree_lca/tree_lca_solution.ipynb @@ -0,0 +1,314 @@ +{ + "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: Find the lowest common ancestor of two nodes in a binary tree.\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", + "* Is this a binary search tree?\n", + " * No\n", + "* Can we assume the two nodes are in the tree?\n", + " * No\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "
\n",
+    "          _10_\n",
+    "        /      \\\n",
+    "       5        9\n",
+    "      / \\      / \\\n",
+    "     12  3    18  20\n",
+    "        / \\       /\n",
+    "       1   8     40\n",
+    "
\n", + "\n", + "* 0, 5 -> None\n", + "* 5, 0 -> None\n", + "* 1, 8 -> 3\n", + "* 12, 8 -> 5\n", + "* 12, 40 -> 10\n", + "* 9, 20 -> 9\n", + "* 3, 5 -> 5" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "* Verify both nodes are in the tree\n", + "* Base cases\n", + " * If the root is None, return None\n", + " * If the root is either node, return the root\n", + "* Recursively search left and right\n", + "* If the left and right are both nodes\n", + " * The return the root\n", + "* Else, left or right, whichever is valid\n", + "\n", + "Complexity:\n", + "* Time: O(h), where h is the height of the tree\n", + "* Space: O(h), where h is the recursion depth" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class Node(object):\n", + "\n", + " def __init__(self, key, left=None, right=None):\n", + " self.key = key\n", + " self.left = left\n", + " self.right = right\n", + "\n", + " def __repr__(self):\n", + " return str(self.key)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class BinaryTree(object):\n", + "\n", + " def lca(self, root, node1, node2):\n", + " if None in (root, node1, node2):\n", + " return None\n", + " if (not self._node_in_tree(root, node1) or\n", + " not self._node_in_tree(root, node2)):\n", + " return None\n", + " return self._lca(root, node1, node2)\n", + "\n", + " def _node_in_tree(self, root, node):\n", + " if root is None:\n", + " return False\n", + " if root is node:\n", + " return True\n", + " left = self._node_in_tree(root.left, node)\n", + " right = self._node_in_tree(root.right, node)\n", + " return left or right\n", + "\n", + " def _lca(self, root, node1, node2):\n", + " if root is None:\n", + " return None\n", + " if root is node1 or root is node2:\n", + " return root\n", + " left_node = self._lca(root.left, node1, node2)\n", + " right_node = self._lca(root.right, node1, node2)\n", + " if left_node is not None and right_node is not None:\n", + " return root\n", + " else:\n", + " return left_node if left_node is not None else right_node" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class LcaResult(object):\n", + "\n", + " def __init__(self, node, is_ancestor):\n", + " self.node = node\n", + " self.is_ancestor = is_ancestor\n", + "\n", + "\n", + "class BinaryTreeOptimized(object):\n", + "\n", + " def lca(self, root, node1, node2):\n", + " if root is None:\n", + " raise TypeError('root cannot be None')\n", + " result = self._lca(root, node1, node2)\n", + " if result.is_ancestor:\n", + " return result.node\n", + " return None\n", + "\n", + " def _lca(self, curr_node, node1, node2):\n", + " if curr_node is None:\n", + " return LcaResult(None, is_ancestor=False)\n", + " if curr_node is node1 and curr_node is node2:\n", + " return LcaResult(curr_node, is_ancestor=True)\n", + " left_result = self._lca(curr_node.left, node1, node2)\n", + " if left_result.is_ancestor:\n", + " return left_result\n", + " right_result = self._lca(curr_node.right, node1, node2)\n", + " if right_result.is_ancestor:\n", + " return right_result\n", + " if left_result.node is not None and right_result.node is not None:\n", + " return LcaResult(curr_node, is_ancestor=True)\n", + " elif curr_node is node1 or curr_node is node2:\n", + " is_ancestor = left_result.node is not None or \\\n", + " right_result.node is not None\n", + " return LcaResult(curr_node, is_ancestor)\n", + " else:\n", + " return LcaResult(left_result.node if left_result.node is not None \\\n", + " else right_result.node, is_ancestor=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Unit Test" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting test_lca.py\n" + ] + } + ], + "source": [ + "%%writefile test_lca.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestLowestCommonAncestor(object):\n", + "\n", + " def test_lca(self):\n", + " node10 = Node(10)\n", + " node5 = Node(5)\n", + " node12 = Node(12)\n", + " node3 = Node(3)\n", + " node1 = Node(1)\n", + " node8 = Node(8)\n", + " node9 = Node(9)\n", + " node18 = Node(18)\n", + " node20 = Node(20)\n", + " node40 = Node(40)\n", + " node3.left = node1\n", + " node3.right = node8\n", + " node5.left = node12\n", + " node5.right = node3\n", + " node20.left = node40\n", + " node9.left = node18\n", + " node9.right = node20\n", + " node10.left = node5\n", + " node10.right = node9\n", + " root = node10\n", + " node0 = Node(0)\n", + " binary_tree = BinaryTree()\n", + " assert_equal(binary_tree.lca(root, node0, node5), None)\n", + " assert_equal(binary_tree.lca(root, node5, node0), None)\n", + " assert_equal(binary_tree.lca(root, node1, node8), node3)\n", + " assert_equal(binary_tree.lca(root, node12, node8), node5)\n", + " assert_equal(binary_tree.lca(root, node12, node40), node10)\n", + " assert_equal(binary_tree.lca(root, node9, node20), node9)\n", + " assert_equal(binary_tree.lca(root, node3, node5), node5)\n", + " print('Success: test_lca')\n", + "\n", + "\n", + "def main():\n", + " test = TestLowestCommonAncestor()\n", + " test.test_lca()\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " main()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Success: test_lca\n" + ] + } + ], + "source": [ + "%run -i test_lca.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 +} From 2a368b42b6274715826f542653c855e13047a7d6 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Thu, 30 Mar 2017 05:39:30 -0400 Subject: [PATCH 48/90] Add invert tree challenge --- graphs_trees/invert_tree/__init__.py | 0 .../invert_tree/invert_tree_challenge.ipynb | 200 +++++++++++++++ .../invert_tree/invert_tree_solution.ipynb | 229 ++++++++++++++++++ graphs_trees/invert_tree/test_invert_tree.py | 32 +++ 4 files changed, 461 insertions(+) create mode 100644 graphs_trees/invert_tree/__init__.py create mode 100644 graphs_trees/invert_tree/invert_tree_challenge.ipynb create mode 100644 graphs_trees/invert_tree/invert_tree_solution.ipynb create mode 100644 graphs_trees/invert_tree/test_invert_tree.py diff --git a/graphs_trees/invert_tree/__init__.py b/graphs_trees/invert_tree/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/graphs_trees/invert_tree/invert_tree_challenge.ipynb b/graphs_trees/invert_tree/invert_tree_challenge.ipynb new file mode 100644 index 0000000..d701ae8 --- /dev/null +++ b/graphs_trees/invert_tree/invert_tree_challenge.ipynb @@ -0,0 +1,200 @@ +{ + "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: Invert a binary tree.\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", + "* What does it mean to invert a binary tree?\n", + " * Swap all left and right node pairs\n", + "* Can we assume we already have a Node class?\n", + " * Yes\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",
+    "Input:\n",
+    "     5\n",
+    "   /   \\\n",
+    "  2     7\n",
+    " / \\   / \\\n",
+    "1   3 6   9\n",
+    "\n",
+    "Output:\n",
+    "     5\n",
+    "   /   \\\n",
+    "  7     2\n",
+    " / \\   / \\\n",
+    "9   6 3   1\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": [ + "%run ../bst/bst.py" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class InverseBst(Bst):\n", + "\n", + " def invert_tree(self):\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_invert_tree.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestInvertTree(object):\n", + "\n", + " def test_invert_tree(self):\n", + " root = Node(5)\n", + " bst = InverseBst(root)\n", + " node2 = bst.insert(2)\n", + " node3 = bst.insert(3)\n", + " node1 = bst.insert(1)\n", + " node7 = bst.insert(7)\n", + " node6 = bst.insert(6)\n", + " node9 = bst.insert(9)\n", + " result = bst.invert_tree()\n", + " assert_equal(result, root)\n", + " assert_equal(result.left, node7)\n", + " assert_equal(result.right, node2)\n", + " assert_equal(result.left.left, node9)\n", + " assert_equal(result.left.right, node6)\n", + " assert_equal(result.right.left, node3)\n", + " assert_equal(result.right.right, node1)\n", + " print('Success: test_invert_tree')\n", + "\n", + "\n", + "def main():\n", + " test = TestInvertTree()\n", + " test.test_invert_tree()\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/graphs_trees/invert_tree/invert_tree_solution.ipynb b/graphs_trees/invert_tree/invert_tree_solution.ipynb new file mode 100644 index 0000000..3037e57 --- /dev/null +++ b/graphs_trees/invert_tree/invert_tree_solution.ipynb @@ -0,0 +1,229 @@ +{ + "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: Invert a binary tree.\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", + "* What does it mean to invert a binary tree?\n", + " * Swap all left and right node pairs\n", + "* Can we assume we already have a Node class?\n", + " * Yes\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",
+    "Input:\n",
+    "     5\n",
+    "   /   \\\n",
+    "  2     7\n",
+    " / \\   / \\\n",
+    "1   3 6   9\n",
+    "\n",
+    "Output:\n",
+    "     5\n",
+    "   /   \\\n",
+    "  7     2\n",
+    " / \\   / \\\n",
+    "9   6 3   1\n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "* Base case\n", + " * If the root is None, return\n", + "* Recursive case\n", + " * Recurse on the left node\n", + " * Recurse on the right node\n", + " * Swap left and right\n", + "* Return the node\n", + "\n", + "Complexity:\n", + "* Time: O(n)\n", + "* Space: O(h), where h is the height, for the recursion depth" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%run ../bst/bst.py" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class InverseBst(Bst):\n", + "\n", + " def invert_tree(self):\n", + " if self.root is None:\n", + " raise TypeError('root cannot be None')\n", + " return self._invert_tree(self.root)\n", + "\n", + " def _invert_tree(self, root):\n", + " if root is None:\n", + " return\n", + " self._invert_tree(root.left)\n", + " self._invert_tree(root.right)\n", + " root.left, root.right = root.right, root.left\n", + " return root" + ] + }, + { + "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_invert_tree.py\n" + ] + } + ], + "source": [ + "%%writefile test_invert_tree.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestInvertTree(object):\n", + "\n", + " def test_invert_tree(self):\n", + " root = Node(5)\n", + " bst = InverseBst(root)\n", + " node2 = bst.insert(2)\n", + " node3 = bst.insert(3)\n", + " node1 = bst.insert(1)\n", + " node7 = bst.insert(7)\n", + " node6 = bst.insert(6)\n", + " node9 = bst.insert(9)\n", + " result = bst.invert_tree()\n", + " assert_equal(result, root)\n", + " assert_equal(result.left, node7)\n", + " assert_equal(result.right, node2)\n", + " assert_equal(result.left.left, node9)\n", + " assert_equal(result.left.right, node6)\n", + " assert_equal(result.right.left, node3)\n", + " assert_equal(result.right.right, node1)\n", + " print('Success: test_invert_tree')\n", + "\n", + "\n", + "def main():\n", + " test = TestInvertTree()\n", + " test.test_invert_tree()\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_invert_tree\n" + ] + } + ], + "source": [ + "%run -i test_invert_tree.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/graphs_trees/invert_tree/test_invert_tree.py b/graphs_trees/invert_tree/test_invert_tree.py new file mode 100644 index 0000000..ab33458 --- /dev/null +++ b/graphs_trees/invert_tree/test_invert_tree.py @@ -0,0 +1,32 @@ +from nose.tools import assert_equal, assert_raises + + +class TestInvertTree(object): + + def test_invert_tree(self): + root = Node(5) + bst = InverseBst(root) + node2 = bst.insert(2) + node3 = bst.insert(3) + node1 = bst.insert(1) + node7 = bst.insert(7) + node6 = bst.insert(6) + node9 = bst.insert(9) + result = bst.invert_tree() + assert_equal(result, root) + assert_equal(result.left, node7) + assert_equal(result.right, node2) + assert_equal(result.left.left, node9) + assert_equal(result.left.right, node6) + assert_equal(result.right.left, node3) + assert_equal(result.right.right, node1) + print('Success: test_invert_tree') + + +def main(): + test = TestInvertTree() + test.test_invert_tree() + + +if __name__ == '__main__': + main() \ No newline at end of file From d352f56ecbd5267574630ac7409bc92f3f2a5613 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Thu, 30 Mar 2017 05:39:53 -0400 Subject: [PATCH 49/90] Add min heap challenge --- graphs_trees/min_heap/__init__.py | 0 graphs_trees/min_heap/min_heap.py | 66 +++ .../min_heap/min_heap_challenge.ipynb | 214 +++++++++ graphs_trees/min_heap/min_heap_solution.ipynb | 411 ++++++++++++++++++ graphs_trees/min_heap/test_min_heap.py | 50 +++ 5 files changed, 741 insertions(+) create mode 100644 graphs_trees/min_heap/__init__.py create mode 100644 graphs_trees/min_heap/min_heap.py create mode 100644 graphs_trees/min_heap/min_heap_challenge.ipynb create mode 100644 graphs_trees/min_heap/min_heap_solution.ipynb create mode 100644 graphs_trees/min_heap/test_min_heap.py diff --git a/graphs_trees/min_heap/__init__.py b/graphs_trees/min_heap/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/graphs_trees/min_heap/min_heap.py b/graphs_trees/min_heap/min_heap.py new file mode 100644 index 0000000..81c0d37 --- /dev/null +++ b/graphs_trees/min_heap/min_heap.py @@ -0,0 +1,66 @@ +from __future__ import division + +import sys + + +class MinHeap(object): + + def __init__(self): + self.array = [] + + def __len__(self): + return len(self.array) + + def extract_min(self): + if not self.array: + return None + if len(self.array) == 1: + return self.array.pop(0) + minimum = self.array[0] + # Move the last element to the root + self.array[0] = self.array.pop(-1) + self._bubble_down(index=0) + return minimum + + def peek_min(self): + return self.array[0] if self.array else None + + def insert(self, key): + if key is None: + raise TypeError('key cannot be None') + self.array.append(key) + self._bubble_up(index=len(self.array)-1) + + def _bubble_up(self, index): + if index == 0: + return + index_parent = (index-1) // 2 + if self.array[index] < self.array[index_parent]: + # Swap the indices and recurse + self.array[index], self.array[index_parent] = \ + self.array[index_parent], self.array[index] + self._bubble_up(index_parent) + + def _bubble_down(self, index): + min_child_index = self._find_smaller_child(index) + if min_child_index == -1: + return + if self.array[index] > self.array[min_child_index]: + # Swap the indices and recurse + self.array[index], self.array[min_child_index] = \ + self.array[min_child_index], self.array[index] + self._bubble_down(min_child_index) + + def _find_smaller_child(self, index): + left_child_index = 2 * index + 1 + right_child_index = 2 * index + 2 + if right_child_index >= len(self.array): + if left_child_index >= len(self.array): + return -1 + else: + return left_child_index + else: + if self.array[left_child_index] < self.array[right_child_index]: + return left_child_index + else: + return right_child_index \ No newline at end of file diff --git a/graphs_trees/min_heap/min_heap_challenge.ipynb b/graphs_trees/min_heap/min_heap_challenge.ipynb new file mode 100644 index 0000000..88ed925 --- /dev/null +++ b/graphs_trees/min_heap/min_heap_challenge.ipynb @@ -0,0 +1,214 @@ +{ + "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 a min heap with extract_min and insert methods.\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 assume the inputs are ints?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* Extract min of an empty tree\n", + "* Extract min general case\n", + "* Insert into an empty tree\n", + "* Insert general case (left and right insert)\n", + "\n", + "
\n",
+    "          _5_\n",
+    "        /     \\\n",
+    "       20     15\n",
+    "      / \\    /  \\\n",
+    "     22  40 25\n",
+    "     \n",
+    "extract_min(): 5\n",
+    "\n",
+    "          _15_\n",
+    "        /      \\\n",
+    "       20      25\n",
+    "      / \\     /  \\\n",
+    "     22  40 \n",
+    "\n",
+    "insert(2):\n",
+    "\n",
+    "          _2_\n",
+    "        /     \\\n",
+    "       20      5\n",
+    "      / \\     / \\\n",
+    "     22  40  25  15\n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "Refer to the [Solution Notebook](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/min_heap/min_heap_solution.ipynb). 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 MinHeap(object):\n", + "\n", + " def __init__(self):\n", + " # TODO: Implement me\n", + " pass\n", + "\n", + " def extract_min(self):\n", + " # TODO: Implement me\n", + " pass \n", + "\n", + " def peek_min(self):\n", + " # TODO: Implement me\n", + " pass\n", + "\n", + " def insert(self, data):\n", + " # TODO: Implement me\n", + " pass\n", + "\n", + " def _bubble_up(self, index):\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_min_heap.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestMinHeap(object):\n", + "\n", + " def test_min_heap(self):\n", + " heap = MinHeap()\n", + " assert_equal(heap.peek_min(), None)\n", + " assert_equal(heap.extract_min(), None)\n", + " heap.insert(20)\n", + " assert_equal(heap.peek_min(), 20)\n", + " heap.insert(5)\n", + " assert_equal(heap.peek_min(), 5)\n", + " heap.insert(15)\n", + " heap.insert(22)\n", + " heap.insert(40)\n", + " heap.insert(5)\n", + " assert_equal(heap.peek_min(), 5)\n", + " heap.insert(3)\n", + " assert_equal(heap.peek_min(), 3)\n", + " assert_equal(heap.extract_min(), 3)\n", + " assert_equal(heap.peek_min(), 5)\n", + " print('Success: test_min_heap')\n", + "\n", + " \n", + "def main():\n", + " test = TestMinHeap()\n", + " test.test_min_heap()\n", + "\n", + " \n", + "if __name__ == '__main__':\n", + " main()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Solution Notebook\n", + "\n", + "Review the [Solution Notebook](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/min_heap/min_heap_solution.ipynb) 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/graphs_trees/min_heap/min_heap_solution.ipynb b/graphs_trees/min_heap/min_heap_solution.ipynb new file mode 100644 index 0000000..f886d51 --- /dev/null +++ b/graphs_trees/min_heap/min_heap_solution.ipynb @@ -0,0 +1,411 @@ +{ + "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 a min heap with extract_min and insert methods.\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 assume the inputs are ints?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* Extract min of an empty tree\n", + "* Extract min general case\n", + "* Insert into an empty tree\n", + "* Insert general case (left and right insert)\n", + "\n", + "
\n",
+    "          _5_\n",
+    "        /     \\\n",
+    "       20     15\n",
+    "      / \\    /  \\\n",
+    "     22  40 25\n",
+    "     \n",
+    "extract_min(): 5\n",
+    "\n",
+    "          _15_\n",
+    "        /      \\\n",
+    "       20      25\n",
+    "      / \\     /  \\\n",
+    "     22  40 \n",
+    "\n",
+    "insert(2):\n",
+    "\n",
+    "          _2_\n",
+    "        /     \\\n",
+    "       20      5\n",
+    "      / \\     / \\\n",
+    "     22  40  25  15\n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "A heap is a complete binary tree where each node is smaller than its children.\n", + "\n", + "### extract_min\n", + "\n", + "
\n",
+    "          _5_\n",
+    "        /     \\\n",
+    "       20     15\n",
+    "      / \\    /  \\\n",
+    "     22  40 25\n",
+    "\n",
+    "Save the root as the value to be returned: 5\n",
+    "Move the right most element to the root: 25\n",
+    "\n",
+    "          _25_\n",
+    "        /      \\\n",
+    "       20      15\n",
+    "      / \\     /  \\\n",
+    "     22  40 \n",
+    "\n",
+    "Bubble down 25: Swap 25 and 15 (the smaller child)\n",
+    "\n",
+    "          _15_\n",
+    "        /      \\\n",
+    "       20      25\n",
+    "      / \\     /  \\\n",
+    "     22  40 \n",
+    "\n",
+    "Return 5\n",
+    "
\n", + "\n", + "We'll use an array to represent the tree, here are the indices:\n", + "\n", + "
\n",
+    "          _0_\n",
+    "        /     \\\n",
+    "       1       2\n",
+    "      / \\     / \\\n",
+    "     3   4   \n",
+    "
\n", + "\n", + "To get to a child, we take 2 * index + 1 (left child) or 2 * index + 2 (right child).\n", + "\n", + "For example, the right child of index 1 is 2 * 1 + 2 = 4.\n", + "\n", + "Complexity:\n", + "* Time: O(lg(n))\n", + "* Space: O(lg(n)) for the recursion depth (tree height), or O(1) if using an iterative approach\n", + "\n", + "### insert\n", + "\n", + "
\n",
+    "          _5_\n",
+    "        /     \\\n",
+    "       20     15\n",
+    "      / \\    /  \\\n",
+    "     22  40 25\n",
+    "\n",
+    "insert(2):\n",
+    "Insert at the right-most spot to maintain the heap property.\n",
+    "\n",
+    "          _5_\n",
+    "        /     \\\n",
+    "       20     15\n",
+    "      / \\    /  \\\n",
+    "     22  40 25   2\n",
+    "\n",
+    "Bubble up 2: Swap 2 and 15\n",
+    "\n",
+    "          _5_\n",
+    "        /     \\\n",
+    "       20     2\n",
+    "      / \\    / \\\n",
+    "     22  40 25  15\n",
+    "\n",
+    "Bubble up 2: Swap 2 and 5\n",
+    "\n",
+    "          _2_\n",
+    "        /     \\\n",
+    "       20     5\n",
+    "      / \\    / \\\n",
+    "     22  40 25  15\n",
+    "
\n", + "\n", + "We'll use an array to represent the tree, here are the indices:\n", + "\n", + "
\n",
+    "          _0_\n",
+    "        /     \\\n",
+    "       1       2\n",
+    "      / \\     / \\\n",
+    "     3   4   5   6\n",
+    "
\n", + "\n", + "To get to a parent, we take (index - 1) // 2. \n", + "\n", + "For example, the parent of index 6 is (6 - 1) // 2 = 2.\n", + "\n", + "Complexity:\n", + "* Time: O(lg(n))\n", + "* Space: O(lg(n)) for the recursion depth (tree height), or O(1) if using an iterative approach" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting min_heap.py\n" + ] + } + ], + "source": [ + "%%writefile min_heap.py\n", + "from __future__ import division\n", + "\n", + "import sys\n", + "\n", + "\n", + "class MinHeap(object):\n", + "\n", + " def __init__(self):\n", + " self.array = []\n", + "\n", + " def __len__(self):\n", + " return len(self.array)\n", + "\n", + " def extract_min(self):\n", + " if not self.array:\n", + " return None\n", + " if len(self.array) == 1:\n", + " return self.array.pop(0)\n", + " minimum = self.array[0]\n", + " # Move the last element to the root\n", + " self.array[0] = self.array.pop(-1)\n", + " self._bubble_down(index=0)\n", + " return minimum\n", + "\n", + " def peek_min(self):\n", + " return self.array[0] if self.array else None\n", + "\n", + " def insert(self, key):\n", + " if key is None:\n", + " raise TypeError('key cannot be None')\n", + " self.array.append(key)\n", + " self._bubble_up(index=len(self.array) - 1)\n", + "\n", + " def _bubble_up(self, index):\n", + " if index == 0:\n", + " return\n", + " index_parent = (index - 1) // 2\n", + " if self.array[index] < self.array[index_parent]:\n", + " # Swap the indices and recurse\n", + " self.array[index], self.array[index_parent] = \\\n", + " self.array[index_parent], self.array[index]\n", + " self._bubble_up(index_parent)\n", + "\n", + " def _bubble_down(self, index):\n", + " min_child_index = self._find_smaller_child(index)\n", + " if min_child_index == -1:\n", + " return\n", + " if self.array[index] > self.array[min_child_index]:\n", + " # Swap the indices and recurse\n", + " self.array[index], self.array[min_child_index] = \\\n", + " self.array[min_child_index], self.array[index]\n", + " self._bubble_down(min_child_index)\n", + "\n", + " def _find_smaller_child(self, index):\n", + " left_child_index = 2 * index + 1\n", + " right_child_index = 2 * index + 2\n", + " # No right child\n", + " if right_child_index >= len(self.array):\n", + " # No left child\n", + " if left_child_index >= len(self.array):\n", + " return -1\n", + " # Left child only\n", + " else:\n", + " return left_child_index\n", + " else:\n", + " # Compare left and right children\n", + " if self.array[left_child_index] < self.array[right_child_index]:\n", + " return left_child_index\n", + " else:\n", + " return right_child_index" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%run min_heap.py" + ] + }, + { + "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_min_heap.py\n" + ] + } + ], + "source": [ + "%%writefile test_min_heap.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestMinHeap(object):\n", + "\n", + " def test_min_heap(self):\n", + " heap = MinHeap()\n", + " assert_equal(heap.peek_min(), None)\n", + " assert_equal(heap.extract_min(), None)\n", + " heap.insert(20)\n", + " assert_equal(heap.array[0], 20)\n", + " heap.insert(5)\n", + " assert_equal(heap.array[0], 5)\n", + " assert_equal(heap.array[1], 20)\n", + " heap.insert(15)\n", + " assert_equal(heap.array[0], 5)\n", + " assert_equal(heap.array[1], 20)\n", + " assert_equal(heap.array[2], 15)\n", + " heap.insert(22)\n", + " assert_equal(heap.array[0], 5)\n", + " assert_equal(heap.array[1], 20)\n", + " assert_equal(heap.array[2], 15)\n", + " assert_equal(heap.array[3], 22)\n", + " heap.insert(40)\n", + " assert_equal(heap.array[0], 5)\n", + " assert_equal(heap.array[1], 20)\n", + " assert_equal(heap.array[2], 15)\n", + " assert_equal(heap.array[3], 22)\n", + " assert_equal(heap.array[4], 40)\n", + " heap.insert(3)\n", + " assert_equal(heap.array[0], 3)\n", + " assert_equal(heap.array[1], 20)\n", + " assert_equal(heap.array[2], 5)\n", + " assert_equal(heap.array[3], 22)\n", + " assert_equal(heap.array[4], 40)\n", + " assert_equal(heap.array[5], 15)\n", + " mins = []\n", + " while heap:\n", + " mins.append(heap.extract_min())\n", + " assert_equal(mins, [3, 5, 15, 20, 22, 40])\n", + " print('Success: test_min_heap')\n", + "\n", + " \n", + "def main():\n", + " test = TestMinHeap()\n", + " test.test_min_heap()\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_min_heap\n" + ] + } + ], + "source": [ + "%run -i test_min_heap.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/graphs_trees/min_heap/test_min_heap.py b/graphs_trees/min_heap/test_min_heap.py new file mode 100644 index 0000000..103724d --- /dev/null +++ b/graphs_trees/min_heap/test_min_heap.py @@ -0,0 +1,50 @@ +from nose.tools import assert_equal + + +class TestMinHeap(object): + + def test_min_heap(self): + heap = MinHeap() + assert_equal(heap.peek_min(), None) + assert_equal(heap.extract_min(), None) + heap.insert(20) + assert_equal(heap.array[0], 20) + heap.insert(5) + assert_equal(heap.array[0], 5) + assert_equal(heap.array[1], 20) + heap.insert(15) + assert_equal(heap.array[0], 5) + assert_equal(heap.array[1], 20) + assert_equal(heap.array[2], 15) + heap.insert(22) + assert_equal(heap.array[0], 5) + assert_equal(heap.array[1], 20) + assert_equal(heap.array[2], 15) + assert_equal(heap.array[3], 22) + heap.insert(40) + assert_equal(heap.array[0], 5) + assert_equal(heap.array[1], 20) + assert_equal(heap.array[2], 15) + assert_equal(heap.array[3], 22) + assert_equal(heap.array[4], 40) + heap.insert(3) + assert_equal(heap.array[0], 3) + assert_equal(heap.array[1], 20) + assert_equal(heap.array[2], 5) + assert_equal(heap.array[3], 22) + assert_equal(heap.array[4], 40) + assert_equal(heap.array[5], 15) + mins = [] + while heap: + mins.append(heap.extract_min()) + assert_equal(mins, [3, 5, 15, 20, 22, 40]) + print('Success: test_min_heap') + + +def main(): + test = TestMinHeap() + test.test_min_heap() + + +if __name__ == '__main__': + main() \ No newline at end of file From 3837b5a8cca0c4fed7326eb3676583290e830398 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Thu, 30 Mar 2017 05:40:14 -0400 Subject: [PATCH 50/90] Add graph shortest path unweighted challenge --- .../__init__.py | 0 .../shortest_path_challenge.ipynb | 215 ++++++++++++++ .../shortest_path_solution.ipynb | 274 ++++++++++++++++++ .../test_shortest_path.py | 33 +++ 4 files changed, 522 insertions(+) create mode 100644 graphs_trees/graph_shortest_path_unweighted/__init__.py create mode 100644 graphs_trees/graph_shortest_path_unweighted/shortest_path_challenge.ipynb create mode 100644 graphs_trees/graph_shortest_path_unweighted/shortest_path_solution.ipynb create mode 100644 graphs_trees/graph_shortest_path_unweighted/test_shortest_path.py diff --git a/graphs_trees/graph_shortest_path_unweighted/__init__.py b/graphs_trees/graph_shortest_path_unweighted/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/graphs_trees/graph_shortest_path_unweighted/shortest_path_challenge.ipynb b/graphs_trees/graph_shortest_path_unweighted/shortest_path_challenge.ipynb new file mode 100644 index 0000000..58e7765 --- /dev/null +++ b/graphs_trees/graph_shortest_path_unweighted/shortest_path_challenge.ipynb @@ -0,0 +1,215 @@ +{ + "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: Find the shortest path between two nodes in a graph.\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", + "* Is the graph directed?\n", + " * Yes\n", + "* Is the graph weighted?\n", + " * No\n", + "* Can we assume we already have Graph and Node classes?\n", + " * Yes\n", + "* Are the inputs two Nodes?\n", + " * Yes\n", + "* Is the output a list of Node keys that make up the shortest path?\n", + " * Yes\n", + "* If there is no path, should we return None?\n", + " * Yes\n", + "* Can we assume this is a connected graph?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "Input:\n", + "* `add_edge(source, destination, weight)`\n", + "\n", + "```\n", + "graph.add_edge(0, 1)\n", + "graph.add_edge(0, 4)\n", + "graph.add_edge(0, 5)\n", + "graph.add_edge(1, 3)\n", + "graph.add_edge(1, 4)\n", + "graph.add_edge(2, 1)\n", + "graph.add_edge(3, 2)\n", + "graph.add_edge(3, 4)\n", + "```\n", + "\n", + "Result:\n", + "* search_path(start=0, end=2) -> [0, 1, 3, 2]\n", + "* search_path(start=0, end=0) -> [0]\n", + "* search_path(start=4, end=5) -> None" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "Refer to the [Solution Notebook](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/graph_path_exists/path_exists_solution.ipynb). 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": [ + "%run ../graph/graph.py\n", + "%load ../graph/graph.py" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class GraphShortestPath(Graph):\n", + "\n", + " def shortest_path(self, source_key, dest_key):\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_shortest_path.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestShortestPath(object):\n", + "\n", + " def test_shortest_path(self):\n", + " nodes = []\n", + " graph = GraphShortestPath()\n", + " for id in range(0, 6):\n", + " nodes.append(graph.add_node(id))\n", + " graph.add_edge(0, 1)\n", + " graph.add_edge(0, 4)\n", + " graph.add_edge(0, 5)\n", + " graph.add_edge(1, 3)\n", + " graph.add_edge(1, 4)\n", + " graph.add_edge(2, 1)\n", + " graph.add_edge(3, 2)\n", + " graph.add_edge(3, 4)\n", + "\n", + " assert_equal(graph.shortest_path(nodes[0].key, nodes[2].key), [0, 1, 3, 2])\n", + " assert_equal(graph.shortest_path(nodes[0].key, nodes[0].key), [0])\n", + " assert_equal(graph.shortest_path(nodes[4].key, nodes[5].key), None)\n", + "\n", + " print('Success: test_shortest_path')\n", + "\n", + "\n", + "def main():\n", + " test = TestShortestPath()\n", + " test.test_shortest_path()\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " main()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Solution Notebook\n", + "\n", + "Review the [Solution Notebook](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/graph_path_exists/path_exists_solution.ipynb) 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.4.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/graphs_trees/graph_shortest_path_unweighted/shortest_path_solution.ipynb b/graphs_trees/graph_shortest_path_unweighted/shortest_path_solution.ipynb new file mode 100644 index 0000000..1e1bc13 --- /dev/null +++ b/graphs_trees/graph_shortest_path_unweighted/shortest_path_solution.ipynb @@ -0,0 +1,274 @@ +{ + "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: Find the shortest path between two nodes in a graph.\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", + "* Is the graph directed?\n", + " * Yes\n", + "* Is the graph weighted?\n", + " * No\n", + "* Can we assume we already have Graph and Node classes?\n", + " * Yes\n", + "* Are the inputs two Nodes?\n", + " * Yes\n", + "* Is the output a list of Node keys that make up the shortest path?\n", + " * Yes\n", + "* If there is no path, should we return None?\n", + " * Yes\n", + "* Can we assume this is a connected graph?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "Input:\n", + "* `add_edge(source, destination, weight)`\n", + "\n", + "```\n", + "graph.add_edge(0, 1)\n", + "graph.add_edge(0, 4)\n", + "graph.add_edge(0, 5)\n", + "graph.add_edge(1, 3)\n", + "graph.add_edge(1, 4)\n", + "graph.add_edge(2, 1)\n", + "graph.add_edge(3, 2)\n", + "graph.add_edge(3, 4)\n", + "```\n", + "\n", + "Result:\n", + "* search_path(start=0, end=2) -> [0, 1, 3, 2]\n", + "* search_path(start=0, end=0) -> [0]\n", + "* search_path(start=4, end=5) -> None" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "To determine the shorted path in an unweighted graph, we can use breadth-first search keeping track of the previous nodes ids for each node. Previous nodes ids can be a dictionary of key: current node id and value: previous node id.\n", + "\n", + "* If the start node is the end node, return True\n", + "* Add the start node to the queue and mark it as visited\n", + " * Update the previous node ids, the previous node id of the start node is None\n", + "* While the queue is not empty\n", + " * Dequeue a node and visit it\n", + " * If the node is the end node, return the previous nodes\n", + " * Set the previous node to the current node\n", + " * Iterate through each adjacent node\n", + " * If the node has not been visited, add it to the queue and mark it as visited\n", + " * Update the previous node ids\n", + "* Return None\n", + "\n", + "Walk the previous node ids backwards to get the path.\n", + "\n", + "Complexity:\n", + "* Time: O(V + E), where V = number of vertices and E = number of edges\n", + "* Space: O(V + E)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%run ../graph/graph.py" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from collections import deque\n", + "\n", + "\n", + "class GraphShortestPath(Graph):\n", + "\n", + " def shortest_path(self, source_key, dest_key):\n", + " if source_key is None or dest_key is None:\n", + " return None\n", + " if source_key is dest_key:\n", + " return [source_key]\n", + " prev_node_keys = self._shortest_path(source_key, dest_key)\n", + " if prev_node_keys is None:\n", + " return None\n", + " else:\n", + " path_ids = [dest_key]\n", + " prev_node_key = prev_node_keys[dest_key]\n", + " while prev_node_key is not None:\n", + " path_ids.append(prev_node_key)\n", + " prev_node_key = prev_node_keys[prev_node_key]\n", + " return path_ids[::-1]\n", + "\n", + " def _shortest_path(self, source_key, dest_key):\n", + " queue = deque()\n", + " queue.append(self.nodes[source_key])\n", + " prev_node_keys = {source_key: None}\n", + " self.nodes[source_key].visit_state = State.visited\n", + " while queue:\n", + " node = queue.popleft()\n", + " if node.key is dest_key:\n", + " return prev_node_keys\n", + " prev_node = node\n", + " for adj_node in node.adj_nodes.values():\n", + " if adj_node.visit_state == State.unvisited:\n", + " queue.append(adj_node)\n", + " prev_node_keys[adj_node.key] = prev_node.key\n", + " adj_node.visit_state = State.visited\n", + " return None" + ] + }, + { + "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_shortest_path.py\n" + ] + } + ], + "source": [ + "%%writefile test_shortest_path.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestShortestPath(object):\n", + "\n", + " def test_shortest_path(self):\n", + " nodes = []\n", + " graph = GraphShortestPath()\n", + " for id in range(0, 6):\n", + " nodes.append(graph.add_node(id))\n", + " graph.add_edge(0, 1)\n", + " graph.add_edge(0, 4)\n", + " graph.add_edge(0, 5)\n", + " graph.add_edge(1, 3)\n", + " graph.add_edge(1, 4)\n", + " graph.add_edge(2, 1)\n", + " graph.add_edge(3, 2)\n", + " graph.add_edge(3, 4)\n", + "\n", + " assert_equal(graph.shortest_path(nodes[0].key, nodes[2].key), [0, 1, 3, 2])\n", + " assert_equal(graph.shortest_path(nodes[0].key, nodes[0].key), [0])\n", + " assert_equal(graph.shortest_path(nodes[4].key, nodes[5].key), None)\n", + "\n", + " print('Success: test_shortest_path')\n", + "\n", + "\n", + "def main():\n", + " test = TestShortestPath()\n", + " test.test_shortest_path()\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_shortest_path\n" + ] + } + ], + "source": [ + "%run -i test_shortest_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/graphs_trees/graph_shortest_path_unweighted/test_shortest_path.py b/graphs_trees/graph_shortest_path_unweighted/test_shortest_path.py new file mode 100644 index 0000000..83f61d4 --- /dev/null +++ b/graphs_trees/graph_shortest_path_unweighted/test_shortest_path.py @@ -0,0 +1,33 @@ +from nose.tools import assert_equal + + +class TestShortestPath(object): + + def test_shortest_path(self): + nodes = [] + graph = GraphShortestPath() + for id in range(0, 6): + nodes.append(graph.add_node(id)) + graph.add_edge(0, 1) + graph.add_edge(0, 4) + graph.add_edge(0, 5) + graph.add_edge(1, 3) + graph.add_edge(1, 4) + graph.add_edge(2, 1) + graph.add_edge(3, 2) + graph.add_edge(3, 4) + + assert_equal(graph.shortest_path(nodes[0].key, nodes[2].key), [0, 1, 3, 2]) + assert_equal(graph.shortest_path(nodes[0].key, nodes[0].key), [0]) + assert_equal(graph.shortest_path(nodes[4].key, nodes[5].key), None) + + print('Success: test_shortest_path') + + +def main(): + test = TestShortestPath() + test.test_shortest_path() + + +if __name__ == '__main__': + main() \ No newline at end of file From 3e0d78f685467e2a8d33f66ea08841ef84da177f Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Thu, 30 Mar 2017 05:40:46 -0400 Subject: [PATCH 51/90] Add graph shortest path challenge --- graphs_trees/graph_shortest_path/__init__.py | 0 .../graph_shortest_path_challenge.ipynb | 255 +++++++++++++ .../graph_shortest_path_solution.ipynb | 355 ++++++++++++++++++ .../graph_shortest_path/priority_queue.py | 41 ++ .../graph_shortest_path/test_shortest_path.py | 41 ++ 5 files changed, 692 insertions(+) create mode 100644 graphs_trees/graph_shortest_path/__init__.py create mode 100644 graphs_trees/graph_shortest_path/graph_shortest_path_challenge.ipynb create mode 100644 graphs_trees/graph_shortest_path/graph_shortest_path_solution.ipynb create mode 100644 graphs_trees/graph_shortest_path/priority_queue.py create mode 100644 graphs_trees/graph_shortest_path/test_shortest_path.py diff --git a/graphs_trees/graph_shortest_path/__init__.py b/graphs_trees/graph_shortest_path/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/graphs_trees/graph_shortest_path/graph_shortest_path_challenge.ipynb b/graphs_trees/graph_shortest_path/graph_shortest_path_challenge.ipynb new file mode 100644 index 0000000..517e509 --- /dev/null +++ b/graphs_trees/graph_shortest_path/graph_shortest_path_challenge.ipynb @@ -0,0 +1,255 @@ +{ + "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: Find the shortest path between two nodes in a graph.\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", + "* Is this a directional graph?\n", + " * Yes\n", + "* Could the graph have cycles?\n", + " * Yes\n", + " * Note: If the answer were no, this would be a DAG. \n", + " * DAGs can be solved with a [topological sort](http://www.geeksforgeeks.org/shortest-path-for-directed-acyclic-graphs/)\n", + "* Are the edges weighted?\n", + " * Yes\n", + " * Note: If the edges were not weighted, we could do a BFS\n", + "* Are the edges all non-negative numbers?\n", + " * Yes\n", + " * Note: Graphs with negative edges can be done with Bellman-Ford\n", + " * Graphs with negative cost cycles do not have a defined shortest path\n", + "* Do we have to check for non-negative edges?\n", + " * No\n", + "* Can we assume this is a connected graph?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * No\n", + "* Can we assume we already have a graph class?\n", + " * Yes\n", + "* Can we assume we already have a priority queue class?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "The constaints state we don't have to check for negative edges, so we test with the general case.\n", + "\n", + "
\n",
+    "graph.add_edge('a', 'b', weight=5)\n",
+    "graph.add_edge('a', 'c', weight=3)\n",
+    "graph.add_edge('a', 'e', weight=2)\n",
+    "graph.add_edge('b', 'd', weight=2)\n",
+    "graph.add_edge('c', 'b', weight=1)\n",
+    "graph.add_edge('c', 'd', weight=1)\n",
+    "graph.add_edge('d', 'a', weight=1)\n",
+    "graph.add_edge('d', 'g', weight=2)\n",
+    "graph.add_edge('d', 'h', weight=1)\n",
+    "graph.add_edge('e', 'a', weight=1)\n",
+    "graph.add_edge('e', 'h', weight=4)\n",
+    "graph.add_edge('e', 'i', weight=7)\n",
+    "graph.add_edge('f', 'b', weight=3)\n",
+    "graph.add_edge('f', 'g', weight=1)\n",
+    "graph.add_edge('g', 'c', weight=3)\n",
+    "graph.add_edge('g', 'i', weight=2)\n",
+    "graph.add_edge('h', 'c', weight=2)\n",
+    "graph.add_edge('h', 'f', weight=2)\n",
+    "graph.add_edge('h', 'g', weight=2)\n",
+    "shortest_path = ShortestPath(graph)\n",
+    "result = shortest_path.find_shortest_path('a', 'i')\n",
+    "assert_equal(result, ['a', 'c', 'd', 'g', 'i'])\n",
+    "assert_equal(shortest_path.path_weight['i'], 8)\n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "Refer to the [Solution Notebook](https://github.com/donnemartin/interactive-coding-challenges/graphs_trees/graph_shortest_path/graph_shortest_path_solution.ipynb). 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": [ + "%run ../../arrays_strings/priority_queue/priority_queue.py\n", + "%load ../../arrays_strings/priority_queue/priority_queue.py" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%run ../graph/graph.py\n", + "%load ../graph/graph.py" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class ShortestPath(object):\n", + "\n", + " def __init__(self, graph):\n", + " # TODO: Implement me\n", + " pass\n", + "\n", + " def find_shortest_path(self, start_node_key, end_node_key):\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_shortest_path.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestShortestPath(object):\n", + "\n", + " def test_shortest_path(self):\n", + " graph = Graph()\n", + " graph.add_edge('a', 'b', weight=5)\n", + " graph.add_edge('a', 'c', weight=3)\n", + " graph.add_edge('a', 'e', weight=2)\n", + " graph.add_edge('b', 'd', weight=2)\n", + " graph.add_edge('c', 'b', weight=1)\n", + " graph.add_edge('c', 'd', weight=1)\n", + " graph.add_edge('d', 'a', weight=1)\n", + " graph.add_edge('d', 'g', weight=2)\n", + " graph.add_edge('d', 'h', weight=1)\n", + " graph.add_edge('e', 'a', weight=1)\n", + " graph.add_edge('e', 'h', weight=4)\n", + " graph.add_edge('e', 'i', weight=7)\n", + " graph.add_edge('f', 'b', weight=3)\n", + " graph.add_edge('f', 'g', weight=1)\n", + " graph.add_edge('g', 'c', weight=3)\n", + " graph.add_edge('g', 'i', weight=2)\n", + " graph.add_edge('h', 'c', weight=2)\n", + " graph.add_edge('h', 'f', weight=2)\n", + " graph.add_edge('h', 'g', weight=2)\n", + " shortest_path = ShortestPath(graph)\n", + " result = shortest_path.find_shortest_path('a', 'i')\n", + " assert_equal(result, ['a', 'c', 'd', 'g', 'i'])\n", + " assert_equal(shortest_path.path_weight['i'], 8)\n", + "\n", + " print('Success: test_shortest_path')\n", + "\n", + "\n", + "def main():\n", + " test = TestShortestPath()\n", + " test.test_shortest_path()\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " main()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Solution Notebook\n", + "\n", + "Review the [Solution Notebook](https://github.com/donnemartin/interactive-coding-challenges/graphs_trees/graph_shortest_path/graph_shortest_path_solution.ipynb) 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.4.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/graphs_trees/graph_shortest_path/graph_shortest_path_solution.ipynb b/graphs_trees/graph_shortest_path/graph_shortest_path_solution.ipynb new file mode 100644 index 0000000..abf68c0 --- /dev/null +++ b/graphs_trees/graph_shortest_path/graph_shortest_path_solution.ipynb @@ -0,0 +1,355 @@ +{ + "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: Find the shortest path between two nodes in a graph.\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", + "* Is this a directional graph?\n", + " * Yes\n", + "* Could the graph have cycles?\n", + " * Yes\n", + " * Note: If the answer were no, this would be a DAG. \n", + " * DAGs can be solved with a [topological sort](http://www.geeksforgeeks.org/shortest-path-for-directed-acyclic-graphs/)\n", + "* Are the edges weighted?\n", + " * Yes\n", + " * Note: If the edges were not weighted, we could do a BFS\n", + "* Are the edges all non-negative numbers?\n", + " * Yes\n", + " * Note: Graphs with negative edges can be done with Bellman-Ford\n", + " * Graphs with negative cost cycles do not have a defined shortest path\n", + "* Do we have to check for non-negative edges?\n", + " * No\n", + "* Can we assume this is a connected graph?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * No\n", + "* Can we assume we already have a graph class?\n", + " * Yes\n", + "* Can we assume we already have a priority queue class?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "The constaints state we don't have to check for negative edges, so we test with the general case.\n", + "\n", + "
\n",
+    "graph.add_edge('a', 'b', weight=5)\n",
+    "graph.add_edge('a', 'c', weight=3)\n",
+    "graph.add_edge('a', 'e', weight=2)\n",
+    "graph.add_edge('b', 'd', weight=2)\n",
+    "graph.add_edge('c', 'b', weight=1)\n",
+    "graph.add_edge('c', 'd', weight=1)\n",
+    "graph.add_edge('d', 'a', weight=1)\n",
+    "graph.add_edge('d', 'g', weight=2)\n",
+    "graph.add_edge('d', 'h', weight=1)\n",
+    "graph.add_edge('e', 'a', weight=1)\n",
+    "graph.add_edge('e', 'h', weight=4)\n",
+    "graph.add_edge('e', 'i', weight=7)\n",
+    "graph.add_edge('f', 'b', weight=3)\n",
+    "graph.add_edge('f', 'g', weight=1)\n",
+    "graph.add_edge('g', 'c', weight=3)\n",
+    "graph.add_edge('g', 'i', weight=2)\n",
+    "graph.add_edge('h', 'c', weight=2)\n",
+    "graph.add_edge('h', 'f', weight=2)\n",
+    "graph.add_edge('h', 'g', weight=2)\n",
+    "shortest_path = ShortestPath(graph)\n",
+    "result = shortest_path.find_shortest_path('a', 'i')\n",
+    "assert_equal(result, ['a', 'c', 'd', 'g', 'i'])\n",
+    "assert_equal(shortest_path.path_weight['i'], 8)\n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "Wikipedia's animation:\n", + "\n", + "![](https://upload.wikimedia.org/wikipedia/commons/5/57/Dijkstra_Animation.gif)\n", + "\n", + "Initialize the following:\n", + "\n", + "* previous = {} # Key: node key, val: prev node key, shortest path\n", + " * Set each node's previous node key to None\n", + "* path_weight = {} # Key: node key, val: weight, shortest path\n", + " * Set each node's shortest path weight to infinity\n", + "* remaining = PriorityQueue() # Queue of node key, path weight\n", + " * Add each node's shortest path weight to the priority queue\n", + "\n", + "* Set the start node's path_weight to 0 and update the value in remaining\n", + "* Loop while remaining still has items\n", + " * Extract the min node (node with minimum path weight) from remaining\n", + " * Loop through each adjacent node in the min node\n", + " * Calculate the new weight:\n", + " * Adjacent node's edge weight + the min node's path_weight \n", + " * If the newly calculated path is less than the adjacent node's current path_weight:\n", + " * Set the node's previous node key leading to the shortest path\n", + " * Update the adjacent node's shortest path and update the value in the priority queue\n", + "* Walk backwards to determine the shortest path:\n", + " * Start at the end node, walk the previous dict to get to the start node\n", + "* Reverse the list and return it\n", + "\n", + "### Complexity for array-based priority queue:\n", + "\n", + "* Time: O(v^2), where v is the number of vertices\n", + "* Space: O(v^2)\n", + "\n", + "This might be better than the min-heap-based variant if the graph has a lot of edges.\n", + "\n", + "O(v^2) is better than O((v + v^2) log v).\n", + "\n", + "### Complexity for min-heap-based priority queue:\n", + "\n", + "* Time: O((v + e) log v), where v is the number of vertices, e is the number of edges\n", + "* Space: O((v + e) log v)\n", + "\n", + "This might be better than the array-based variant if the graph is sparse." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%run ../../arrays_strings/priority_queue/priority_queue.py" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%run ../graph/graph.py" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\n", + "\n", + "\n", + "class ShortestPath(object):\n", + "\n", + " def __init__(self, graph):\n", + " if graph is None:\n", + " raise TypeError('graph cannot be None')\n", + " self.graph = graph\n", + " self.previous = {} # Key: node key, val: prev node key, shortest path\n", + " self.path_weight = {} # Key: node key, val: weight, shortest path\n", + " self.remaining = PriorityQueue() # Queue of node key, path weight\n", + " for key in self.graph.nodes.keys():\n", + " # Set each node's previous node key to None\n", + " # Set each node's shortest path weight to infinity\n", + " # Add each node's shortest path weight to the priority queue\n", + " self.previous[key] = None\n", + " self.path_weight[key] = sys.maxsize\n", + " self.remaining.insert(\n", + " PriorityQueueNode(key, self.path_weight[key]))\n", + "\n", + " def find_shortest_path(self, start_node_key, end_node_key):\n", + " if start_node_key is None or end_node_key is None:\n", + " raise TypeError('Input node keys cannot be None')\n", + " if (start_node_key not in self.graph.nodes or\n", + " end_node_key not in self.graph.nodes):\n", + " raise ValueError('Invalid start or end node key')\n", + " # Set the start node's shortest path weight to 0\n", + " # and update the value in the priority queue\n", + " self.path_weight[start_node_key] = 0\n", + " self.remaining.decrease_key(start_node_key, 0)\n", + " while self.remaining:\n", + " # Extract the min node (node with minimum path weight)\n", + " # from the priority queue\n", + " min_node_key = self.remaining.extract_min().obj\n", + " min_node = self.graph.nodes[min_node_key]\n", + " # Loop through each adjacent node in the min node\n", + " for adj_key in min_node.adj_nodes.keys():\n", + " # Node's path:\n", + " # Adjacent node's edge weight + the min node's\n", + " # shortest path weight\n", + " new_weight = (min_node.adj_weights[adj_key] +\n", + " self.path_weight[min_node_key])\n", + " # Only update if the newly calculated path is\n", + " # less than the existing node's shortest path\n", + " if self.path_weight[adj_key] > new_weight:\n", + " # Set the node's previous node key leading to the shortest path\n", + " # Update the adjacent node's shortest path and\n", + " # update the value in the priority queue\n", + " self.previous[adj_key] = min_node_key\n", + " self.path_weight[adj_key] = new_weight\n", + " self.remaining.decrease_key(adj_key, new_weight)\n", + " # Walk backwards to determine the shortest path:\n", + " # Start at the end node, walk the previous dict to get to the start node\n", + " result = []\n", + " current_node_key = end_node_key\n", + " while current_node_key is not None:\n", + " result.append(current_node_key)\n", + " current_node_key = self.previous[current_node_key]\n", + " # Reverse the list\n", + " return result[::-1]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Unit Test" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting test_shortest_path.py\n" + ] + } + ], + "source": [ + "%%writefile test_shortest_path.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestShortestPath(object):\n", + "\n", + " def test_shortest_path(self):\n", + " graph = Graph()\n", + " graph.add_edge('a', 'b', weight=5)\n", + " graph.add_edge('a', 'c', weight=3)\n", + " graph.add_edge('a', 'e', weight=2)\n", + " graph.add_edge('b', 'd', weight=2)\n", + " graph.add_edge('c', 'b', weight=1)\n", + " graph.add_edge('c', 'd', weight=1)\n", + " graph.add_edge('d', 'a', weight=1)\n", + " graph.add_edge('d', 'g', weight=2)\n", + " graph.add_edge('d', 'h', weight=1)\n", + " graph.add_edge('e', 'a', weight=1)\n", + " graph.add_edge('e', 'h', weight=4)\n", + " graph.add_edge('e', 'i', weight=7)\n", + " graph.add_edge('f', 'b', weight=3)\n", + " graph.add_edge('f', 'g', weight=1)\n", + " graph.add_edge('g', 'c', weight=3)\n", + " graph.add_edge('g', 'i', weight=2)\n", + " graph.add_edge('h', 'c', weight=2)\n", + " graph.add_edge('h', 'f', weight=2)\n", + " graph.add_edge('h', 'g', weight=2)\n", + " shortest_path = ShortestPath(graph)\n", + " result = shortest_path.find_shortest_path('a', 'i')\n", + " assert_equal(result, ['a', 'c', 'd', 'g', 'i'])\n", + " assert_equal(shortest_path.path_weight['i'], 8)\n", + "\n", + " print('Success: test_shortest_path')\n", + "\n", + "\n", + "def main():\n", + " test = TestShortestPath()\n", + " test.test_shortest_path()\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " main()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Success: test_shortest_path\n" + ] + } + ], + "source": [ + "%run -i test_shortest_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.4.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/graphs_trees/graph_shortest_path/priority_queue.py b/graphs_trees/graph_shortest_path/priority_queue.py new file mode 100644 index 0000000..27a8cc3 --- /dev/null +++ b/graphs_trees/graph_shortest_path/priority_queue.py @@ -0,0 +1,41 @@ +import sys + + +class PriorityQueueNode(object): + + def __init__(self, obj, key): + self.obj = obj + self.key = key + + def __repr__(self): + return str(self.obj) + ': ' + str(self.key) + + +class PriorityQueue(object): + + def __init__(self): + self.queue = [] + + def insert(self, node): + if node is not None: + self.queue.append(node) + return self.queue[-1] + return None + + def extract_min(self): + if not self.queue: + return None + minimum = sys.maxsize + for index, node in enumerate(self.queue): + if node.key < minimum: + minimum = node.key + minimum_index = index + node = self.queue.pop(minimum_index) + return node.obj + + def decrease_key(self, obj, new_key): + for node in self.queue: + if node.obj is obj: + node.key = new_key + return node + return None \ No newline at end of file diff --git a/graphs_trees/graph_shortest_path/test_shortest_path.py b/graphs_trees/graph_shortest_path/test_shortest_path.py new file mode 100644 index 0000000..237783b --- /dev/null +++ b/graphs_trees/graph_shortest_path/test_shortest_path.py @@ -0,0 +1,41 @@ +from nose.tools import assert_equal + + +class TestShortestPath(object): + + def test_shortest_path(self): + graph = Graph() + graph.add_edge('a', 'b', weight=5) + graph.add_edge('a', 'c', weight=3) + graph.add_edge('a', 'e', weight=2) + graph.add_edge('b', 'd', weight=2) + graph.add_edge('c', 'b', weight=1) + graph.add_edge('c', 'd', weight=1) + graph.add_edge('d', 'a', weight=1) + graph.add_edge('d', 'g', weight=2) + graph.add_edge('d', 'h', weight=1) + graph.add_edge('e', 'a', weight=1) + graph.add_edge('e', 'h', weight=4) + graph.add_edge('e', 'i', weight=7) + graph.add_edge('f', 'b', weight=3) + graph.add_edge('f', 'g', weight=1) + graph.add_edge('g', 'c', weight=3) + graph.add_edge('g', 'i', weight=2) + graph.add_edge('h', 'c', weight=2) + graph.add_edge('h', 'f', weight=2) + graph.add_edge('h', 'g', weight=2) + shortest_path = ShortestPath(graph) + result = shortest_path.find_shortest_path('a', 'i') + assert_equal(result, ['a', 'c', 'd', 'g', 'i']) + assert_equal(shortest_path.path_weight['i'], 8) + + print('Success: test_shortest_path') + + +def main(): + test = TestShortestPath() + test.test_shortest_path() + + +if __name__ == '__main__': + main() \ No newline at end of file From 3051c877e5a126cab5691ca148b23b596fcc0262 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Thu, 30 Mar 2017 05:41:06 -0400 Subject: [PATCH 52/90] Add graph build order challenge --- graphs_trees/graph_build_order/__init__.py | 0 .../build_order_challenge.ipynb | 242 ++++++++++++++ .../build_order_solution.ipynb | 310 ++++++++++++++++++ .../graph_build_order/test_build_order.py | 51 +++ 4 files changed, 603 insertions(+) create mode 100644 graphs_trees/graph_build_order/__init__.py create mode 100644 graphs_trees/graph_build_order/build_order_challenge.ipynb create mode 100644 graphs_trees/graph_build_order/build_order_solution.ipynb create mode 100644 graphs_trees/graph_build_order/test_build_order.py diff --git a/graphs_trees/graph_build_order/__init__.py b/graphs_trees/graph_build_order/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/graphs_trees/graph_build_order/build_order_challenge.ipynb b/graphs_trees/graph_build_order/build_order_challenge.ipynb new file mode 100644 index 0000000..7d7bb49 --- /dev/null +++ b/graphs_trees/graph_build_order/build_order_challenge.ipynb @@ -0,0 +1,242 @@ +{ + "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: Find a build order given a list of projects and dependencies.\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", + "* Is it possible to have a cyclic graph as the input?\n", + " * Yes\n", + "* Can we assume we already have Graph and Node classes?\n", + " * Yes\n", + "* Can we assume this is a connected graph?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* projects: a, b, c, d, e, f, g\n", + "* dependencies: (d, g), (f, c), (f, b), (f, a), (c, a), (b, a), (a, e), (b, e)\n", + "* output: d, f, c, b, g, a, e\n", + "\n", + "Note: Edge direction is down\n", + "
\n",
+    "    f     d\n",
+    "   /|\\    |\n",
+    "  c | b   g\n",
+    "   \\|/|\n",
+    "    a |\n",
+    "    |/\n",
+    "    e\n",
+    "
\n", + "\n", + "Test a graph with a cycle, output should be None" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "Refer to the [Solution Notebook](https://github.com/donnemartin/interactive-coding-challenges/graphs_trees/build_order/build_order_solution.ipynb). 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 Dependency(object):\n", + "\n", + " def __init__(self, node_key_before, node_key_after):\n", + " self.node_key_before = node_key_before\n", + " self.node_key_after = node_key_after" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%run ../graph/graph.py\n", + "%load ../graph/graph.py" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class BuildOrder(object):\n", + "\n", + " def __init__(self, dependencies):\n", + " # TODO: Implement me\n", + " pass\n", + "\n", + " def find_build_order(self):\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_build_order.py\n", + "from nose.tools import assert_equal\n", + "from nose.tools import assert_true\n", + "\n", + "\n", + "class TestBuildOrder(object):\n", + "\n", + " def __init__(self):\n", + " self.dependencies = [\n", + " Dependency('d', 'g'),\n", + " Dependency('f', 'c'),\n", + " Dependency('f', 'b'),\n", + " Dependency('f', 'a'),\n", + " Dependency('c', 'a'),\n", + " Dependency('b', 'a'),\n", + " Dependency('a', 'e'),\n", + " Dependency('b', 'e'),\n", + " ]\n", + "\n", + " def test_build_order(self):\n", + " build_order = BuildOrder(self.dependencies)\n", + " processed_nodes = build_order.find_build_order()\n", + "\n", + " expected_result0 = ('d', 'f')\n", + " expected_result1 = ('c', 'b', 'g')\n", + " assert_true(processed_nodes[0].key in expected_result0)\n", + " assert_true(processed_nodes[1].key in expected_result0)\n", + " assert_true(processed_nodes[2].key in expected_result1)\n", + " assert_true(processed_nodes[3].key in expected_result1)\n", + " assert_true(processed_nodes[4].key in expected_result1)\n", + " assert_true(processed_nodes[5].key is 'a')\n", + " assert_true(processed_nodes[6].key is 'e')\n", + "\n", + " print('Success: test_build_order')\n", + "\n", + " def test_build_order_circular(self):\n", + " self.dependencies.append(Dependency('e', 'f'))\n", + " build_order = BuildOrder(self.dependencies)\n", + " processed_nodes = build_order.find_build_order()\n", + " assert_true(processed_nodes is None)\n", + "\n", + " print('Success: test_build_order_circular')\n", + "\n", + "\n", + "def main():\n", + " test = TestBuildOrder()\n", + " test.test_build_order()\n", + " test.test_build_order_circular()\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " main()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Solution Notebook\n", + "\n", + "Review the [Solution Notebook](https://github.com/donnemartin/interactive-coding-challenges/graphs_trees/build_order/build_order_solution.ipynb) 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.4.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/graphs_trees/graph_build_order/build_order_solution.ipynb b/graphs_trees/graph_build_order/build_order_solution.ipynb new file mode 100644 index 0000000..90f2087 --- /dev/null +++ b/graphs_trees/graph_build_order/build_order_solution.ipynb @@ -0,0 +1,310 @@ +{ + "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: Find a build order given a list of projects and dependencies.\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", + "* Is it possible to have a cyclic graph as the input?\n", + " * Yes\n", + "* Can we assume we already have Graph and Node classes?\n", + " * Yes\n", + "* Can we assume this is a connected graph?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* projects: a, b, c, d, e, f, g\n", + "* dependencies: (d, g), (f, c), (f, b), (f, a), (c, a), (b, a), (a, e), (b, e)\n", + "* output: d, f, c, b, g, a, e\n", + "\n", + "Note: Edge direction is down\n", + "
\n",
+    "    f     d\n",
+    "   /|\\    |\n",
+    "  c | b   g\n",
+    "   \\|/|\n",
+    "    a |\n",
+    "    |/\n",
+    "    e\n",
+    "
\n", + "\n", + "Test a graph with a cycle, output should be None" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "We can determine the build order using a topological sort.\n", + "\n", + "* Build the graph with projects (nodes) and dependencies (directed edges)\n", + "* Add initially non-dependent nodes to processed_nodes\n", + " * If none exist, we have a circular dependency, return None\n", + "* While the length processed_nodes < the length of graph nodes\n", + " * Remove outgoing edges from newly added items in processed_nodes\n", + " * Add non-dependent nodes to processed_nodes\n", + " * If we didn't add any nodes, we have a circular dependency, return None\n", + "* Return processed_nodes\n", + "\n", + "Complexity:\n", + "* Time: O(V + E)\n", + "* Space: O(V + E)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from collections import deque\n", + "\n", + "\n", + "class Dependency(object):\n", + "\n", + " def __init__(self, node_key_before, node_key_after):\n", + " self.node_key_before = node_key_before\n", + " self.node_key_after = node_key_after" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%run ../graph/graph.py" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class BuildOrder(object):\n", + "\n", + " def __init__(self, dependencies):\n", + " self.dependencies = dependencies\n", + " self.graph = Graph()\n", + " self._build_graph()\n", + "\n", + " def _build_graph(self):\n", + " for dependency in self.dependencies:\n", + " self.graph.add_edge(dependency.node_key_before,\n", + " dependency.node_key_after)\n", + "\n", + " def _find_start_nodes(self, processed_nodes):\n", + " nodes_to_process = {}\n", + " for key, node in self.graph.nodes.items():\n", + " if node.incoming_edges == 0 and key not in processed_nodes:\n", + " nodes_to_process[key] = node\n", + " return nodes_to_process\n", + "\n", + " def _process_nodes(self, nodes_to_process, processed_nodes):\n", + " for node in nodes_to_process.values():\n", + " # We'll need to iterate on copies since we'll need\n", + " # to change the dictionaries during iteration with\n", + " # the remove_neighbor call\n", + " for adj_node in list(node.adj_nodes.values()):\n", + " node.remove_neighbor(adj_node)\n", + " processed_nodes[node.key] = node\n", + " nodes_to_process = {}\n", + "\n", + " def find_build_order(self):\n", + " result = []\n", + " nodes_to_process = {}\n", + " processed_nodes = {}\n", + " while len(result) != len(self.graph.nodes):\n", + " nodes_to_process = self._find_start_nodes(processed_nodes)\n", + " if not nodes_to_process:\n", + " return None\n", + " result.extend(nodes_to_process.values())\n", + " self._process_nodes(nodes_to_process, processed_nodes)\n", + " return result" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Unit Test" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%run ../utils/results.py" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting test_build_order.py\n" + ] + } + ], + "source": [ + "%%writefile test_build_order.py\n", + "from nose.tools import assert_equal\n", + "from nose.tools import assert_true\n", + "\n", + "\n", + "class TestBuildOrder(object):\n", + "\n", + " def __init__(self):\n", + " self.dependencies = [\n", + " Dependency('d', 'g'),\n", + " Dependency('f', 'c'),\n", + " Dependency('f', 'b'),\n", + " Dependency('f', 'a'),\n", + " Dependency('c', 'a'),\n", + " Dependency('b', 'a'),\n", + " Dependency('a', 'e'),\n", + " Dependency('b', 'e'),\n", + " ]\n", + "\n", + " def test_build_order(self):\n", + " build_order = BuildOrder(self.dependencies)\n", + " processed_nodes = build_order.find_build_order()\n", + "\n", + " expected_result0 = ('d', 'f')\n", + " expected_result1 = ('c', 'b', 'g')\n", + " assert_true(processed_nodes[0].key in expected_result0)\n", + " assert_true(processed_nodes[1].key in expected_result0)\n", + " assert_true(processed_nodes[2].key in expected_result1)\n", + " assert_true(processed_nodes[3].key in expected_result1)\n", + " assert_true(processed_nodes[4].key in expected_result1)\n", + " assert_true(processed_nodes[5].key is 'a')\n", + " assert_true(processed_nodes[6].key is 'e')\n", + "\n", + " print('Success: test_build_order')\n", + "\n", + " def test_build_order_circular(self):\n", + " self.dependencies.append(Dependency('e', 'f'))\n", + " build_order = BuildOrder(self.dependencies)\n", + " processed_nodes = build_order.find_build_order()\n", + " assert_true(processed_nodes is None)\n", + "\n", + " print('Success: test_build_order_circular')\n", + "\n", + "\n", + "def main():\n", + " test = TestBuildOrder()\n", + " test.test_build_order()\n", + " test.test_build_order_circular()\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " main()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Success: test_build_order\n", + "Success: test_build_order_circular\n" + ] + } + ], + "source": [ + "%run -i test_build_order.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/graphs_trees/graph_build_order/test_build_order.py b/graphs_trees/graph_build_order/test_build_order.py new file mode 100644 index 0000000..0671951 --- /dev/null +++ b/graphs_trees/graph_build_order/test_build_order.py @@ -0,0 +1,51 @@ +from nose.tools import assert_equal +from nose.tools import assert_true + + +class TestBuildOrder(object): + + def __init__(self): + self.dependencies = [ + Dependency('d', 'g'), + Dependency('f', 'c'), + Dependency('f', 'b'), + Dependency('f', 'a'), + Dependency('c', 'a'), + Dependency('b', 'a'), + Dependency('a', 'e'), + Dependency('b', 'e'), + ] + + def test_build_order(self): + build_order = BuildOrder(self.dependencies) + processed_nodes = build_order.find_build_order() + + expected_result0 = ('d', 'f') + expected_result1 = ('c', 'b', 'g') + assert_true(processed_nodes[0].key in expected_result0) + assert_true(processed_nodes[1].key in expected_result0) + assert_true(processed_nodes[2].key in expected_result1) + assert_true(processed_nodes[3].key in expected_result1) + assert_true(processed_nodes[4].key in expected_result1) + assert_true(processed_nodes[5].key is 'a') + assert_true(processed_nodes[6].key is 'e') + + print('Success: test_build_order') + + def test_build_order_circular(self): + self.dependencies.append(Dependency('e', 'f')) + build_order = BuildOrder(self.dependencies) + processed_nodes = build_order.find_build_order() + assert_true(processed_nodes is None) + + print('Success: test_build_order_circular') + + +def main(): + test = TestBuildOrder() + test.test_build_order() + test.test_build_order_circular() + + +if __name__ == '__main__': + main() \ No newline at end of file From 6c507fc3ea9e318726a33bce2bbbad79cadd3f43 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Thu, 30 Mar 2017 05:41:29 -0400 Subject: [PATCH 53/90] Add bst second largest challenge --- graphs_trees/bst_second_largest/__init__.py | 0 .../bst_second_largest_challenge.ipynb | 212 ++++++++++++++ .../bst_second_largest_solution.ipynb | 271 ++++++++++++++++++ .../test_bst_second_largest.py | 36 +++ 4 files changed, 519 insertions(+) create mode 100644 graphs_trees/bst_second_largest/__init__.py create mode 100644 graphs_trees/bst_second_largest/bst_second_largest_challenge.ipynb create mode 100644 graphs_trees/bst_second_largest/bst_second_largest_solution.ipynb create mode 100644 graphs_trees/bst_second_largest/test_bst_second_largest.py diff --git a/graphs_trees/bst_second_largest/__init__.py b/graphs_trees/bst_second_largest/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/graphs_trees/bst_second_largest/bst_second_largest_challenge.ipynb b/graphs_trees/bst_second_largest/bst_second_largest_challenge.ipynb new file mode 100644 index 0000000..0ebb78c --- /dev/null +++ b/graphs_trees/bst_second_largest/bst_second_largest_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: Find the second largest node in a binary search tree.\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 this is called on a None input or a single node, should we raise an exception?\n", + " * Yes\n", + " * None -> TypeError\n", + " * Single node -> ValueError\n", + "* Can we assume we already have a Node class with an insert method?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None or single node -> Exception\n", + "\n", + "
\n",
+    "Input:\n",
+    "                _10_\n",
+    "              _/    \\_          \n",
+    "             5        15\n",
+    "            / \\       / \\\n",
+    "           3   8     12  20\n",
+    "          /     \\         \\\n",
+    "         2       4        30\n",
+    "\n",
+    "Output: 20\n",
+    "\n",
+    "Input:\n",
+    "                 10\n",
+    "                 /  \n",
+    "                5\n",
+    "               / \\\n",
+    "              3   7\n",
+    "Output: 7\n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "Refer to the [Solution Notebook](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/check_balance/check_balance_solution.ipynb). 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": [ + "%run ../bst/bst.py\n", + "%load ../bst/bst.py" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(Bst):\n", + "\n", + " def find_second_largest(self):\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_bst_second_largest.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestBstSecondLargest(object):\n", + "\n", + " def test_bst_second_largest(self):\n", + " bst = Solution(None)\n", + " assert_raises(TypeError, bst.find_second_largest)\n", + " root = Node(10)\n", + " bst = Solution(root)\n", + " node5 = bst.insert(5)\n", + " node15 = bst.insert(15)\n", + " node3 = bst.insert(3)\n", + " node8 = bst.insert(8)\n", + " node12 = bst.insert(12)\n", + " node20 = bst.insert(20)\n", + " node2 = bst.insert(2)\n", + " node4 = bst.insert(4)\n", + " node30 = bst.insert(30)\n", + " assert_equal(bst.find_second_largest(), node20)\n", + " root = Node(10)\n", + " bst = Solution(root)\n", + " node5 = bst.insert(5)\n", + " node3 = bst.insert(3)\n", + " node7 = bst.insert(7)\n", + " assert_equal(bst.find_second_largest(), node7)\n", + " print('Success: test_bst_second_largest')\n", + "\n", + "\n", + "def main():\n", + " test = TestBstSecondLargest()\n", + " test.test_bst_second_largest()\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " main()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Solution Notebook\n", + "\n", + "Review the [Solution Notebook](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/check_balance/check_balance_solution.ipynb) 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/graphs_trees/bst_second_largest/bst_second_largest_solution.ipynb b/graphs_trees/bst_second_largest/bst_second_largest_solution.ipynb new file mode 100644 index 0000000..6da1202 --- /dev/null +++ b/graphs_trees/bst_second_largest/bst_second_largest_solution.ipynb @@ -0,0 +1,271 @@ +{ + "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: Find the second largest node in a binary search tree.\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 this is called on a None input or a single node, should we raise an exception?\n", + " * Yes\n", + " * None -> TypeError\n", + " * Single node -> ValueError\n", + "* Can we assume we already have a Node class with an insert method?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None or single node -> Exception\n", + "\n", + "
\n",
+    "Input:\n",
+    "                _10_\n",
+    "              _/    \\_          \n",
+    "             5        15\n",
+    "            / \\       / \\\n",
+    "           3   8     12  20\n",
+    "          /     \\         \\\n",
+    "         2       4        30\n",
+    "\n",
+    "Output: 20\n",
+    "\n",
+    "Input:\n",
+    "                 10\n",
+    "                 /  \n",
+    "                5\n",
+    "               / \\\n",
+    "              3   7\n",
+    "Output: 7\n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "
\n",
+    "\n",
+    "If there is no right node, the second largest is the right most left subtree:\n",
+    "\n",
+    "                 10\n",
+    "                 /  \n",
+    "                5\n",
+    "               / \\\n",
+    "              3   7\n",
+    "\n",
+    "If there is a right node and the right node has children, recurse to that right child:\n",
+    "\n",
+    "                _10_\n",
+    "              _/    \\_          \n",
+    "             5        15\n",
+    "            / \\       / \\\n",
+    "           3   8     12  20\n",
+    "          /     \\         \\\n",
+    "         2       4        30\n",
+    "\n",
+    "Eventually we'll get to the following scenario:\n",
+    "\n",
+    "                 20\n",
+    "                  \\\n",
+    "                   30\n",
+    "\n",
+    "If the right node has no children, the second largest is the current node.\n",
+    "\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(h)\n", + "* Space: O(h), where h is the height of the tree" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%run ../bst/bst.py" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(Bst):\n", + "\n", + " def _find_second_largest(self, node):\n", + " if node.right is not None:\n", + " if node.right.left is not None or node.right.right is not None:\n", + " return self._find_second_largest(node.right)\n", + " else:\n", + " return node\n", + " else:\n", + " return self._find_right_most_node(node.left)\n", + "\n", + " def _find_right_most_node(self, node):\n", + " if node.right is not None:\n", + " return self._find_right_most_node(node.right)\n", + " else:\n", + " return node\n", + "\n", + " def find_second_largest(self):\n", + " if self.root is None:\n", + " raise TypeError('root cannot be None')\n", + " if self.root.right is None and self.root.left is None:\n", + " raise ValueError('root must have at least one child')\n", + " return self._find_second_largest(self.root)" + ] + }, + { + "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_bst_second_largest.py\n" + ] + } + ], + "source": [ + "%%writefile test_bst_second_largest.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestBstSecondLargest(object):\n", + "\n", + " def test_bst_second_largest(self):\n", + " bst = Solution(None)\n", + " assert_raises(TypeError, bst.find_second_largest)\n", + " root = Node(10)\n", + " bst = Solution(root)\n", + " node5 = bst.insert(5)\n", + " node15 = bst.insert(15)\n", + " node3 = bst.insert(3)\n", + " node8 = bst.insert(8)\n", + " node12 = bst.insert(12)\n", + " node20 = bst.insert(20)\n", + " node2 = bst.insert(2)\n", + " node4 = bst.insert(4)\n", + " node30 = bst.insert(30)\n", + " assert_equal(bst.find_second_largest(), node20)\n", + " root = Node(10)\n", + " bst = Solution(root)\n", + " node5 = bst.insert(5)\n", + " node3 = bst.insert(3)\n", + " node7 = bst.insert(7)\n", + " assert_equal(bst.find_second_largest(), node7)\n", + " print('Success: test_bst_second_largest')\n", + "\n", + "\n", + "def main():\n", + " test = TestBstSecondLargest()\n", + " test.test_bst_second_largest()\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_bst_second_largest\n" + ] + } + ], + "source": [ + "%run -i test_bst_second_largest.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/graphs_trees/bst_second_largest/test_bst_second_largest.py b/graphs_trees/bst_second_largest/test_bst_second_largest.py new file mode 100644 index 0000000..a5a67db --- /dev/null +++ b/graphs_trees/bst_second_largest/test_bst_second_largest.py @@ -0,0 +1,36 @@ +from nose.tools import assert_equal, assert_raises + + +class TestBstSecondLargest(object): + + def test_bst_second_largest(self): + bst = Solution(None) + assert_raises(TypeError, bst.find_second_largest) + root = Node(10) + bst = Solution(root) + node5 = bst.insert(5) + node15 = bst.insert(15) + node3 = bst.insert(3) + node8 = bst.insert(8) + node12 = bst.insert(12) + node20 = bst.insert(20) + node2 = bst.insert(2) + node4 = bst.insert(4) + node30 = bst.insert(30) + assert_equal(bst.find_second_largest(), node20) + root = Node(10) + bst = Solution(root) + node5 = bst.insert(5) + node3 = bst.insert(3) + node7 = bst.insert(7) + assert_equal(bst.find_second_largest(), node7) + print('Success: test_bst_second_largest') + + +def main(): + test = TestBstSecondLargest() + test.test_bst_second_largest() + + +if __name__ == '__main__': + main() \ No newline at end of file From 1112520784e38d0cf29440755aa9932e2348ef27 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Thu, 30 Mar 2017 05:42:09 -0400 Subject: [PATCH 54/90] Add two sum challenge --- arrays_strings/two_sum/__init__.py | 0 arrays_strings/two_sum/test_two_sum.py | 23 ++ .../two_sum/two_sum_challenge.ipynb | 170 +++++++++++ arrays_strings/two_sum/two_sum_solution.ipynb | 271 ++++++++++++++++++ 4 files changed, 464 insertions(+) create mode 100644 arrays_strings/two_sum/__init__.py create mode 100644 arrays_strings/two_sum/test_two_sum.py create mode 100644 arrays_strings/two_sum/two_sum_challenge.ipynb create mode 100644 arrays_strings/two_sum/two_sum_solution.ipynb diff --git a/arrays_strings/two_sum/__init__.py b/arrays_strings/two_sum/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/arrays_strings/two_sum/test_two_sum.py b/arrays_strings/two_sum/test_two_sum.py new file mode 100644 index 0000000..9f6fff3 --- /dev/null +++ b/arrays_strings/two_sum/test_two_sum.py @@ -0,0 +1,23 @@ +from nose.tools import assert_equal, assert_raises + + +class TestTwoSum(object): + + def test_two_sum(self): + solution = Solution() + assert_raises(TypeError, solution.two_sum, None, None) + assert_raises(ValueError, solution.two_sum, [], 0) + target = 7 + nums = [1, 3, 2, -7, 5] + expected = [2, 4] + assert_equal(solution.two_sum(nums, target), expected) + print('Success: test_two_sum') + + +def main(): + test = TestTwoSum() + test.test_two_sum() + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/arrays_strings/two_sum/two_sum_challenge.ipynb b/arrays_strings/two_sum/two_sum_challenge.ipynb new file mode 100644 index 0000000..733b9ec --- /dev/null +++ b/arrays_strings/two_sum/two_sum_challenge.ipynb @@ -0,0 +1,170 @@ +{ + "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 an array, find the two indices that sum to a specific value.\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", + "* Is there exactly one solution?\n", + " * Yes\n", + "* Is there always a solution?\n", + " * Yes\n", + "* Is the array an array of ints?\n", + " * Yes\n", + "* Is the array sorted?\n", + " No\n", + "* Are negative values possible?\n", + " * Yes\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 input -> TypeError\n", + "* [] -> ValueError\n", + "* [1, 3, 2, -7, 5], 7 -> [2, 4]" + ] + }, + { + "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 Solution(object):\n", + "\n", + " def two_sum(self, val):\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_two_sum.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestTwoSum(object):\n", + "\n", + " def test_two_sum(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.two_sum, None)\n", + " assert_equal(solution.two_sum(0), 0)\n", + " print('Success: test_two_sum')\n", + "\n", + "\n", + "def main():\n", + " test = TestTwoSum()\n", + " test.test_two_sum()\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/arrays_strings/two_sum/two_sum_solution.ipynb b/arrays_strings/two_sum/two_sum_solution.ipynb new file mode 100644 index 0000000..ad44d07 --- /dev/null +++ b/arrays_strings/two_sum/two_sum_solution.ipynb @@ -0,0 +1,271 @@ +{ + "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 an array, find the two indices that sum to a specific value.\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", + "* Is there exactly one solution?\n", + " * Yes\n", + "* Is there always a solution?\n", + " * Yes\n", + "* Is the array an array of ints?\n", + " * Yes\n", + "* Is the array sorted?\n", + " No\n", + "* Are negative values possible?\n", + " * Yes\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 input -> TypeError\n", + "* [] -> ValueError\n", + "* [1, 3, 2, -7, 5], 7 -> [2, 4]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "### Brute force\n", + "\n", + "* For i in range(len(input)):\n", + " * For j in range(i+1, len(input)):\n", + " * if i + j == target return True\n", + "* return False\n", + "\n", + "Complexity:\n", + "* Time: O(n^2)\n", + "* Space: O(1)\n", + "\n", + "### Optimized\n", + "\n", + "
\n",
+    "* Loop through each num in nums\n",
+    "    * Calculate the cache_target = target - num\n",
+    "\n",
+    "target = 7\n",
+    "index  =  0  1  2   3  4\n",
+    "nums   = [1, 3, 2, -7, 5]\n",
+    "          ^\n",
+    "cache_target = 7 - 1 = 6\n",
+    "cache\n",
+    "6 -> 0\n",
+    "\n",
+    "1 not in cache\n",
+    "\n",
+    "index  =  0  1  2   3  4\n",
+    "nums   = [1, 3, 2, -7, 5]\n",
+    "             ^\n",
+    "cache_target = 7 - 3 = 4\n",
+    "cache\n",
+    "6 -> 0\n",
+    "4 -> 1\n",
+    "\n",
+    "3 not in cache\n",
+    "\n",
+    "index  =  0  1  2   3  4\n",
+    "nums   = [1, 3, 2, -7, 5]\n",
+    "                ^\n",
+    "cache_target = 7 - 2 = 5\n",
+    "cache\n",
+    "6 -> 0\n",
+    "4 -> 1\n",
+    "5 -> 2\n",
+    "\n",
+    "2 not in cache\n",
+    "\n",
+    "index  =  0  1  2   3  4\n",
+    "nums   = [1, 3, 2, -7, 5]\n",
+    "                    ^\n",
+    "cache_target = 7 + 7 = 14\n",
+    "cache\n",
+    "6  -> 0\n",
+    "4  -> 1\n",
+    "5  -> 2\n",
+    "14 -> 3\n",
+    "\n",
+    "-7 not in cache\n",
+    "\n",
+    "index  =  0  1  2   3  4\n",
+    "nums   = [1, 3, 2, -7, 5]\n",
+    "                       ^\n",
+    "cache_target = 7 - 5 = 2\n",
+    "cache\n",
+    "6  -> 0\n",
+    "4  -> 1\n",
+    "5  -> 2\n",
+    "14 -> 3\n",
+    "\n",
+    "5 in cache, success, output matching indices: cache[num] and current iteration index\n",
+    "\n",
+    "output = [2, 4]\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(n)\n", + "* Space: O(n)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def two_sum(self, nums, target):\n", + " if nums is None or target is None:\n", + " raise TypeError('nums or target cannot be None')\n", + " if not nums:\n", + " raise ValueError('nums cannot be empty')\n", + " cache = {}\n", + " for index, num in enumerate(nums):\n", + " cache_target = target - num\n", + " if num in cache:\n", + " return [cache[num], index]\n", + " else:\n", + " cache[cache_target] = index\n", + " return None" + ] + }, + { + "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_two_sum.py\n" + ] + } + ], + "source": [ + "%%writefile test_two_sum.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestTwoSum(object):\n", + "\n", + " def test_two_sum(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.two_sum, None, None)\n", + " assert_raises(ValueError, solution.two_sum, [], 0)\n", + " target = 7\n", + " nums = [1, 3, 2, -7, 5]\n", + " expected = [2, 4]\n", + " assert_equal(solution.two_sum(nums, target), expected)\n", + " print('Success: test_two_sum')\n", + "\n", + "\n", + "def main():\n", + " test = TestTwoSum()\n", + " test.test_two_sum()\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_two_sum\n" + ] + } + ], + "source": [ + "%run -i test_two_sum.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 +} From 3ecc5ed263893d0d3b299fcf43d4aefbb9f4a70e Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Thu, 30 Mar 2017 05:42:41 -0400 Subject: [PATCH 55/90] Add priority queue challenge --- arrays_strings/priority_queue/__init__.py | 0 .../priority_queue/priority_queue.py | 41 +++ .../priority_queue_challenge.ipynb | 209 +++++++++++++ .../priority_queue_solution.ipynb | 276 ++++++++++++++++++ .../priority_queue/test_priority_queue.py | 30 ++ 5 files changed, 556 insertions(+) create mode 100644 arrays_strings/priority_queue/__init__.py create mode 100644 arrays_strings/priority_queue/priority_queue.py create mode 100644 arrays_strings/priority_queue/priority_queue_challenge.ipynb create mode 100644 arrays_strings/priority_queue/priority_queue_solution.ipynb create mode 100644 arrays_strings/priority_queue/test_priority_queue.py diff --git a/arrays_strings/priority_queue/__init__.py b/arrays_strings/priority_queue/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/arrays_strings/priority_queue/priority_queue.py b/arrays_strings/priority_queue/priority_queue.py new file mode 100644 index 0000000..3f0e4cd --- /dev/null +++ b/arrays_strings/priority_queue/priority_queue.py @@ -0,0 +1,41 @@ +import sys + + +class PriorityQueueNode(object): + + def __init__(self, obj, key): + self.obj = obj + self.key = key + + def __repr__(self): + return str(self.obj) + ': ' + str(self.key) + + +class PriorityQueue(object): + + def __init__(self): + self.array = [] + + def __len__(self): + return len(self.array) + + def insert(self, node): + self.array.append(node) + return self.array[-1] + + def extract_min(self): + if not self.array: + return None + minimum = sys.maxsize + for index, node in enumerate(self.array): + if node.key < minimum: + minimum = node.key + minimum_index = index + return self.array.pop(minimum_index) + + def decrease_key(self, obj, new_key): + for node in self.array: + if node.obj is obj: + node.key = new_key + return node + return None \ No newline at end of file diff --git a/arrays_strings/priority_queue/priority_queue_challenge.ipynb b/arrays_strings/priority_queue/priority_queue_challenge.ipynb new file mode 100644 index 0000000..7fc2b4e --- /dev/null +++ b/arrays_strings/priority_queue/priority_queue_challenge.ipynb @@ -0,0 +1,209 @@ +{ + "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 a priority queue backed by an array.\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", + "* Do we expect the methods to be insert, extract_min, and decrease_key?\n", + " * Yes\n", + "* Can we assume there aren't any duplicate keys?\n", + " * Yes\n", + "* Do we need to validate inputs?\n", + " * No\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "### insert\n", + "\n", + "* `insert` general case -> inserted node\n", + "\n", + "### extract_min\n", + "\n", + "* `extract_min` from an empty list -> None\n", + "* `extract_min` general case -> min node\n", + "\n", + "### decrease_key\n", + "\n", + "* `decrease_key` an invalid key -> None\n", + "* `decrease_key` general case -> updated node" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "Refer to the [Solution Notebook](https://github.com/donnemartin/interactive-coding-challenges/arrays_strings/priority_queue/priority_queue_solution.ipynb). 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 PriorityQueueNode(object):\n", + "\n", + " def __init__(self, obj, key):\n", + " self.obj = obj\n", + " self.key = key\n", + "\n", + " def __repr__(self):\n", + " return str(self.obj) + ': ' + str(self.key)\n", + "\n", + "\n", + "class PriorityQueue(object):\n", + "\n", + " def __init__(self):\n", + " self.array = []\n", + "\n", + " def __len__(self):\n", + " return len(self.array)\n", + "\n", + " def insert(self, node):\n", + " # TODO: Implement me\n", + " pass\n", + "\n", + " def extract_min(self):\n", + " # TODO: Implement me\n", + " pass\n", + "\n", + " def decrease_key(self, obj, new_key):\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_priority_queue.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestPriorityQueue(object):\n", + "\n", + " def test_priority_queue(self):\n", + " priority_queue = PriorityQueue()\n", + " assert_equal(priority_queue.extract_min(), None)\n", + " priority_queue.insert(PriorityQueueNode('a', 20))\n", + " priority_queue.insert(PriorityQueueNode('b', 5))\n", + " priority_queue.insert(PriorityQueueNode('c', 15))\n", + " priority_queue.insert(PriorityQueueNode('d', 22))\n", + " priority_queue.insert(PriorityQueueNode('e', 40))\n", + " priority_queue.insert(PriorityQueueNode('f', 3))\n", + " priority_queue.decrease_key('f', 2)\n", + " priority_queue.decrease_key('a', 19)\n", + " mins = []\n", + " while priority_queue.array:\n", + " mins.append(priority_queue.extract_min().key)\n", + " assert_equal(mins, [2, 5, 15, 19, 22, 40])\n", + " print('Success: test_min_heap')\n", + "\n", + "\n", + "def main():\n", + " test = TestPriorityQueue()\n", + " test.test_priority_queue()\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " main()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Solution Notebook\n", + "\n", + "Review the [Solution Notebook](https://github.com/donnemartin/interactive-coding-challenges/arrays_strings/priority_queue/priority_queue_solution.ipynb) 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/arrays_strings/priority_queue/priority_queue_solution.ipynb b/arrays_strings/priority_queue/priority_queue_solution.ipynb new file mode 100644 index 0000000..27d0144 --- /dev/null +++ b/arrays_strings/priority_queue/priority_queue_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 a priority queue backed by an array.\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", + "* Do we expect the methods to be insert, extract_min, and decrease_key?\n", + " * Yes\n", + "* Can we assume there aren't any duplicate keys?\n", + " * Yes\n", + "* Do we need to validate inputs?\n", + " * No\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "### insert\n", + "\n", + "* `insert` general case -> inserted node\n", + "\n", + "### extract_min\n", + "\n", + "* `extract_min` from an empty list -> None\n", + "* `extract_min` general case -> min node\n", + "\n", + "### decrease_key\n", + "\n", + "* `decrease_key` an invalid key -> None\n", + "* `decrease_key` general case -> updated node" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "### insert\n", + "\n", + "* Append to the internal array.\n", + "\n", + "Complexity:\n", + "* Time: O(1)\n", + "* Space: O(1)\n", + "\n", + "### extract_min\n", + "\n", + "* Loop through each item in the internal array\n", + " * Update the min value as needed\n", + "* Remove the min element from the array and return it\n", + "\n", + "Complexity:\n", + "* Time: O(n)\n", + "* Space: O(1)\n", + "\n", + "### decrease_key\n", + "\n", + "* Loop through each item in the internal array to find the matching input\n", + " * Update the matching element's key\n", + "\n", + "Complexity:\n", + "* Time: O(n)\n", + "* Space: O(1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting priority_queue.py\n" + ] + } + ], + "source": [ + "%%writefile priority_queue.py\n", + "import sys\n", + "\n", + "\n", + "class PriorityQueueNode(object):\n", + "\n", + " def __init__(self, obj, key):\n", + " self.obj = obj\n", + " self.key = key\n", + "\n", + " def __repr__(self):\n", + " return str(self.obj) + ': ' + str(self.key)\n", + "\n", + "\n", + "class PriorityQueue(object):\n", + "\n", + " def __init__(self):\n", + " self.array = []\n", + "\n", + " def __len__(self):\n", + " return len(self.array)\n", + "\n", + " def insert(self, node):\n", + " self.array.append(node)\n", + " return self.array[-1]\n", + "\n", + " def extract_min(self):\n", + " if not self.array:\n", + " return None\n", + " minimum = sys.maxsize\n", + " for index, node in enumerate(self.array):\n", + " if node.key < minimum:\n", + " minimum = node.key\n", + " minimum_index = index\n", + " return self.array.pop(minimum_index)\n", + "\n", + " def decrease_key(self, obj, new_key):\n", + " for node in self.array:\n", + " if node.obj is obj:\n", + " node.key = new_key\n", + " return node\n", + " return None" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%run priority_queue.py" + ] + }, + { + "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_priority_queue.py\n" + ] + } + ], + "source": [ + "%%writefile test_priority_queue.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestPriorityQueue(object):\n", + "\n", + " def test_priority_queue(self):\n", + " priority_queue = PriorityQueue()\n", + " assert_equal(priority_queue.extract_min(), None)\n", + " priority_queue.insert(PriorityQueueNode('a', 20))\n", + " priority_queue.insert(PriorityQueueNode('b', 5))\n", + " priority_queue.insert(PriorityQueueNode('c', 15))\n", + " priority_queue.insert(PriorityQueueNode('d', 22))\n", + " priority_queue.insert(PriorityQueueNode('e', 40))\n", + " priority_queue.insert(PriorityQueueNode('f', 3))\n", + " priority_queue.decrease_key('f', 2)\n", + " priority_queue.decrease_key('a', 19)\n", + " mins = []\n", + " while priority_queue.array:\n", + " mins.append(priority_queue.extract_min().key)\n", + " assert_equal(mins, [2, 5, 15, 19, 22, 40])\n", + " print('Success: test_min_heap')\n", + "\n", + "\n", + "def main():\n", + " test = TestPriorityQueue()\n", + " test.test_priority_queue()\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_min_heap\n" + ] + } + ], + "source": [ + "%run -i test_priority_queue.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/arrays_strings/priority_queue/test_priority_queue.py b/arrays_strings/priority_queue/test_priority_queue.py new file mode 100644 index 0000000..8983a9d --- /dev/null +++ b/arrays_strings/priority_queue/test_priority_queue.py @@ -0,0 +1,30 @@ +from nose.tools import assert_equal + + +class TestPriorityQueue(object): + + def test_priority_queue(self): + priority_queue = PriorityQueue() + assert_equal(priority_queue.extract_min(), None) + priority_queue.insert(PriorityQueueNode('a', 20)) + priority_queue.insert(PriorityQueueNode('b', 5)) + priority_queue.insert(PriorityQueueNode('c', 15)) + priority_queue.insert(PriorityQueueNode('d', 22)) + priority_queue.insert(PriorityQueueNode('e', 40)) + priority_queue.insert(PriorityQueueNode('f', 3)) + priority_queue.decrease_key('f', 2) + priority_queue.decrease_key('a', 19) + mins = [] + while priority_queue.array: + mins.append(priority_queue.extract_min().key) + assert_equal(mins, [2, 5, 15, 19, 22, 40]) + print('Success: test_min_heap') + + +def main(): + test = TestPriorityQueue() + test.test_priority_queue() + + +if __name__ == '__main__': + main() \ No newline at end of file From 7d8a40adf84583085bb33d642f7ccecc1043f6c4 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Thu, 30 Mar 2017 05:43:16 -0400 Subject: [PATCH 56/90] Add str diff challenge --- arrays_strings/str_diff/__init__.py | 0 .../str_diff/str_diff_challenge.ipynb | 165 ++++++++++++++ .../str_diff/str_diff_solution.ipynb | 215 ++++++++++++++++++ arrays_strings/str_diff/test_str_diff.py | 20 ++ 4 files changed, 400 insertions(+) create mode 100644 arrays_strings/str_diff/__init__.py create mode 100644 arrays_strings/str_diff/str_diff_challenge.ipynb create mode 100644 arrays_strings/str_diff/str_diff_solution.ipynb create mode 100644 arrays_strings/str_diff/test_str_diff.py diff --git a/arrays_strings/str_diff/__init__.py b/arrays_strings/str_diff/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/arrays_strings/str_diff/str_diff_challenge.ipynb b/arrays_strings/str_diff/str_diff_challenge.ipynb new file mode 100644 index 0000000..b91c27d --- /dev/null +++ b/arrays_strings/str_diff/str_diff_challenge.ipynb @@ -0,0 +1,165 @@ +{ + "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: Find the single different char between two strings.\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 assume the strings are ASCII?\n", + " * Yes\n", + "* Is case important?\n", + " * The strings are lower case\n", + "* Can we assume the inputs are valid?\n", + " * No, check for None\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None input -> TypeError\n", + "* 'abcd', 'abcde' -> 'e'\n", + "* 'aaabbcdd', 'abdbacade' -> 'e'" + ] + }, + { + "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 Solution(object):\n", + "\n", + " def find_diff(self, s, t):\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_str_diff.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestFindDiff(object):\n", + "\n", + " def test_find_diff(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.find_diff, None)\n", + " assert_equal(solution.find_diff('abcd', 'abcde'), 'e')\n", + " assert_equal(solution.find_diff('aaabbcdd', 'abdbacade'), 'e')\n", + " print('Success: test_find_diff')\n", + "\n", + "\n", + "def main():\n", + " test = TestFindDiff()\n", + " test.test_find_diff()\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/arrays_strings/str_diff/str_diff_solution.ipynb b/arrays_strings/str_diff/str_diff_solution.ipynb new file mode 100644 index 0000000..90813c4 --- /dev/null +++ b/arrays_strings/str_diff/str_diff_solution.ipynb @@ -0,0 +1,215 @@ +{ + "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: Find the single different char between two strings.\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 assume the strings are ASCII?\n", + " * Yes\n", + "* Is case important?\n", + " * The strings are lower case\n", + "* Can we assume the inputs are valid?\n", + " * No, check for None\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None input -> TypeError\n", + "* 'abcd', 'abcde' -> 'e'\n", + "* 'aaabbcdd', 'abdbacade' -> 'e'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "### Dictionary\n", + "\n", + "* Keep a dictionary of seen values in s\n", + "* Loop through t, decrementing the seen values\n", + " * If the char is not there or if the decrement results in a negative value, return the char\n", + "\n", + "Complexity:\n", + "* Time: O(m+n), where m and n are the lengths of s, t\n", + "* Space: O(h), for the dict, where h is the unique chars in s\n", + "\n", + "### XOR\n", + "\n", + "* XOR the two strings, which will isolate the differing char\n", + "\n", + "Complexity:\n", + "* Time: O(m+n), where m and n are the lengths of s, t\n", + "* Space: O(1), for the dict, where h is the unique chars in s" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Solution(object):\n", + "\n", + " def find_diff(self, str1, str2):\n", + " if str1 is None or str2 is None:\n", + " raise TypeError('str1 or str2 cannot be None')\n", + " seen = {}\n", + " for char in str1:\n", + " if char in seen:\n", + " seen[char] += 1\n", + " else:\n", + " seen[char] = 1\n", + " for char in str2:\n", + " try:\n", + " seen[char] -= 1\n", + " except KeyError:\n", + " return char\n", + " if seen[char] < 0:\n", + " return char\n", + " return None\n", + "\n", + " def find_diff_xor(self, str1, str2):\n", + " if str1 is None or str2 is None:\n", + " raise TypeError('str1 or str2 cannot be None')\n", + " result = 0\n", + " for char in str1:\n", + " result ^= ord(char)\n", + " for char in str2:\n", + " result ^= ord(char)\n", + " return chr(result)" + ] + }, + { + "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_str_diff.py\n" + ] + } + ], + "source": [ + "%%writefile test_str_diff.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestFindDiff(object):\n", + "\n", + " def test_find_diff(self):\n", + " solution = Solution()\n", + " assert_raises(TypeError, solution.find_diff, None)\n", + " assert_equal(solution.find_diff('abcd', 'abcde'), 'e')\n", + " assert_equal(solution.find_diff('aaabbcdd', 'abdbacade'), 'e')\n", + " print('Success: test_find_diff')\n", + "\n", + "\n", + "def main():\n", + " test = TestFindDiff()\n", + " test.test_find_diff()\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_find_diff\n" + ] + } + ], + "source": [ + "%run -i test_str_diff.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/arrays_strings/str_diff/test_str_diff.py b/arrays_strings/str_diff/test_str_diff.py new file mode 100644 index 0000000..7807db3 --- /dev/null +++ b/arrays_strings/str_diff/test_str_diff.py @@ -0,0 +1,20 @@ +from nose.tools import assert_equal, assert_raises + + +class TestFindDiff(object): + + def test_find_diff(self): + solution = Solution() + assert_raises(TypeError, solution.find_diff, None) + assert_equal(solution.find_diff('abcd', 'abcde'), 'e') + assert_equal(solution.find_diff('aaabbcdd', 'abdbacade'), 'e') + print('Success: test_find_diff') + + +def main(): + test = TestFindDiff() + test.test_find_diff() + + +if __name__ == '__main__': + main() \ No newline at end of file From 907f75d0d0878f4b2135fe0a8db5cb453c176fc4 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Thu, 30 Mar 2017 05:43:38 -0400 Subject: [PATCH 57/90] Add bit challenge --- bit_manipulation/bit/__init__.py | 0 bit_manipulation/bit/bit_challenge.ipynb | 225 ++++++++++++++ bit_manipulation/bit/bit_solution.ipynb | 358 +++++++++++++++++++++++ bit_manipulation/bit/test_bit.py | 38 +++ 4 files changed, 621 insertions(+) create mode 100644 bit_manipulation/bit/__init__.py create mode 100644 bit_manipulation/bit/bit_challenge.ipynb create mode 100644 bit_manipulation/bit/bit_solution.ipynb create mode 100644 bit_manipulation/bit/test_bit.py diff --git a/bit_manipulation/bit/__init__.py b/bit_manipulation/bit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bit_manipulation/bit/bit_challenge.ipynb b/bit_manipulation/bit/bit_challenge.ipynb new file mode 100644 index 0000000..85fc5ae --- /dev/null +++ b/bit_manipulation/bit/bit_challenge.ipynb @@ -0,0 +1,225 @@ +{ + "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 common bit manipulation operations: get_bit, set_bit, clear_bit, clear_bits_msb_to_index, clear_bits_msb_to_lsb, update_bit.\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 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 as a number input -> Exception\n", + "* Negative index -> Exception\n", + "\n", + "### get_bit\n", + " number = 0b10001110, index = 3\n", + " expected = True\n", + "### set_bit\n", + " number = 0b10001110, index = 4\n", + " expected = 0b10011110\n", + "### clear_bit\n", + " number = 0b10001110, index = 3\n", + " expected = 0b10000110\n", + "### clear_bits_msb_to_index\n", + " number = 0b10001110, index = 3\n", + " expected = 0b00000110\n", + "### clear_bits_index_to_lsb\n", + " number = 0b10001110, index = 3\n", + " expected = 0b10000000\n", + "### update_bit\n", + " number = 0b10001110, index = 3, value = 1\n", + " expected = 0b10001110\n", + " number = 0b10001110, index = 3, value = 0\n", + " expected = 0b10000110\n", + " number = 0b10001110, index = 0, value = 1\n", + " expected = 0b10001111" + ] + }, + { + "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 Bit(object):\n", + "\n", + " def __init__(self, number):\n", + " # TODO: Implement me\n", + " pass\n", + "\n", + " def get_bit(self, index):\n", + " # TODO: Implement me\n", + " pass\n", + "\n", + " def set_bit(self, index):\n", + " # TODO: Implement me\n", + " pass\n", + "\n", + " def clear_bit(self, index):\n", + " # TODO: Implement me\n", + " pass\n", + "\n", + " def clear_bits_msb_to_index(self, index):\n", + " # TODO: Implement me\n", + " pass\n", + "\n", + " def clear_bits_index_to_lsb(self, index):\n", + " # TODO: Implement me\n", + " pass\n", + "\n", + " def update_bit(self, index, value):\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_bit.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestBit(object):\n", + "\n", + " def test_bit(self):\n", + " number = int('10001110', base=2)\n", + " bit = Bit(number)\n", + " assert_equal(bit.get_bit(index=3), True)\n", + " expected = int('10011110', base=2)\n", + " assert_equal(bit.set_bit(index=4), expected)\n", + " bit = Bit(number)\n", + " expected = int('10000110', base=2)\n", + " assert_equal(bit.clear_bit(index=3), expected)\n", + " bit = Bit(number)\n", + " expected = int('00000110', base=2)\n", + " assert_equal(bit.clear_bits_msb_to_index(index=3), expected)\n", + " bit = Bit(number)\n", + " expected = int('10000000', base=2)\n", + " assert_equal(bit.clear_bits_index_to_lsb(index=3), expected)\n", + " bit = Bit(number)\n", + " assert_equal(bit.update_bit(index=3, value=1), number)\n", + " bit = Bit(number)\n", + " expected = int('10000110', base=2)\n", + " assert_equal(bit.update_bit(index=3, value=0), expected)\n", + " bit = Bit(number)\n", + " expected = int('10001111', base=2)\n", + " assert_equal(bit.update_bit(index=0, value=1), expected)\n", + " print('Success: test_bit')\n", + "\n", + "\n", + "def main():\n", + " test = TestBit()\n", + " test.test_bit()\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/bit_manipulation/bit/bit_solution.ipynb b/bit_manipulation/bit/bit_solution.ipynb new file mode 100644 index 0000000..be72758 --- /dev/null +++ b/bit_manipulation/bit/bit_solution.ipynb @@ -0,0 +1,358 @@ +{ + "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 common bit manipulation operations: get_bit, set_bit, clear_bit, clear_bits_msb_to_index, clear_bits_msb_to_lsb, update_bit.\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 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 as a number input -> Exception\n", + "* Negative index -> Exception\n", + "\n", + "### get_bit\n", + " number = 0b10001110, index = 3\n", + " expected = True\n", + "### set_bit\n", + " number = 0b10001110, index = 4\n", + " expected = 0b10011110\n", + "### clear_bit\n", + " number = 0b10001110, index = 3\n", + " expected = 0b10000110\n", + "### clear_bits_msb_to_index\n", + " number = 0b10001110, index = 3\n", + " expected = 0b00000110\n", + "### clear_bits_index_to_lsb\n", + " number = 0b10001110, index = 3\n", + " expected = 0b10000000\n", + "### update_bit\n", + " number = 0b10001110, index = 3, value = 1\n", + " expected = 0b10001110\n", + " number = 0b10001110, index = 3, value = 0\n", + " expected = 0b10000110\n", + " number = 0b10001110, index = 0, value = 1\n", + " expected = 0b10001111" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "### get_bit\n", + "\n", + "
\n",
+    "indices  7 6 5 4  3 2 1 0  index = 3\n",
+    "--------------------------------------------------\n",
+    "input    1 0 0 0  1 1 1 0  0b10001110\n",
+    "mask     0 0 0 0  1 0 0 0  1 << index\n",
+    "--------------------------------------------------\n",
+    "result   0 0 0 0  1 0 0 0  number & mask != 0\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(n)\n", + "* Space: O(n)\n", + "\n", + "### set_bit\n", + "\n", + "
\n",
+    "indices  7 6 5 4  3 2 1 0  index = 4\n",
+    "--------------------------------------------------\n",
+    "input    1 0 0 0  1 1 1 0  0b10001110\n",
+    "mask     0 0 0 1  0 0 0 0  1 << index\n",
+    "--------------------------------------------------\n",
+    "result   1 0 0 1  1 1 1 0  number | mask\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(n)\n", + "* Space: O(n)\n", + "\n", + "### clear_bit\n", + "\n", + "
\n",
+    "indices  7 6 5 4  3 2 1 0  index = 3\n",
+    "--------------------------------------------------\n",
+    "input    1 0 0 0  1 1 1 0  0b10001110\n",
+    "mask     0 0 0 0  1 0 0 0  1 << index\n",
+    "mask     1 1 1 1  0 1 1 1  ~(1 << index)\n",
+    "--------------------------------------------------\n",
+    "result   1 0 0 0  0 1 1 0  number & mask\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(n)\n", + "* Space: O(n)\n", + "\n", + "### clear_bits_msb_to_index\n", + "\n", + "
\n",
+    "indices  7 6 5 4  3 2 1 0  index = 3\n",
+    "--------------------------------------------------\n",
+    "input    1 0 0 0  1 1 1 0  0b10001110\n",
+    "mask     0 0 0 0  1 0 0 0  1 << index\n",
+    "mask     0 0 0 0  0 1 1 1  (1 << index) - 1\n",
+    "--------------------------------------------------\n",
+    "result   0 0 0 0  0 1 1 1  number & mask\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(n)\n", + "* Space: O(n)\n", + "\n", + "### clear_bits_index_to_lsb\n", + "\n", + "
\n",
+    "indices  7 6 5 4  3 2 1 0  index = 3\n",
+    "--------------------------------------------------\n",
+    "input    1 0 0 0  1 1 1 0  0b10001110\n",
+    "mask     0 0 0 1  0 0 0 0  1 << index + 1\n",
+    "mask     0 0 0 0  1 1 1 1  (1 << index + 1) - 1\n",
+    "mask     1 1 1 1  0 0 0 0  ~((1 << index + 1) - 1)\n",
+    "--------------------------------------------------\n",
+    "result   1 0 0 0  0 0 0 0  number & mask\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(n)\n", + "* Space: O(n)\n", + "\n", + "### update_bit\n", + "\n", + "* Use `get_bit` to see if the value is already set or cleared\n", + "* If not, use `set_bit` if setting the value or `clear_bit` if clearing the value\n", + "\n", + "Complexity:\n", + "* Time: O(n)\n", + "* Space: O(n)\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def validate_index(func):\n", + " def validate_index_wrapper(self, *args, **kwargs):\n", + " for arg in args:\n", + " if arg < 0:\n", + " raise IndexError('Invalid index')\n", + " return func(self, *args, **kwargs)\n", + " return validate_index_wrapper" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Bit(object):\n", + "\n", + " def __init__(self, number):\n", + " if number is None:\n", + " raise TypeError('number cannot be None')\n", + " self.number = number\n", + "\n", + " @validate_index\n", + " def get_bit(self, index):\n", + " mask = 1 << index\n", + " return self.number & mask != 0\n", + "\n", + " @validate_index\n", + " def set_bit(self, index):\n", + " mask = 1 << index\n", + " self.number |= mask\n", + " return self.number\n", + "\n", + " @validate_index\n", + " def clear_bit(self, index):\n", + " mask = ~(1 << index)\n", + " self.number &= mask\n", + " return self.number\n", + "\n", + " @validate_index\n", + " def clear_bits_msb_to_index(self, index):\n", + " mask = (1 << index) - 1\n", + " self.number &= mask\n", + " return self.number\n", + "\n", + " @validate_index\n", + " def clear_bits_index_to_lsb(self, index):\n", + " mask = ~((1 << index + 1) - 1)\n", + " self.number &= mask\n", + " return self.number\n", + "\n", + " @validate_index\n", + " def update_bit(self, index, value):\n", + " if value is None or value not in (0, 1):\n", + " raise Exception('Invalid value')\n", + " if self.get_bit(index) == value:\n", + " return self.number\n", + " if value:\n", + " self.set_bit(index)\n", + " else:\n", + " self.clear_bit(index)\n", + " return self.number" + ] + }, + { + "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_bit.py\n" + ] + } + ], + "source": [ + "%%writefile test_bit.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestBit(object):\n", + "\n", + " def test_bit(self):\n", + " number = int('10001110', base=2)\n", + " bit = Bit(number)\n", + " assert_equal(bit.get_bit(index=3), True)\n", + " expected = int('10011110', base=2)\n", + " assert_equal(bit.set_bit(index=4), expected)\n", + " bit = Bit(number)\n", + " expected = int('10000110', base=2)\n", + " assert_equal(bit.clear_bit(index=3), expected)\n", + " bit = Bit(number)\n", + " expected = int('00000110', base=2)\n", + " assert_equal(bit.clear_bits_msb_to_index(index=3), expected)\n", + " bit = Bit(number)\n", + " expected = int('10000000', base=2)\n", + " assert_equal(bit.clear_bits_index_to_lsb(index=3), expected)\n", + " bit = Bit(number)\n", + " assert_equal(bit.update_bit(index=3, value=1), number)\n", + " bit = Bit(number)\n", + " expected = int('10000110', base=2)\n", + " assert_equal(bit.update_bit(index=3, value=0), expected)\n", + " bit = Bit(number)\n", + " expected = int('10001111', base=2)\n", + " assert_equal(bit.update_bit(index=0, value=1), expected)\n", + " print('Success: test_bit')\n", + "\n", + "\n", + "def main():\n", + " test = TestBit()\n", + " test.test_bit()\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_bit\n" + ] + } + ], + "source": [ + "%run -i test_bit.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/bit_manipulation/bit/test_bit.py b/bit_manipulation/bit/test_bit.py new file mode 100644 index 0000000..2bca822 --- /dev/null +++ b/bit_manipulation/bit/test_bit.py @@ -0,0 +1,38 @@ +from nose.tools import assert_equal + + +class TestBit(object): + + def test_bit(self): + number = int('10001110', base=2) + bit = Bit(number) + assert_equal(bit.get_bit(index=3), True) + expected = int('10011110', base=2) + assert_equal(bit.set_bit(index=4), expected) + bit = Bit(number) + expected = int('10000110', base=2) + assert_equal(bit.clear_bit(index=3), expected) + bit = Bit(number) + expected = int('00000110', base=2) + assert_equal(bit.clear_bits_msb_to_index(index=3), expected) + bit = Bit(number) + expected = int('10000000', base=2) + assert_equal(bit.clear_bits_index_to_lsb(index=3), expected) + bit = Bit(number) + assert_equal(bit.update_bit(index=3, value=1), number) + bit = Bit(number) + expected = int('10000110', base=2) + assert_equal(bit.update_bit(index=3, value=0), expected) + bit = Bit(number) + expected = int('10001111', base=2) + assert_equal(bit.update_bit(index=0, value=1), expected) + print('Success: test_bit') + + +def main(): + test = TestBit() + test.test_bit() + + +if __name__ == '__main__': + main() \ No newline at end of file From dc4c01c6908f52b156846571e954ce3341fb191e Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Thu, 30 Mar 2017 05:44:27 -0400 Subject: [PATCH 58/90] Add print binary challenge --- bit_manipulation/print_binary/__init__.py | 0 .../print_binary/print_binary_challenge.ipynb | 178 +++++++++++++++ .../print_binary/print_binary_solution.ipynb | 215 ++++++++++++++++++ .../print_binary/test_print_binary.py | 25 ++ 4 files changed, 418 insertions(+) create mode 100644 bit_manipulation/print_binary/__init__.py create mode 100644 bit_manipulation/print_binary/print_binary_challenge.ipynb create mode 100644 bit_manipulation/print_binary/print_binary_solution.ipynb create mode 100644 bit_manipulation/print_binary/test_print_binary.py diff --git a/bit_manipulation/print_binary/__init__.py b/bit_manipulation/print_binary/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bit_manipulation/print_binary/print_binary_challenge.ipynb b/bit_manipulation/print_binary/print_binary_challenge.ipynb new file mode 100644 index 0000000..8eff2bc --- /dev/null +++ b/bit_manipulation/print_binary/print_binary_challenge.ipynb @@ -0,0 +1,178 @@ +{ + "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 real number between 0 and 1, print the binary representation. If the length of the representation is > 32, return 'ERROR'.\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", + "* Is the input a float?\n", + " * Yes\n", + "* Is the output a string?\n", + " * Yes\n", + "* Is 0 and 1 inclusive?\n", + " * No\n", + "* Does the result include a trailing zero and decimal point?\n", + " * Yes\n", + "* Is the leading zero and decimal point counted in the 32 char limit?\n", + " * Yes\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 -> 'ERROR'\n", + "* Out of bounds (0, 1) -> 'ERROR'\n", + "* General case\n", + " * 0.625 -> 0.101\n", + " * 0.987654321 -> 'ERROR'" + ] + }, + { + "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 Bits(object):\n", + "\n", + " def print_binary(self, num):\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_print_binary.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestBits(object):\n", + "\n", + " def test_print_binary(self):\n", + " bit = Bits()\n", + " assert_equal(bit.print_binary(None), 'ERROR')\n", + " assert_equal(bit.print_binary(0), 'ERROR')\n", + " assert_equal(bit.print_binary(1), 'ERROR')\n", + " num = 0.625\n", + " expected = '0.101'\n", + " assert_equal(bit.print_binary(num), expected)\n", + " num = 0.987654321\n", + " assert_equal(bit.print_binary(num), 'ERROR')\n", + " print('Success: test_print_binary')\n", + "\n", + "\n", + "def main():\n", + " test = TestBits()\n", + " test.test_print_binary()\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/bit_manipulation/print_binary/print_binary_solution.ipynb b/bit_manipulation/print_binary/print_binary_solution.ipynb new file mode 100644 index 0000000..9f697a9 --- /dev/null +++ b/bit_manipulation/print_binary/print_binary_solution.ipynb @@ -0,0 +1,215 @@ +{ + "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 real number between 0 and 1, print the binary representation. If the length of the representation is > 32, return 'ERROR'.\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", + "* Is the input a float?\n", + " * Yes\n", + "* Is the output a string?\n", + " * Yes\n", + "* Is 0 and 1 inclusive?\n", + " * No\n", + "* Does the result include a trailing zero and decimal point?\n", + " * Yes\n", + "* Is the leading zero and decimal point counted in the 32 char limit?\n", + " * Yes\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 -> 'ERROR'\n", + "* Out of bounds (0, 1) -> 'ERROR'\n", + "* General case\n", + " * 0.625 -> 0.101\n", + " * 0.987654321 -> 'ERROR'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "* Set the result to '0.'\n", + "* Start with a fraction of 0.5, which is 0.1 in base 2\n", + "* Loop while num > 0\n", + " * Check num versus fraction\n", + " * If num > fraction, add a 1 to the result, num -= fraction\n", + " * Else, add a 0 to the result\n", + " * If the len(result) > 32, return 'ERROR'\n", + " \n", + "Complexity:\n", + "* Time: O(1)\n", + "* Space: O(1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from __future__ import division\n", + "\n", + "\n", + "class Bits(object):\n", + "\n", + " MAX_BITS = 32\n", + "\n", + " def print_binary(self, num):\n", + " if num is None or num >= 1 or num <= 0:\n", + " return 'ERROR'\n", + " result = ['0', '.']\n", + " fraction = 0.5\n", + " while num:\n", + " if num >= fraction:\n", + " result.append('1')\n", + " num -= fraction\n", + " else:\n", + " result.append('0')\n", + " if len(result) > self.MAX_BITS:\n", + " return 'ERROR'\n", + " fraction /= 2\n", + " return ''.join(result)" + ] + }, + { + "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_print_binary.py\n" + ] + } + ], + "source": [ + "%%writefile test_print_binary.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestBits(object):\n", + "\n", + " def test_print_binary(self):\n", + " bit = Bits()\n", + " assert_equal(bit.print_binary(None), 'ERROR')\n", + " assert_equal(bit.print_binary(0), 'ERROR')\n", + " assert_equal(bit.print_binary(1), 'ERROR')\n", + " num = 0.625\n", + " expected = '0.101'\n", + " assert_equal(bit.print_binary(num), expected)\n", + " num = 0.987654321\n", + " assert_equal(bit.print_binary(num), 'ERROR')\n", + " print('Success: test_print_binary')\n", + "\n", + "\n", + "def main():\n", + " test = TestBits()\n", + " test.test_print_binary()\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_print_binary\n" + ] + } + ], + "source": [ + "%run -i test_print_binary.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/bit_manipulation/print_binary/test_print_binary.py b/bit_manipulation/print_binary/test_print_binary.py new file mode 100644 index 0000000..e32267b --- /dev/null +++ b/bit_manipulation/print_binary/test_print_binary.py @@ -0,0 +1,25 @@ +from nose.tools import assert_equal + + +class TestBits(object): + + def test_print_binary(self): + bit = Bits() + assert_equal(bit.print_binary(None), 'ERROR') + assert_equal(bit.print_binary(0), 'ERROR') + assert_equal(bit.print_binary(1), 'ERROR') + num = 0.625 + expected = '0.101' + assert_equal(bit.print_binary(num), expected) + num = 0.987654321 + assert_equal(bit.print_binary(num), 'ERROR') + print('Success: test_print_binary') + + +def main(): + test = TestBits() + test.test_print_binary() + + +if __name__ == '__main__': + main() \ No newline at end of file From 470b38cdc4c7c2cdf7e93bb606a331130af10155 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Thu, 30 Mar 2017 05:45:04 -0400 Subject: [PATCH 59/90] Add pairwise swap challenge --- bit_manipulation/pairwise_swap/__init__.py | 0 .../pairwise_swap_challenge.ipynb | 174 ++++++++++++++ .../pairwise_swap_solution.ipynb | 219 ++++++++++++++++++ .../pairwise_swap/test_pairwise_swap.py | 24 ++ 4 files changed, 417 insertions(+) create mode 100644 bit_manipulation/pairwise_swap/__init__.py create mode 100644 bit_manipulation/pairwise_swap/pairwise_swap_challenge.ipynb create mode 100644 bit_manipulation/pairwise_swap/pairwise_swap_solution.ipynb create mode 100644 bit_manipulation/pairwise_swap/test_pairwise_swap.py diff --git a/bit_manipulation/pairwise_swap/__init__.py b/bit_manipulation/pairwise_swap/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bit_manipulation/pairwise_swap/pairwise_swap_challenge.ipynb b/bit_manipulation/pairwise_swap/pairwise_swap_challenge.ipynb new file mode 100644 index 0000000..6174be8 --- /dev/null +++ b/bit_manipulation/pairwise_swap/pairwise_swap_challenge.ipynb @@ -0,0 +1,174 @@ +{ + "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: Swap the odd and even bits of a positive integer with as few operations as possible.\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 assume the input is always a positive int?\n", + " * Yes\n", + "* Can we assume we're working with 32 bits?\n", + " * Yes\n", + "* Is the output an int?\n", + " * Yes\n", + "* Can we assume the inputs are valid (not None)?\n", + " * No\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> Exception\n", + "* 0 -> 0\n", + "* -1 -> -1\n", + "* General case\n", + "
\n",
+    "    input  = 1001 1111 0110\n",
+    "    result = 0110 1111 1001\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": false
+   },
+   "outputs": [],
+   "source": [
+    "class Bits(object):\n",
+    "\n",
+    "    def pairwise_swap(self, num):\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_pairwise_swap.py\n",
+    "from nose.tools import assert_equal\n",
+    "\n",
+    "\n",
+    "class TestBits(object):\n",
+    "\n",
+    "    def test_pairwise_swap(self):\n",
+    "        bits = Bits()\n",
+    "        assert_equal(bits.pairwise_swap(0), 0)\n",
+    "        assert_equal(bits.pairwise_swap(1), 1)\n",
+    "        num = int('0000100111110110', base=2)\n",
+    "        expected = int('0000011011111001', base=2)\n",
+    "        assert_equal(bits.pairwise_swap(num), expected)\n",
+    "        print('Success: test_pairwise_swap')\n",
+    "\n",
+    "\n",
+    "def main():\n",
+    "    test = TestBits()\n",
+    "    test.test_pairwise_swap()\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/bit_manipulation/pairwise_swap/pairwise_swap_solution.ipynb b/bit_manipulation/pairwise_swap/pairwise_swap_solution.ipynb
new file mode 100644
index 0000000..9de5545
--- /dev/null
+++ b/bit_manipulation/pairwise_swap/pairwise_swap_solution.ipynb
@@ -0,0 +1,219 @@
+{
+ "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: Swap the odd and even bits of a positive integer with as few operations as possible.\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 assume the input is always a positive int?\n",
+    "    * Yes\n",
+    "* Can we assume we're working with 32 bits?\n",
+    "    * Yes\n",
+    "* Is the output an int?\n",
+    "    * Yes\n",
+    "* Can we assume the inputs are valid (not None)?\n",
+    "    * No\n",
+    "* Can we assume this fits memory?\n",
+    "    * Yes"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Test Cases\n",
+    "\n",
+    "* None -> Exception\n",
+    "* 0 -> 0\n",
+    "* -1 -> -1\n",
+    "* General case\n",
+    "
\n",
+    "    input  = 0000 1001 1111 0110\n",
+    "    result = 0000 0110 1111 1001\n",
+    "
"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Algorithm\n",
+    "\n",
+    "
\n",
+    "* Isolate the odd bits with a mask:\n",
+    "    0000 1001 1111 0110  num\n",
+    "    1010 1010 1010 1010  mask\n",
+    "    --------------------------------\n",
+    "    0000 1000 1010 0010  num & mask\n",
+    "\n",
+    "* Shift the odd bits right:\n",
+    "    0000 0100 0101 0001  odd\n",
+    "\n",
+    "* Isolate the even bits with a mask:\n",
+    "    0000 1001 1111 0110  num\n",
+    "    0101 0101 0101 0101  mask\n",
+    "    --------------------------------\n",
+    "    0000 0001 0101 0100  num & mask\n",
+    "\n",
+    "* Shift the even bits left:\n",
+    "    0000 0010 1010 1000  even\n",
+    "\n",
+    "* Return even | odd\n",
+    "    0000 0100 0101 0001  odd\n",
+    "    0000 0010 1010 1000  even\n",
+    "    --------------------------------\n",
+    "    0000 0110 1111 1001  odd | even\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(b), where b is the number of bits\n", + "* Space: O(b), where b is the number of bits" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Bits(object):\n", + "\n", + " def pairwise_swap(self, num):\n", + " if num is None:\n", + " raise TypeError('num cannot be None')\n", + " if num == 0 or num == 1:\n", + " return num\n", + " odd = (num & int('1010101010101010', base=2)) >> 1\n", + " even = (num & int('0101010101010101', base=2)) << 1\n", + " return odd | even" + ] + }, + { + "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_pairwise_swap.py\n" + ] + } + ], + "source": [ + "%%writefile test_pairwise_swap.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestBits(object):\n", + "\n", + " def test_pairwise_swap(self):\n", + " bits = Bits()\n", + " assert_equal(bits.pairwise_swap(0), 0)\n", + " assert_equal(bits.pairwise_swap(1), 1)\n", + " num = int('0000100111110110', base=2)\n", + " expected = int('0000011011111001', base=2)\n", + " assert_equal(bits.pairwise_swap(num), expected)\n", + " assert_raises(TypeError, bits.pairwise_swap, None)\n", + " \n", + " print('Success: test_pairwise_swap')\n", + "\n", + "\n", + "def main():\n", + " test = TestBits()\n", + " test.test_pairwise_swap()\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_pairwise_swap\n" + ] + } + ], + "source": [ + "%run -i test_pairwise_swap.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/bit_manipulation/pairwise_swap/test_pairwise_swap.py b/bit_manipulation/pairwise_swap/test_pairwise_swap.py new file mode 100644 index 0000000..5ee4cae --- /dev/null +++ b/bit_manipulation/pairwise_swap/test_pairwise_swap.py @@ -0,0 +1,24 @@ +from nose.tools import assert_equal, assert_raises + + +class TestBits(object): + + def test_pairwise_swap(self): + bits = Bits() + assert_equal(bits.pairwise_swap(0), 0) + assert_equal(bits.pairwise_swap(1), 1) + num = int('0000100111110110', base=2) + expected = int('0000011011111001', base=2) + assert_equal(bits.pairwise_swap(num), expected) + assert_raises(TypeError, bits.pairwise_swap, None) + + print('Success: test_pairwise_swap') + + +def main(): + test = TestBits() + test.test_pairwise_swap() + + +if __name__ == '__main__': + main() \ No newline at end of file From b1ea49f0c8ea9358f61f1598bd8fa10c78ddaed1 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Thu, 30 Mar 2017 05:48:26 -0400 Subject: [PATCH 60/90] Add insert m into n challenge --- bit_manipulation/insert_m_into_n/__init__.py | 0 .../insert_m_into_n_challenge.ipynb | 173 ++++++++++++++ .../insert_m_into_n_solution.ipynb | 221 ++++++++++++++++++ .../insert_m_into_n/test_insert_m_into_n.py | 21 ++ 4 files changed, 415 insertions(+) create mode 100644 bit_manipulation/insert_m_into_n/__init__.py create mode 100644 bit_manipulation/insert_m_into_n/insert_m_into_n_challenge.ipynb create mode 100644 bit_manipulation/insert_m_into_n/insert_m_into_n_solution.ipynb create mode 100644 bit_manipulation/insert_m_into_n/test_insert_m_into_n.py diff --git a/bit_manipulation/insert_m_into_n/__init__.py b/bit_manipulation/insert_m_into_n/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bit_manipulation/insert_m_into_n/insert_m_into_n_challenge.ipynb b/bit_manipulation/insert_m_into_n/insert_m_into_n_challenge.ipynb new file mode 100644 index 0000000..6d98306 --- /dev/null +++ b/bit_manipulation/insert_m_into_n/insert_m_into_n_challenge.ipynb @@ -0,0 +1,173 @@ +{ + "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 two 16 bit numbers, n and m, and two indices i, j, insert m into n such that m starts at bit j and ends at bit i.\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 assume j > i?\n", + " * Yes\n", + "* Can we assume i through j have enough space for m?\n", + " * Yes\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 as an input -> Exception\n", + "* Negative index for i or j -> Exception\n", + "* General case\n", + "
\n",
+    "i      = 2\n",
+    "j      = 6\n",
+    "n      = 0000 0100 0000 0000\n",
+    "m      = 0000 0000 0001 0011\n",
+    "result = 0000 0100 0100 1100\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": false + }, + "outputs": [], + "source": [ + "class Bits(object):\n", + "\n", + " def insert_m_into_n(self, m, n, i, j):\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_insert_m_into_n.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestBit(object):\n", + "\n", + " def test_insert_m_into_n(self):\n", + " n = int('0000010000111101', base=2)\n", + " m = int('0000000000010011', base=2)\n", + " expected = int('0000010001001101', base=2)\n", + " bits = Bits()\n", + " assert_equal(bits.insert_m_into_n(m, n, i=2, j=6), expected)\n", + " print('Success: test_insert_m_into_n')\n", + "\n", + "\n", + "def main():\n", + " test = TestBit()\n", + " test.test_insert_m_into_n()\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/bit_manipulation/insert_m_into_n/insert_m_into_n_solution.ipynb b/bit_manipulation/insert_m_into_n/insert_m_into_n_solution.ipynb new file mode 100644 index 0000000..c65f66a --- /dev/null +++ b/bit_manipulation/insert_m_into_n/insert_m_into_n_solution.ipynb @@ -0,0 +1,221 @@ +{ + "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 two 16 bit numbers, n and m, and two indices i, j, insert m into n such that m starts at bit j and ends at bit i.\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 assume j > i?\n", + " * Yes\n", + "* Can we assume i through j have enough space for m?\n", + " * Yes\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 as an input -> Exception\n", + "* Negative index for i or j -> Exception\n", + "* General case\n", + "\n", + "
\n",
+    "i = 2, j = 6\n",
+    "                    j    i\n",
+    "n      = 0000 0100 0011 1101\n",
+    "m      = 0000 0000 0001 0011\n",
+    "result = 0000 0100 0100 1101\n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "
\n",
+    "                    j    i\n",
+    "n      = 0000 0100 0011 1101\n",
+    "m      = 0000 0000 0001 0011\n",
+    "\n",
+    "lmask  = 1111 1111 1111 1111  -1\n",
+    "lmask  = 1111 1111 1000 0000  -1 << (j + 1)\n",
+    "\n",
+    "rmask  = 0000 0000 0000 0001   1\n",
+    "rmask  = 0000 0000 0000 0100   1 << i\n",
+    "rmask  = 0000 0000 0000 0011   (1 << i) -1\n",
+    "\n",
+    "mask   = 1111 1111 1000 0011   lmask | rmask\n",
+    "\n",
+    "n      = 0000 0100 0011 1101\n",
+    "mask   = 1111 1111 1000 0011   n & mask \n",
+    "--------------------------------------------------\n",
+    "n2     = 0000 0100 0000 0001\n",
+    "\n",
+    "n2     = 0000 0100 0000 0001\n",
+    "mask2  = 0000 0000 0100 1100   m << i\n",
+    "--------------------------------------------------\n",
+    "result = 0000 0100 0100 1101   n2 | mask2\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(b), where b is the number of bits\n", + "* Space: O(b), where b is the number of bits" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Bits(object):\n", + "\n", + " def insert_m_into_n(self, m, n, i, j):\n", + " if None in (m, n, i, j):\n", + " raise TypeError('Argument cannot be None')\n", + " if i < 0 or j < 0:\n", + " raise ValueError('Index cannot be negative')\n", + " left_mask = -1 << (j + 1)\n", + " right_mask = (1 << i) - 1\n", + " n_mask = left_mask | right_mask\n", + " # Clear bits from j to i, inclusive\n", + " n_cleared = n & n_mask\n", + " # Shift m into place before inserting it into n\n", + " m_mask = m << i\n", + " return n_cleared | m_mask" + ] + }, + { + "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_insert_m_into_n.py\n" + ] + } + ], + "source": [ + "%%writefile test_insert_m_into_n.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestBit(object):\n", + "\n", + " def test_insert_m_into_n(self):\n", + " n = int('0000010000111101', base=2)\n", + " m = int('0000000000010011', base=2)\n", + " expected = int('0000010001001101', base=2)\n", + " bits = Bits()\n", + " assert_equal(bits.insert_m_into_n(m, n, i=2, j=6), expected)\n", + " print('Success: test_insert_m_into_n')\n", + "\n", + "\n", + "def main():\n", + " test = TestBit()\n", + " test.test_insert_m_into_n()\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_insert_m_into_n\n" + ] + } + ], + "source": [ + "%run -i test_insert_m_into_n.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/bit_manipulation/insert_m_into_n/test_insert_m_into_n.py b/bit_manipulation/insert_m_into_n/test_insert_m_into_n.py new file mode 100644 index 0000000..4fcc231 --- /dev/null +++ b/bit_manipulation/insert_m_into_n/test_insert_m_into_n.py @@ -0,0 +1,21 @@ +from nose.tools import assert_equal + + +class TestBit(object): + + def test_insert_m_into_n(self): + n = int('0000010000111101', base=2) + m = int('0000000000010011', base=2) + expected = int('0000010001001101', base=2) + bits = Bits() + assert_equal(bits.insert_m_into_n(m, n, i=2, j=6), expected) + print('Success: test_insert_m_into_n') + + +def main(): + test = TestBit() + test.test_insert_m_into_n() + + +if __name__ == '__main__': + main() \ No newline at end of file From d4d819b4768c078a86503c1ec9c9802f057679c0 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Thu, 30 Mar 2017 05:49:03 -0400 Subject: [PATCH 61/90] Add get next challenge --- bit_manipulation/get_next/__init__.py | 0 .../get_next/get_next_challenge.ipynb | 186 ++++++++++++ .../get_next/get_next_solution.ipynb | 272 ++++++++++++++++++ .../get_next/test_get_next_largest.py | 33 +++ 4 files changed, 491 insertions(+) create mode 100644 bit_manipulation/get_next/__init__.py create mode 100644 bit_manipulation/get_next/get_next_challenge.ipynb create mode 100644 bit_manipulation/get_next/get_next_solution.ipynb create mode 100644 bit_manipulation/get_next/test_get_next_largest.py diff --git a/bit_manipulation/get_next/__init__.py b/bit_manipulation/get_next/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bit_manipulation/get_next/get_next_challenge.ipynb b/bit_manipulation/get_next/get_next_challenge.ipynb new file mode 100644 index 0000000..265dafd --- /dev/null +++ b/bit_manipulation/get_next/get_next_challenge.ipynb @@ -0,0 +1,186 @@ +{ + "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 positive integer, get the next largest number and the next smallest number with the same number of 1's as the given number.\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", + "* Is the output a positive int?\n", + " * Yes\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 -> Exception\n", + "* 0 -> Exception\n", + "* negative int -> Exception\n", + "* General case:\n", + "
\n",
+    "    * Input:         0000 0000 1101 0111\n",
+    "    * Next largest:  0000 0000 1101 1011\n",
+    "    * Next smallest: 0000 0000 1100 1111\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": false + }, + "outputs": [], + "source": [ + "class Bits(object):\n", + "\n", + " def get_next_largest(self, num):\n", + " # TODO: Implement me\n", + " pass\n", + "\n", + " def get_next_smallest(self, num):\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_get_next_largest.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestBits(object):\n", + "\n", + " def test_get_next_largest(self):\n", + " bits = Bits()\n", + " assert_raises(Exception, bits.get_next_largest, None)\n", + " assert_raises(Exception, bits.get_next_largest, 0)\n", + " assert_raises(Exception, bits.get_next_largest, -1)\n", + " num = int('011010111', base=2)\n", + " expected = int('011011011', base=2)\n", + " assert_equal(bits.get_next_largest(num), expected)\n", + " print('Success: test_get_next_largest')\n", + "\n", + " def test_get_next_smallest(self):\n", + " bits = Bits()\n", + " assert_raises(Exception, bits.get_next_smallest, None)\n", + " assert_raises(Exception, bits.get_next_smallest, 0)\n", + " assert_raises(Exception, bits.get_next_smallest, -1)\n", + " num = int('011010111', base=2)\n", + " expected = int('011001111', base=2)\n", + " assert_equal(bits.get_next_smallest(num), expected)\n", + " print('Success: test_get_next_smallest')\n", + "\n", + "def main():\n", + " test = TestBits()\n", + " test.test_get_next_largest()\n", + " test.test_get_next_smallest()\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/bit_manipulation/get_next/get_next_solution.ipynb b/bit_manipulation/get_next/get_next_solution.ipynb new file mode 100644 index 0000000..fa87ce3 --- /dev/null +++ b/bit_manipulation/get_next/get_next_solution.ipynb @@ -0,0 +1,272 @@ +{ + "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 positive integer, get the next largest number and the next smallest number with the same number of 1's as the given number.\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", + "* Is the output a positive int?\n", + " * Yes\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 -> Exception\n", + "* 0 -> Exception\n", + "* negative int -> Exception\n", + "* General case:\n", + "
\n",
+    "    * Input:         0000 0000 1101 0111\n",
+    "    * Next largest:  0000 0000 1101 1011\n",
+    "    * Next smallest: 0000 0000 1100 1111\n",
+    "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "### get_next_largest\n", + "\n", + "* Find the right-most non trailing zero, call this index\n", + " * We'll use a mask of 1 and do a logical right shift on a copy of num to examine each bit starting from the right\n", + " * Count the number of zeroes to the right of index\n", + " * While num & 1 == 0 and num_copy != 0:\n", + " * Increment number of zeroes\n", + " * Logical shift right num_copy\n", + " * Count the number of ones to the right of index\n", + " * While num & 1 == 1 and num_copy != 0:\n", + " * Increment number of ones\n", + " * Logical shift right num_copy\n", + " * The index will be the sum of number of ones and number of zeroes\n", + " * Set the index bit\n", + " * Clear all bits to the right of index\n", + " * Set bits starting from 0\n", + " * Only set (number of ones - 1) bits because we set index to 1\n", + "\n", + "We should make a note that Python does not have a logical right shift operator built in. We can either use a positive number of implement one for a 32 bit number:\n", + "\n", + " num % 0x100000000 >> n\n", + "\n", + "### get_next_smallest\n", + "\n", + "* The algorithm for finding the next smallest number is very similar to finding the next largest number\n", + " * Instead of finding the right-most non-trailing zero, we'll find the right most non-trailing one and clear it\n", + " * Clear all bits to the right of index\n", + " * Set bits starting at 0 to the number of ones to the right of index, plus one\n", + "\n", + "Complexity:\n", + "* Time: O(b), where b is the number of bits in num\n", + "* Space: O(b), where b is the number of bits in num" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Bits(object):\n", + "\n", + " def get_next_largest(self, num):\n", + " if num is None:\n", + " raise TypeError('num cannot be None')\n", + " if num <= 0:\n", + " raise ValueError('num cannot be 0 or negative')\n", + " num_ones = 0\n", + " num_zeroes = 0\n", + " num_copy = num\n", + " # We'll look for index, which is the right-most non-trailing zero\n", + " # Count number of zeroes to the right of index\n", + " while num_copy != 0 and num_copy & 1 == 0:\n", + " num_zeroes += 1\n", + " num_copy >>= 1\n", + " # Count number of ones to the right of index\n", + " while num_copy != 0 and num_copy & 1 == 1:\n", + " num_ones += 1\n", + " num_copy >>= 1\n", + " # Determine index and set the bit\n", + " index = num_zeroes + num_ones\n", + " num |= 1 << index\n", + " # Clear all bits to the right of index\n", + " num &= ~((1 << index) - 1)\n", + " # Set bits starting from 0\n", + " num |= ((1 << num_ones - 1) - 1)\n", + " return num\n", + "\n", + " def get_next_smallest(self, num):\n", + " if num is None:\n", + " raise TypeError('num cannot be None')\n", + " if num <= 0:\n", + " raise ValueError('num cannot be 0 or negative')\n", + " num_ones = 0\n", + " num_zeroes = 0\n", + " num_copy = num\n", + " # We'll look for index, which is the right-most non-trailing one\n", + " # Count number of zeroes to the right of index\n", + " while num_copy != 0 and num_copy & 1 == 1:\n", + " num_ones += 1\n", + " num_copy >>= 1\n", + " # Count number of zeroes to the right of index\n", + " while num_copy != 0 and num_copy & 1 == 0:\n", + " num_zeroes += 1\n", + " num_copy >>= 1\n", + " # Determine index and clear the bit\n", + " index = num_zeroes + num_ones\n", + " num &= ~(1 << index)\n", + " # Clear all bits to the right of index\n", + " num &= ~((1 << index) - 1)\n", + " # Set bits starting from 0\n", + " num |= (1 << num_ones + 1) - 1\n", + " return num" + ] + }, + { + "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_get_next_largest.py\n" + ] + } + ], + "source": [ + "%%writefile test_get_next_largest.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestBits(object):\n", + "\n", + " def test_get_next_largest(self):\n", + " bits = Bits()\n", + " assert_raises(Exception, bits.get_next_largest, None)\n", + " assert_raises(Exception, bits.get_next_largest, 0)\n", + " assert_raises(Exception, bits.get_next_largest, -1)\n", + " num = int('011010111', base=2)\n", + " expected = int('011011011', base=2)\n", + " assert_equal(bits.get_next_largest(num), expected)\n", + " print('Success: test_get_next_largest')\n", + "\n", + " def test_get_next_smallest(self):\n", + " bits = Bits()\n", + " assert_raises(Exception, bits.get_next_smallest, None)\n", + " assert_raises(Exception, bits.get_next_smallest, 0)\n", + " assert_raises(Exception, bits.get_next_smallest, -1)\n", + " num = int('011010111', base=2)\n", + " expected = int('011001111', base=2)\n", + " assert_equal(bits.get_next_smallest(num), expected)\n", + " print('Success: test_get_next_smallest')\n", + "\n", + "def main():\n", + " test = TestBits()\n", + " test.test_get_next_largest()\n", + " test.test_get_next_smallest()\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_get_next_largest\n", + "Success: test_get_next_smallest\n" + ] + } + ], + "source": [ + "%run -i test_get_next_largest.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/bit_manipulation/get_next/test_get_next_largest.py b/bit_manipulation/get_next/test_get_next_largest.py new file mode 100644 index 0000000..0fd11e2 --- /dev/null +++ b/bit_manipulation/get_next/test_get_next_largest.py @@ -0,0 +1,33 @@ +from nose.tools import assert_equal, assert_raises + + +class TestBits(object): + + def test_get_next_largest(self): + bits = Bits() + assert_raises(Exception, bits.get_next_largest, None) + assert_raises(Exception, bits.get_next_largest, 0) + assert_raises(Exception, bits.get_next_largest, -1) + num = int('011010111', base=2) + expected = int('011011011', base=2) + assert_equal(bits.get_next_largest(num), expected) + print('Success: test_get_next_largest') + + def test_get_next_smallest(self): + bits = Bits() + assert_raises(Exception, bits.get_next_smallest, None) + assert_raises(Exception, bits.get_next_smallest, 0) + assert_raises(Exception, bits.get_next_smallest, -1) + num = int('011010111', base=2) + expected = int('011001111', base=2) + assert_equal(bits.get_next_smallest(num), expected) + print('Success: test_get_next_smallest') + +def main(): + test = TestBits() + test.test_get_next_largest() + test.test_get_next_smallest() + + +if __name__ == '__main__': + main() \ No newline at end of file From c06594871d348edad5b69ca67fe237741f1de512 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Thu, 30 Mar 2017 05:49:29 -0400 Subject: [PATCH 62/90] Add flip bit challenge --- bit_manipulation/flip_bit/__init__.py | 0 .../flip_bit/flip_bit_challenge.ipynb | 182 ++++++++++++ .../flip_bit/flip_bit_solution.ipynb | 275 ++++++++++++++++++ bit_manipulation/flip_bit/test_flip_bit.py | 29 ++ 4 files changed, 486 insertions(+) create mode 100644 bit_manipulation/flip_bit/__init__.py create mode 100644 bit_manipulation/flip_bit/flip_bit_challenge.ipynb create mode 100644 bit_manipulation/flip_bit/flip_bit_solution.ipynb create mode 100644 bit_manipulation/flip_bit/test_flip_bit.py diff --git a/bit_manipulation/flip_bit/__init__.py b/bit_manipulation/flip_bit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bit_manipulation/flip_bit/flip_bit_challenge.ipynb b/bit_manipulation/flip_bit/flip_bit_challenge.ipynb new file mode 100644 index 0000000..c755106 --- /dev/null +++ b/bit_manipulation/flip_bit/flip_bit_challenge.ipynb @@ -0,0 +1,182 @@ +{ + "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: Flip one bit from 0 to 1 to maximize the longest sequence of 1s.\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", + "* Is the input an int, base 2?\n", + " * Yes\n", + "* Can we assume the input is a 32 bit number?\n", + " * Yes\n", + "* Do we have to validate the length of the input?\n", + " * No\n", + "* Is the output an int?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * No\n", + "* Can we assume we are using a positive number since Python doesn't have an >>> operator?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> Exception\n", + "* All 1's -> Count of 1s\n", + "* All 0's -> 1\n", + "* General case\n", + " * 0000 1111 1101 1101 1111 0011 1111 0000 -> 10 (ten)" + ] + }, + { + "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 Bits(object):\n", + "\n", + " def flip_bit(self, num):\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_flip_bit.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestBits(object):\n", + "\n", + " def test_flip_bit(self):\n", + " bits = Bits()\n", + " assert_raises(TypeError, bits.flip_bit, None)\n", + " assert_equal(bits.flip_bit(0), 1)\n", + " assert_equal(bits.flip_bit(-1), bits.MAX_BITS)\n", + " num = int('00001111110111011110001111110000', base=2)\n", + " expected = 10\n", + " assert_equal(bits.flip_bit(num), expected)\n", + " num = int('00000100111011101111100011111011', base=2)\n", + " expected = 9\n", + " assert_equal(bits.flip_bit(num), expected)\n", + " num = int('00010011101110111110001111101111', base=2)\n", + " expected = 10\n", + " assert_equal(bits.flip_bit(num), expected)\n", + " print('Success: test_print_binary')\n", + "\n", + "\n", + "def main():\n", + " test = TestBits()\n", + " test.test_flip_bit()\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/bit_manipulation/flip_bit/flip_bit_solution.ipynb b/bit_manipulation/flip_bit/flip_bit_solution.ipynb new file mode 100644 index 0000000..6b7889f --- /dev/null +++ b/bit_manipulation/flip_bit/flip_bit_solution.ipynb @@ -0,0 +1,275 @@ +{ + "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: Flip one bit from 0 to 1 to maximize the longest sequence of 1s.\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", + "* Is the input an int, base 2?\n", + " * Yes\n", + "* Can we assume the input is a 32 bit number?\n", + " * Yes\n", + "* Do we have to validate the length of the input?\n", + " * No\n", + "* Is the output an int?\n", + " * Yes\n", + "* Can we assume the inputs are valid?\n", + " * No\n", + "* Can we assume we are using a positive number since Python doesn't have an >>> operator?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* None -> Exception\n", + "* All 1's -> Count of 1s\n", + "* All 0's -> 1\n", + "* General case\n", + " * Trailing zeroes\n", + " * 0000 1111 1101 1101 1111 0011 1111 0000 -> 10 (ten)\n", + " * Trailing ones\n", + " * 0000 1001 1101 1101 1111 0001 1111 0111 -> 9" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "* seen = []\n", + "* Build a list of sequence counts\n", + " * Look for 0's\n", + " * This will be 0 length if the input has trailing ones\n", + " * Add sequence length to seen\n", + " * Look for 1's\n", + " * Add sequence length to seen\n", + "* Find the largest sequence of ones looking at seen\n", + " * Loop through seen\n", + " * On each iteration of the loop, flip what we are looking for from 0 to 1 and vice versa\n", + " * If seen[i] represents 1's, continue, we only want to process 0's\n", + " * If this is our first iteration:\n", + " * max_result = seen[i+1] + 1 if seen[i] > 0\n", + " * continue\n", + " * If we are looking at leading zeroes (i == len(seen)-1):\n", + " * result = seen[i-1] + 1\n", + " * If we are looking at one zero:\n", + " * result = seen[i+1] + seen[i-1] + 1\n", + " * If we are looking at multiple zeroes:\n", + " * result = max(seen[i+1], seen[i-1]) + 1\n", + " * Update max_result based on result\n", + "\n", + "We should make a note that Python does not have a logical right shift operator built in. We can either use a positive number or implement one for a 32 bit number:\n", + "\n", + " num % 0x100000000 >> n\n", + " \n", + "Complexity:\n", + "* Time: O(b)\n", + "* Space: O(b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Bits(object):\n", + "\n", + " MAX_BITS = 32\n", + " \n", + " def _build_seen_list(self, num):\n", + " seen = []\n", + " looking_for = 0\n", + " count = 0\n", + " for _ in range(self.MAX_BITS):\n", + " if num & 1 != looking_for:\n", + " seen.append(count)\n", + " looking_for = not looking_for\n", + " count = 0\n", + " count += 1\n", + " num >>= 1\n", + " seen.append(count)\n", + " return seen\n", + " \n", + " def flip_bit(self, num):\n", + " if num is None:\n", + " raise TypeError('num cannot be None')\n", + " if num == -1:\n", + " return self.MAX_BITS\n", + " if num == 0:\n", + " return 1\n", + " seen = self._build_seen_list(num)\n", + " max_result = 0\n", + " looking_for = 0\n", + " for index, count in enumerate(seen):\n", + " result = 0\n", + " # Only look for zeroes\n", + " if looking_for == 1:\n", + " looking_for = not looking_for\n", + " continue\n", + " # First iteration, take trailing zeroes\n", + " # or trailing ones into account\n", + " if index == 0:\n", + " if count != 0:\n", + " # Trailing zeroes\n", + " try:\n", + " result = seen[index + 1] + 1\n", + " except IndexError:\n", + " result = 1\n", + " # Last iteration\n", + " elif index == len(seen) - 1:\n", + " result = 1 + seen[index - 1]\n", + " else:\n", + " # One zero\n", + " if count == 1:\n", + " result = seen[index + 1] + seen[index - 1] + 1\n", + " # Multiple zeroes\n", + " else:\n", + " result = max(seen[index + 1], seen[index - 1]) + 1\n", + " if result > max_result:\n", + " max_result = result\n", + " looking_for = not looking_for\n", + " return max_result" + ] + }, + { + "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_flip_bit.py\n" + ] + } + ], + "source": [ + "%%writefile test_flip_bit.py\n", + "from nose.tools import assert_equal, assert_raises\n", + "\n", + "\n", + "class TestBits(object):\n", + "\n", + " def test_flip_bit(self):\n", + " bits = Bits()\n", + " assert_raises(TypeError, bits.flip_bit, None)\n", + " assert_equal(bits.flip_bit(0), 1)\n", + " assert_equal(bits.flip_bit(-1), bits.MAX_BITS)\n", + " num = int('00001111110111011110001111110000', base=2)\n", + " expected = 10\n", + " assert_equal(bits.flip_bit(num), expected)\n", + " num = int('00000100111011101111100011111011', base=2)\n", + " expected = 9\n", + " assert_equal(bits.flip_bit(num), expected)\n", + " num = int('00010011101110111110001111101111', base=2)\n", + " expected = 10\n", + " assert_equal(bits.flip_bit(num), expected)\n", + " print('Success: test_print_binary')\n", + "\n", + "\n", + "def main():\n", + " test = TestBits()\n", + " test.test_flip_bit()\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_print_binary\n" + ] + } + ], + "source": [ + "%run -i test_flip_bit.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/bit_manipulation/flip_bit/test_flip_bit.py b/bit_manipulation/flip_bit/test_flip_bit.py new file mode 100644 index 0000000..6d72d58 --- /dev/null +++ b/bit_manipulation/flip_bit/test_flip_bit.py @@ -0,0 +1,29 @@ +from nose.tools import assert_equal, assert_raises + + +class TestBits(object): + + def test_flip_bit(self): + bits = Bits() + assert_raises(TypeError, bits.flip_bit, None) + assert_equal(bits.flip_bit(0), 1) + assert_equal(bits.flip_bit(-1), bits.MAX_BITS) + num = int('00001111110111011110001111110000', base=2) + expected = 10 + assert_equal(bits.flip_bit(num), expected) + num = int('00000100111011101111100011111011', base=2) + expected = 9 + assert_equal(bits.flip_bit(num), expected) + num = int('00010011101110111110001111101111', base=2) + expected = 10 + assert_equal(bits.flip_bit(num), expected) + print('Success: test_print_binary') + + +def main(): + test = TestBits() + test.test_flip_bit() + + +if __name__ == '__main__': + main() \ No newline at end of file From 5b8ae59f09f17d5eea3c88e77a78dd5aec514cfc Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Thu, 30 Mar 2017 05:49:59 -0400 Subject: [PATCH 63/90] Add draw line challenge --- bit_manipulation/draw_line/__init__.py | 0 .../draw_line/draw_line_challenge.ipynb | 180 +++++++++++ .../draw_line/draw_line_solution.ipynb | 288 ++++++++++++++++++ bit_manipulation/draw_line/test_draw_line.py | 28 ++ 4 files changed, 496 insertions(+) create mode 100644 bit_manipulation/draw_line/__init__.py create mode 100644 bit_manipulation/draw_line/draw_line_challenge.ipynb create mode 100644 bit_manipulation/draw_line/draw_line_solution.ipynb create mode 100644 bit_manipulation/draw_line/test_draw_line.py diff --git a/bit_manipulation/draw_line/__init__.py b/bit_manipulation/draw_line/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bit_manipulation/draw_line/draw_line_challenge.ipynb b/bit_manipulation/draw_line/draw_line_challenge.ipynb new file mode 100644 index 0000000..ed9ce5d --- /dev/null +++ b/bit_manipulation/draw_line/draw_line_challenge.ipynb @@ -0,0 +1,180 @@ +{ + "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 the method draw_line(screen, width, x1, x2) where screen is a list of bytes, width is divisible by 8, and x1, x2 are absolute pixel positions.\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 assume the inputs are valid?\n", + " * No\n", + "* For the output, do we set corresponding bits in screen?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* Invalid inputs -> Exception\n", + " * screen is empty\n", + " * width = 0\n", + " * any input param is None\n", + " * x1 or x2 is out of bounds\n", + "* General case for len(screen) = 20, width = 32:\n", + " * x1 = 2, x2 = 6\n", + " * screen[0] = int('00111110', base=2)\n", + " * x1 = 68, x2 = 80\n", + " * screen[8], int('00001111', base=2)\n", + " * screen[9], int('11111111', base=2)\n", + " * screen[10], int('10000000', base=2)" + ] + }, + { + "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 BitsScreen(object):\n", + "\n", + " def draw_line(self, screen, width, x1, x2):\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_draw_line.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestBitsScreen(object):\n", + "\n", + " def test_draw_line(self):\n", + " bits_screen = BitsScreen()\n", + " screen = []\n", + " for _ in range(20):\n", + " screen.append(int('00000000', base=2))\n", + " bits_screen.draw_line(screen, width=32, x1=68, x2=80)\n", + " assert_equal(screen[8], int('00001111', base=2))\n", + " assert_equal(screen[9], int('11111111', base=2))\n", + " assert_equal(screen[10], int('10000000', base=2))\n", + " bits_screen.draw_line(screen, width=32, x1=2, x2=6)\n", + " assert_equal(screen[0], int('00111110', base=2))\n", + " bits_screen.draw_line(screen, width=32, x1=10, x2=13)\n", + " assert_equal(screen[1], int('00111100', base=2))\n", + " print('Success: test_draw_line')\n", + "\n", + "\n", + "def main():\n", + " test = TestBitsScreen()\n", + " test.test_draw_line()\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/bit_manipulation/draw_line/draw_line_solution.ipynb b/bit_manipulation/draw_line/draw_line_solution.ipynb new file mode 100644 index 0000000..798f46f --- /dev/null +++ b/bit_manipulation/draw_line/draw_line_solution.ipynb @@ -0,0 +1,288 @@ +{ + "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 the method draw_line(screen, width, x1, x2) where screen is a list of bytes, width is divisible by 8, and x1, x2 are absolute pixel positions.\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 assume the inputs are valid?\n", + " * No\n", + "* For the output, do we set corresponding bits in screen?\n", + " * Yes\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* Invalid inputs -> Exception\n", + " * screen is empty\n", + " * width = 0\n", + " * any input param is None\n", + " * x1 or x2 is out of bounds\n", + "* General case for len(screen) = 20, width = 32:\n", + " * x1 = 2, x2 = 6\n", + " * screen[0] = int('00111110', base=2)\n", + " * x1 = 68, x2 = 80\n", + " * screen[8], int('00001111', base=2)\n", + " * screen[9], int('11111111', base=2)\n", + " * screen[10], int('10000000', base=2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithm\n", + "\n", + "* Set start offset to x1 % 8\n", + "* Set end offset to x2 % 8\n", + "* Determine where the first and last full bytes are\n", + " * First full byte = x1 // 8\n", + " * Increment by 1 if start offset != 0\n", + " * Last full byte = x2 // 8\n", + " * Decrement by 1 if end offset != 7\n", + " * Fill the bytes with 1111 1111\n", + "* If x1 and x2 are in the same byte\n", + "\n", + "
\n",
+    "    x1 = 2\n",
+    "    x2 = 6\n",
+    "\n",
+    "    index  0123 4567\n",
+    "    byte   0011 1110\n",
+    "\n",
+    "    We want to build left and right masks such that:\n",
+    "\n",
+    "    left   0011 1111\n",
+    "    right  1111 1110\n",
+    "    ----------------\n",
+    "           0011 1110  left & right\n",
+    "\n",
+    "    Build the left mask:\n",
+    "\n",
+    "    left   0000 0001  1\n",
+    "           0100 0000  1 << (8 - x1)\n",
+    "           0011 1111  (1 << (8 - x1)) - 1\n",
+    "\n",
+    "    Build the right mask:\n",
+    "\n",
+    "    right  0000 0000  1\n",
+    "           0000 0010  1 << (8 - x2 - 1)\n",
+    "           0000 0001  (1 << (8 - x2 - 1) - 1\n",
+    "           1111 1110  ~((1 << (8 - x2 - 1) - 1)\n",
+    "\n",
+    "    Combine the left and right masks:\n",
+    "\n",
+    "    left   0011 1111\n",
+    "    right  1111 1110\n",
+    "    ----------------\n",
+    "           0011 1110  left & right\n",
+    "\n",
+    "    Set screen[x1//8] or screen[x2//8] |= mask\n",
+    "
\n", + "* Else\n", + "
\n",
+    "    If our starting partial byte is:\n",
+    "           0000 1111\n",
+    "\n",
+    "    Build start mask:\n",
+    "    \n",
+    "    start  0000 0001  1\n",
+    "           0001 0000  1 << 1 << start offset\n",
+    "           0000 1111  (1 << 1 << start offset) - 1\n",
+    "\n",
+    "    If our ending partial byte is:\n",
+    "           1111 1100\n",
+    "\n",
+    "    Build end mask:\n",
+    "    \n",
+    "    end    1000 0000  0x10000000\n",
+    "           1111 1100  0x10000000 >> end offset\n",
+    "\n",
+    "    Set screen[x1//8] |= start mask\n",
+    "    Set screen[x2//8] |= end mask\n",
+    "
\n", + "\n", + "Complexity:\n", + "* Time: O(length of screen)\n", + "* Space: O(1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class BitsScreen(object):\n", + "\n", + " def draw_line(self, screen, width, x1, x2):\n", + " if None in (screen, width, x1, x2):\n", + " raise TypeError('Invalid argument: None')\n", + " if not screen or not width:\n", + " raise ValueError('Invalid arg: Empty screen or width')\n", + " MAX_BIT_VALUE = len(screen) * 8\n", + " if x1 < 0 or x2 < 0 or x1 >= MAX_BIT_VALUE or x2 >= MAX_BIT_VALUE:\n", + " raise ValueError('Invalid arg: x1 or x2 out of bounds')\n", + " start_bit = x1 % 8\n", + " end_bit = x2 % 8\n", + " first_full_byte = x1 // 8\n", + " if start_bit != 0:\n", + " first_full_byte += 1\n", + " last_full_byte = x2 // 8\n", + " if end_bit != (8 - 1):\n", + " last_full_byte -= 1\n", + " for byte in range(first_full_byte, last_full_byte + 1):\n", + " screen[byte] = int('11111111', base=2)\n", + " start_byte = x1 // 8\n", + " end_byte = x2 // 8\n", + " if start_byte == end_byte:\n", + " left_mask = (1 << (8 - start_bit)) - 1\n", + " right_mask = ~((1 << (8 - end_bit - 1)) - 1)\n", + " mask = left_mask & right_mask\n", + " screen[start_byte] |= mask\n", + " else:\n", + " start_mask = (1 << (8 - start_bit)) - 1\n", + " end_mask = 1 << (8 - end_bit - 1)\n", + " screen[start_byte] |= start_mask\n", + " screen[end_byte] |= end_mask" + ] + }, + { + "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_draw_line.py\n" + ] + } + ], + "source": [ + "%%writefile test_draw_line.py\n", + "from nose.tools import assert_equal\n", + "\n", + "\n", + "class TestBitsScreen(object):\n", + "\n", + " def test_draw_line(self):\n", + " bits_screen = BitsScreen()\n", + " screen = []\n", + " for _ in range(20):\n", + " screen.append(int('00000000', base=2))\n", + " bits_screen.draw_line(screen, width=32, x1=68, x2=80)\n", + " assert_equal(screen[8], int('00001111', base=2))\n", + " assert_equal(screen[9], int('11111111', base=2))\n", + " assert_equal(screen[10], int('10000000', base=2))\n", + " bits_screen.draw_line(screen, width=32, x1=2, x2=6)\n", + " assert_equal(screen[0], int('00111110', base=2))\n", + " bits_screen.draw_line(screen, width=32, x1=10, x2=13)\n", + " assert_equal(screen[1], int('00111100', base=2))\n", + " print('Success: test_draw_line')\n", + "\n", + "\n", + "def main():\n", + " test = TestBitsScreen()\n", + " test.test_draw_line()\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_draw_line\n" + ] + } + ], + "source": [ + "%run -i test_draw_line.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/bit_manipulation/draw_line/test_draw_line.py b/bit_manipulation/draw_line/test_draw_line.py new file mode 100644 index 0000000..69f736d --- /dev/null +++ b/bit_manipulation/draw_line/test_draw_line.py @@ -0,0 +1,28 @@ +from nose.tools import assert_equal + + +class TestBitsScreen(object): + + def test_draw_line(self): + bits_screen = BitsScreen() + screen = [] + for _ in range(20): + screen.append(int('00000000', base=2)) + bits_screen.draw_line(screen, width=32, x1=68, x2=80) + assert_equal(screen[8], int('00001111', base=2)) + assert_equal(screen[9], int('11111111', base=2)) + assert_equal(screen[10], int('10000000', base=2)) + bits_screen.draw_line(screen, width=32, x1=2, x2=6) + assert_equal(screen[0], int('00111110', base=2)) + bits_screen.draw_line(screen, width=32, x1=10, x2=13) + assert_equal(screen[1], int('00111100', base=2)) + print('Success: test_draw_line') + + +def main(): + test = TestBitsScreen() + test.test_draw_line() + + +if __name__ == '__main__': + main() \ No newline at end of file From 65caa98e572b04a60cb5204bf21b7b393bd2735c Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Fri, 31 Mar 2017 04:45:28 -0400 Subject: [PATCH 64/90] Add bits to flip challenge --- bit_manipulation/bits_to_flip/__init__.py | 0 .../bits_to_flip/bits_to_flip_challenge.ipynb | 172 +++++++++++++++ .../bits_to_flip/bits_to_flip_solution.ipynb | 200 ++++++++++++++++++ .../bits_to_flip/test_bits_to_flip.py | 21 ++ 4 files changed, 393 insertions(+) create mode 100644 bit_manipulation/bits_to_flip/__init__.py create mode 100644 bit_manipulation/bits_to_flip/bits_to_flip_challenge.ipynb create mode 100644 bit_manipulation/bits_to_flip/bits_to_flip_solution.ipynb create mode 100644 bit_manipulation/bits_to_flip/test_bits_to_flip.py diff --git a/bit_manipulation/bits_to_flip/__init__.py b/bit_manipulation/bits_to_flip/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bit_manipulation/bits_to_flip/bits_to_flip_challenge.ipynb b/bit_manipulation/bits_to_flip/bits_to_flip_challenge.ipynb new file mode 100644 index 0000000..29a18e1 --- /dev/null +++ b/bit_manipulation/bits_to_flip/bits_to_flip_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: Determine the number of bits to flip to convert int a to int b'.\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 assume A and B are always ints?\n", + " * Yes\n", + "* Is the output an int?\n", + " * Yes\n", + "* Can we assume A and B are always the same number of bits?\n", + " * Yes\n", + "* Can we assume the inputs are valid (not None)?\n", + " * No\n", + "* Can we assume this fits memory?\n", + " * Yes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Cases\n", + "\n", + "* A or B is None -> Exception\n", + "* General case\n", + "
\n",
+    "    A = 11101\n",
+    "    B = 01111\n",
+    "    Result: 2\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": false
+   },
+   "outputs": [],
+   "source": [
+    "class Bits(object):\n",
+    "\n",
+    "    def bits_to_flip(self, a, b):\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_bits_to_flip.py\n",
+    "from nose.tools import assert_equal\n",
+    "\n",
+    "\n",
+    "class TestBits(object):\n",
+    "\n",
+    "    def test_bits_to_flip(self):\n",
+    "        bits = Bits()\n",
+    "        a = int('11101', base=2)\n",
+    "        b = int('01111', base=2)\n",
+    "        expected = 2\n",
+    "        assert_equal(bits.bits_to_flip(a, b), expected)\n",
+    "        print('Success: test_bits_to_flip')\n",
+    "\n",
+    "\n",
+    "def main():\n",
+    "    test = TestBits()\n",
+    "    test.test_bits_to_flip()\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/bit_manipulation/bits_to_flip/bits_to_flip_solution.ipynb b/bit_manipulation/bits_to_flip/bits_to_flip_solution.ipynb
new file mode 100644
index 0000000..87db11d
--- /dev/null
+++ b/bit_manipulation/bits_to_flip/bits_to_flip_solution.ipynb
@@ -0,0 +1,200 @@
+{
+ "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: Determine the number of bits to flip to convert int a to int b.\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 assume A and B are always ints?\n",
+    "    * Yes\n",
+    "* Is the output an int?\n",
+    "    * Yes\n",
+    "* Can we assume A and B are always the same number of bits?\n",
+    "    * Yes\n",
+    "* Can we assume the inputs are valid (not None)?\n",
+    "    * No\n",
+    "* Can we assume this fits memory?\n",
+    "    * Yes"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Test Cases\n",
+    "\n",
+    "* A or B is None -> Exception\n",
+    "* General case\n",
+    "
\n",
+    "    A = 11101\n",
+    "    B = 01111\n",
+    "    Result: 2\n",
+    "
"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Algorithm\n",
+    "\n",
+    "We can use the xor operator to determine the bit differences between a and b\n",
+    "\n",
+    "* Set count to 0\n",
+    "* Set c to a xor b\n",
+    "* Loop while c != 0:\n",
+    "    * Increment count if the LSB in c is a 1\n",
+    "        * We can check this by using a mask of 1\n",
+    "    * Right shift c by 1\n",
+    "* Return the count\n",
+    "    \n",
+    "Complexity:\n",
+    "* Time: O(b), where b is the number of bits\n",
+    "* Space: O(b), where b is the number of bits"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Code"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "class Bits(object):\n",
+    "\n",
+    "    def bits_to_flip(self, a, b):\n",
+    "        if a is None or b is None:\n",
+    "            raise TypeError('a or b cannot be None')\n",
+    "        count = 0\n",
+    "        c = a ^ b\n",
+    "        while c:\n",
+    "            count += c & 1\n",
+    "            c >>= 1\n",
+    "        return count"
+   ]
+  },
+  {
+   "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_bits_to_flip.py\n"
+     ]
+    }
+   ],
+   "source": [
+    "%%writefile test_bits_to_flip.py\n",
+    "from nose.tools import assert_equal\n",
+    "\n",
+    "\n",
+    "class TestBits(object):\n",
+    "\n",
+    "    def test_bits_to_flip(self):\n",
+    "        bits = Bits()\n",
+    "        a = int('11101', base=2)\n",
+    "        b = int('01111', base=2)\n",
+    "        expected = 2\n",
+    "        assert_equal(bits.bits_to_flip(a, b), expected)\n",
+    "        print('Success: test_bits_to_flip')\n",
+    "\n",
+    "\n",
+    "def main():\n",
+    "    test = TestBits()\n",
+    "    test.test_bits_to_flip()\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_bits_to_flip\n"
+     ]
+    }
+   ],
+   "source": [
+    "%run -i test_bits_to_flip.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/bit_manipulation/bits_to_flip/test_bits_to_flip.py b/bit_manipulation/bits_to_flip/test_bits_to_flip.py
new file mode 100644
index 0000000..c2bbd0b
--- /dev/null
+++ b/bit_manipulation/bits_to_flip/test_bits_to_flip.py
@@ -0,0 +1,21 @@
+from nose.tools import assert_equal
+
+
+class TestBits(object):
+
+    def test_bits_to_flip(self):
+        bits = Bits()
+        a = int('11101', base=2)
+        b = int('01111', base=2)
+        expected = 2
+        assert_equal(bits.bits_to_flip(a, b), expected)
+        print('Success: test_bits_to_flip')
+
+
+def main():
+    test = TestBits()
+    test.test_bits_to_flip()
+
+
+if __name__ == '__main__':
+    main()
\ No newline at end of file

From 6119c3043f1d41c71de70c1605bc9cd772728cc9 Mon Sep 17 00:00:00 2001
From: Donne Martin 
Date: Fri, 31 Mar 2017 04:46:14 -0400
Subject: [PATCH 65/90] Add bit_manipulation/__init__.py

---
 bit_manipulation/__init__.py | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 bit_manipulation/__init__.py

diff --git a/bit_manipulation/__init__.py b/bit_manipulation/__init__.py
new file mode 100644
index 0000000..e69de29

From 1e05d35fcada20b5b3fdfea3dac0861c9bad0353 Mon Sep 17 00:00:00 2001
From: Donne Martin 
Date: Fri, 31 Mar 2017 04:49:08 -0400
Subject: [PATCH 66/90] Fix check balance challenge spacing

---
 .../check_balance/check_balance_solution.ipynb       | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/graphs_trees/check_balance/check_balance_solution.ipynb b/graphs_trees/check_balance/check_balance_solution.ipynb
index f08f038..5835927 100644
--- a/graphs_trees/check_balance/check_balance_solution.ipynb
+++ b/graphs_trees/check_balance/check_balance_solution.ipynb
@@ -104,16 +104,16 @@
    "source": [
     "class BstBalance(Bst):\n",
     "\n",
-    "    def _check_height(self, node):\n",
+    "    def _check_balance(self, node):\n",
     "        if node is None:\n",
     "            return 0\n",
-    "        left_height = self._check_height(node.left)\n",
+    "        left_height = self._check_balance(node.left)\n",
     "        if left_height == -1:\n",
     "            return -1\n",
-    "        right_height = self._check_height(node.right)\n",
+    "        right_height = self._check_balance(node.right)\n",
     "        if right_height == -1:\n",
     "            return -1\n",
-    "        diff = abs(left_height-right_height)\n",
+    "        diff = abs(left_height - right_height)\n",
     "        if diff > 1:\n",
     "            return -1\n",
     "        return 1 + max(left_height, right_height)\n",
@@ -121,7 +121,7 @@
     "    def check_balance(self):\n",
     "        if self.root is None:\n",
     "            raise TypeError('root cannot be None')\n",
-    "        height = self._check_height(self.root)\n",
+    "        height = self._check_balance(self.root)\n",
     "        return height != -1"
    ]
   },
@@ -235,7 +235,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.5.0"
+   "version": "3.4.3"
   }
  },
  "nbformat": 4,

From efe9d3e20442300d738197c9529a6e6f1e80d27b Mon Sep 17 00:00:00 2001
From: Donne Martin 
Date: Fri, 31 Mar 2017 04:51:02 -0400
Subject: [PATCH 67/90] Update README resources

---
 README.md | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/README.md b/README.md
index 1b9fe49..2c5b7cf 100644
--- a/README.md
+++ b/README.md
@@ -407,6 +407,11 @@ Review the [Contributing Guidelines](https://github.com/donnemartin/interactive-
 
 * [Cracking the Coding Interview](http://www.amazon.com/Cracking-Coding-Interview-Programming-Questions/dp/098478280X) | [GitHub Solutions](https://github.com/gaylemcd/ctci)
 * [Programming Interviews Exposed](http://www.amazon.com/gp/product/1118261364/)
+* [The Algorithm Design Manual](http://www.amazon.com/Algorithm-Design-Manual-Steve-Skiena/dp/0387948600) | [Solutions](http://www.algorithm.cs.sunysb.edu/algowiki/index.php/The_Algorithms_Design_Manual_(Second_Edition))
+* [CareerCup](http://www.careercup.com/)
+* [Quora](http://www.quora.com/)
+* [HackerRank](https://www.hackerrank.com)
+* [LeetCode](https://leetcode.com/)
 
 ### Images
 

From 9ef693852c235bdd2e2a0637596aaf68f68f5248 Mon Sep 17 00:00:00 2001
From: Donne Martin 
Date: Fri, 31 Mar 2017 04:52:07 -0400
Subject: [PATCH 68/90] Move README Future Development section

---
 README.md | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/README.md b/README.md
index 2c5b7cf..07b3694 100644
--- a/README.md
+++ b/README.md
@@ -66,14 +66,6 @@ Each challenge has two notebooks, a **challenge notebook** for you to solve and
 * [Challenge Notebook] Unit test for your code.  Expected to fail until you solve the challenge.
 * [Solution Notebook] Unit test for the reference solution(s).
 
-## Future Development
-
-Challenges, solutions, and unit tests are presented in the form of **IPython/Jupyter Notebooks**.
-
-* Notebooks currently contain mostly Python solutions (tested on both Python 2.7 and Python 3.4), but can be extended to include [44 supported languages](https://github.com/ipython/ipython/wiki/IPython-kernels-for-other-languages)
-* Repo will be **continually updated** with new solutions and challenges
-* [Contributions](#contributing) are welcome!
-
 ## Index
 
 ### Challenges Categories
@@ -392,6 +384,14 @@ To **debug** your solution with pdb, refer to the following [ticket](https://git
 
 Note: If your solution is different from those listed in the Solution Notebook, consider submitting a pull request so others can benefit from your work.  Review the [Contributing Guidelines](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) for details.
 
+## Future Development
+
+Challenges, solutions, and unit tests are presented in the form of **IPython/Jupyter Notebooks**.
+
+* Notebooks currently contain mostly Python solutions (tested on both Python 2.7 and Python 3.4), but can be extended to include [44 supported languages](https://github.com/ipython/ipython/wiki/IPython-kernels-for-other-languages)
+* Repo will be **continually updated** with new solutions and challenges
+* [Contributions](#contributing) are welcome!
+
 ## Contributing
 
 Contributions are welcome!

From f526daff81e7f75186af25b0c9af17215e3c6b27 Mon Sep 17 00:00:00 2001
From: Donne Martin 
Date: Fri, 31 Mar 2017 04:52:55 -0400
Subject: [PATCH 69/90] Update README mentions of Python 3.4 to 3.x

---
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 07b3694..f105b8b 100644
--- a/README.md
+++ b/README.md
@@ -358,7 +358,7 @@ More information on Nose can be found [here](https://nose.readthedocs.org/en/lat
 
 ### Notebooks
 
-Challenges are provided in the form of **IPython/Jupyter Notebooks** and have been **tested with Python 2.7 and Python 3.4**.
+Challenges are provided in the form of **IPython/Jupyter Notebooks** and have been **tested with Python 2.7 and Python 3.x**.
 
 *If you need to install IPython/Jupyter Notebook, see the [Notebook Installation](#notebook-installation) section.*
 
@@ -388,7 +388,7 @@ Note: If your solution is different from those listed in the Solution Notebook,
 
 Challenges, solutions, and unit tests are presented in the form of **IPython/Jupyter Notebooks**.
 
-* Notebooks currently contain mostly Python solutions (tested on both Python 2.7 and Python 3.4), but can be extended to include [44 supported languages](https://github.com/ipython/ipython/wiki/IPython-kernels-for-other-languages)
+* Notebooks currently contain mostly Python solutions (tested on both Python 2.7 and Python 3.x), but can be extended to include [44 supported languages](https://github.com/ipython/ipython/wiki/IPython-kernels-for-other-languages)
 * Repo will be **continually updated** with new solutions and challenges
 * [Contributions](#contributing) are welcome!
 

From 1483a530110d6958bde9c7cb69ff2ef8ad4b4528 Mon Sep 17 00:00:00 2001
From: Donne Martin 
Date: Fri, 31 Mar 2017 04:58:38 -0400
Subject: [PATCH 70/90] Add new challenges to README Online Judges

---
 README.md | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/README.md b/README.md
index f105b8b..c219682 100644
--- a/README.md
+++ b/README.md
@@ -281,6 +281,20 @@ Each challenge has two notebooks, a **challenge notebook** for you to solve and
 
 | Challenge | Static Notebooks |
 |--------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|
+| Find the longest substring with at most k distinct chars | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/longest_substr_k_distinct/longest_substr_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/longest_substr_k_distinct/longest_substr_solution.ipynb) |
+| Find the highest product of three numbers | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/prod_three/prod_three_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/prod_three/prod_three_solution.ipynb) |
+| Maximize stocks profit from 1 buy and 1 sell | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/max_profit/max_profit_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/max_profit/max_profit_solution.ipynb) |
+| Move all zeroes in a list to the end | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/move_zeroes/move_zeroes_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/move_zeroes/move_zeroes_solution.ipynb) |
+| Find the products of every other int | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/mult_other_numbers/mult_other_numbers_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/mult_other_numbers/mult_other_numbers_solution.ipynb) |
+| Given a list of entries and exits, find the busiest period | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/busiest_period/busiest_period_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/busiest_period/busiest_period_solution.ipynb) |
+| Determine an island's perimeter | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/island_perimeter/island_perimeter_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/island_perimeter/island_perimeter_solution.ipynb) |
+| Format license keys | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/license_key/format_license_key_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/license_key/format_license_key_solution.ipynb) |
+| Find the longest absolute file path | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/longest_abs_file_path/longest_path_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/longest_abs_file_path/longest_path_solution.ipynb) |
+| Merge tuple ranges | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/merge_ranges/merge_ranges_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/merge_ranges/merge_ranges_solution.ipynb) |
+| Assign cookies | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/assign_cookies/assign_cookies_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/assign_cookies/assign_cookies_solution.ipynb) |
+| Determine if you can win in Nim | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/nim/nim_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/nim/nim_solution.ipynb) |
+| Check if a magazine could have been used to create a ransom note | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/ransom_note/ransom_note_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/ransom_note/ransom_note_solution.ipynb) |
+| Find the number of times a sentence can fit on a screen | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/sentence_screen_fit/sentence_screen_fit_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/sentence_screen_fit/sentence_screen_fit_solution.ipynb) |
 | Utopian tree | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/utopian_tree/utopian_tree_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/utopian_tree/utopian_tree_solution.ipynb) |
 | Maximizing xor | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/maximizing_xor/maximizing_xor_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/online_judges/maximizing_xor/maximizing_xor_solution.ipynb) |
 | Add a challenge | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) |

From 9843eff5c5006dd4ecde7c79ac45d1cd374de5c5 Mon Sep 17 00:00:00 2001
From: Donne Martin 
Date: Fri, 31 Mar 2017 04:59:21 -0400
Subject: [PATCH 71/90] Add new challenges to README Bit Manipulation

---
 README.md | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/README.md b/README.md
index c219682..5c09108 100644
--- a/README.md
+++ b/README.md
@@ -265,10 +265,15 @@ Each challenge has two notebooks, a **challenge notebook** for you to solve and
 
 | Challenge | Static Notebooks |
 |--------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|
-| Given a number between 0 and 1, print the binary representation | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) |
-| Determine the number of bits required to convert integer A to integer B | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) |
-| Swap odd and even bits in an integer with as few instructions as possible | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) |
-| Determine the number of 1 bits in the binary representation of a given integer | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) |
+| Implement common bit manipulation operations | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/bit_manipulation/bit/bit_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/bit_manipulation/bit/bit_solution.ipynb) |
+| Determine number of bits to flip to convert a into b | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/bit_manipulation/bits_to_flip/bits_to_flip_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/bit_manipulation/bits_to_flip/bits_to_flip_solution.ipynb) |
+| Draw a line on a screen | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/bit_manipulation/draw_line/draw_line_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/bit_manipulation/draw_line/draw_line_solution.ipynb) |
+| Flip a bit to maximize the longest sequence of 1s | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/bit_manipulation/flip_bit/flip_bit_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/bit_manipulation/flip_bit/flip_bit_solution.ipynb) |
+| Get the next largest and next smallest numbers | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/bit_manipulation/get_next/get_next_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/bit_manipulation/get_next/get_next_solution.ipynb) |
+| Merge two binary numbers | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/bit_manipulation/insert_m_into_n/insert_m_into_n_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/bit_manipulation/insert_m_into_n/insert_m_into_n_solution.ipynb) |
+| Swap odd and even bits in an integer | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/bit_manipulation/pairwise_swap/pairwise_swap_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/bit_manipulation/pairwise_swap/pairwise_swap_solution.ipynb) |
+| Print the binary representation of a number between 0 and 1 | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/bit_manipulation/print_binary/print_binary_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/bit_manipulation/print_binary/print_binary_solution.ipynb) |
+| Determine the number of 1s in the binary representation of a given integer | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) |
 | Add a challenge | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) |
 
 
From 6cb4a4d7fe5103b69cce2e572f0c3752cea1350a Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Fri, 31 Mar 2017 05:00:08 -0400 Subject: [PATCH 72/90] Add new challenges to README Math and Probability --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5c09108..d33f713 100644 --- a/README.md +++ b/README.md @@ -248,8 +248,13 @@ Each challenge has two notebooks, a **challenge notebook** for you to solve and | Challenge | Static Notebooks | |--------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------| +| Generate a list of primes | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/math_probability/generate_primes/check_prime_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/math_probability/generate_primes/check_prime_solution.ipynb) | +| Find the digital root | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/math_probability/add_digits/add_digits_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/math_probability/add_digits/add_digits_solution.ipynb) | +| Create a class supporting insert, max, min, mean, mode in O(1) | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/math_probability/math_ops/math_ops_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/math_probability/math_ops/math_ops_solution.ipynb) | +| Determine if a number is a power of two | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/math_probability/power_two/power_two_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/math_probability/power_two/power_two_solution.ipynb) | +| Add two numbers without the + or - sign | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/math_probability/sum_of_two/sum_of_two_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/math_probability/sum_of_two/sum_of_two_solution.ipynb) | +| Subtract two numbers without the + or - sign | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/math_probability/sub_two/sub_two_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/math_probability/sub_two/sub_two_solution.ipynb) | | Check if a number is prime | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) | -| Generate a list of primes | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) | | Determine if two lines on a Cartesian plane intersect | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) | | Using only add, implement multiply, subtract, and divide for ints | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) | | Find the kth number such that the only prime factors are 3, 5, and 7 | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) | From 03d8bdda37eab1821b3fc19b0a3a0956e75faabf Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Fri, 31 Mar 2017 05:00:44 -0400 Subject: [PATCH 73/90] Add new challenges to README Recursion and DP --- README.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d33f713..3e1f77d 100644 --- a/README.md +++ b/README.md @@ -226,13 +226,23 @@ Each challenge has two notebooks, a **challenge notebook** for you to solve and | Challenge | Static Notebooks | |--------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------| | Implement fibonacci recursively, dynamically, and iteratively | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/fibonacci/fibonacci_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/fibonacci/fibonacci_solution.ipynb) | -| Implement the Towers of Hanoi with 3 towers and N disks | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/hanoi/hanoi_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/hanoi/hanoi_solution.ipynb) | -| Find the number of ways to represent n cents given an array of coins | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/coin_change_ways/coin_change_ways_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/coin_change_ways/coin_change_ways_solution.ipynb) | +| Maximize items placed in a knapsack | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/knapsack_01/knapsack_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/knapsack_01/knapsack_solution.ipynb) | +| Maximize unbounded items placed in a knapsack | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/knapsack_unbounded/knapsack_unbounded_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/knapsack_unbounded/knapsack_unbounded_solution.ipynb) | +| Find the longest common substring | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/longest_common_substring/longest_common_substr_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/longest_common_substring/longest_common_substr_solution.ipynb) | +| Find the longest increasing subsequence | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/longest_inc_subseq/longest_inc_subseq_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/longest_inc_subseq/longest_inc_subseq_solution.ipynb) | +| Minimize the cost of matrix multiplication | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/matrix_mult/find_min_cost_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/matrix_mult/find_min_cost_solution.ipynb) | +| Maximize stock prices given k transactions | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/max_profit_k/max_profit_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/max_profit_k/max_profit_solution.ipynb) | +| Find the minimum number of ways to represent n cents given an array of coins | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/coin_change_min/coin_change_min_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/coin_change_min/coin_change_min_solution.ipynb) | +| Find the unique number of ways to represent n cents given an array of coins | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/coin_change/coin_change_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/coin_change/coin_change_solution.ipynb) | | Print all valid combinations of n-pairs of parentheses | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/n_pairs_parentheses/n_pairs_parentheses_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/n_pairs_parentheses/n_pairs_parentheses_solution.ipynb) | +| Navigate a maze | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/grid_path/grid_path_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/grid_path/grid_path_solution.ipynb) | +| Print all subsets of a set | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/power_set/power_set_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/power_set/power_set_solution.ipynb) | +| Print all permutations of a string | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/permutations/permutations_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/permutations/permutations_solution.ipynb) | +| Find the magic index in an array | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/magic_index/magic_index_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/magic_index/magic_index_solution.ipynb) | +| Find the number of ways to run up n steps | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/steps/steps_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/steps/steps_solution.ipynb) | +| Implement the Towers of Hanoi with 3 towers and N disks | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/hanoi/hanoi_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/hanoi/hanoi_solution.ipynb) | | Implement factorial recursively, dynamically, and iteratively | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) | | Perform a binary search on a sorted array of integers | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) | -| Print all subsets of a set | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) | -| Print all permutations of a string | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) | | Print all combinations of a string | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) | | Implement a paint fill function | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) | | Find all permutations to represent n cents, given 1, 5, 10, 25 cent coins | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) | From dbc11efce4cf7ece0863fe5bb302d3d2aa4ca59e Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Fri, 31 Mar 2017 05:01:24 -0400 Subject: [PATCH 74/90] Add new challenges to README Sorting and Searching --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e1f77d..8196497 100644 --- a/README.md +++ b/README.md @@ -207,11 +207,16 @@ Each challenge has two notebooks, a **challenge notebook** for you to solve and | Implement insertion sort | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/sorting_searching/insertion_sort/insertion_sort_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/sorting_searching/insertion_sort/insertion_sort_solution.ipynb) | | Implement quick sort | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/sorting_searching/quick_sort/quick_sort_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/sorting_searching/quick_sort/quick_sort_solution.ipynb) | | Implement merge sort | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/sorting_searching/merge_sort/merge_sort_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/sorting_searching/merge_sort/merge_sort_solution.ipynb) | +| Implement radix sort | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/sorting_searching/radix_sort/radix_sort_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/sorting_searching/radix_sort/radix_sort_solution.ipynb) | +| Sort an array of strings so all anagrams are next to each other | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/sorting_searching/anagrams/anagrams_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/sorting_searching/anagrams/anagrams_solution.ipynb) | +| Find an item in a sorted, rotated array | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/sorting_searching/rotated_array_search/rotated_array_search_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/sorting_searching/rotated_array_search/rotated_array_search_solution.ipynb) | +| Search a sorted matrix for an item | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/sorting_searching/search_sorted_matrix/search_sorted_matrix_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/sorting_searching/search_sorted_matrix/search_sorted_matrix_solution.ipynb) | +| Find an int not in an input of n integers | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/sorting_searching/new_int/new_int_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/sorting_searching/new_int/new_int_solution.ipynb) | +| Given sorted arrays A, B, merge B into A in sorted order | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/sorting_searching/merge_into/merge_into_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/sorting_searching/merge_into/merge_into_solution.ipynb) | | Implement a stable selection sort | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) | | Make an unstable sort stable | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) | | Implement an efficient, in-place version of quicksort | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) | | Given two sorted arrays, merge one into the other in sorted order | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) | -| Sort an array of strings so all anagrams are next to each other | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) | | Find an element in a rotated and sorted array of integers | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) | | Add a challenge | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) | From bd16c53de7c6cdb9c4c185638d532d5890f7f96a Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Fri, 31 Mar 2017 05:02:03 -0400 Subject: [PATCH 75/90] Add new challenges to README Graphs and Trees --- README.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8196497..42bd105 100644 --- a/README.md +++ b/README.md @@ -181,16 +181,19 @@ Each challenge has two notebooks, a **challenge notebook** for you to solve and | Check if a binary tree is balanced | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/check_balance/check_balance_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/check_balance/check_balance_solution.ipynb) | | Determine if a tree is a valid binary search tree | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/bst_validate/bst_validate_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/bst_validate/bst_validate_solution.ipynb) | | Find the in-order successor of a given node in a binary search tree | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/bst_successor/bst_successor_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/bst_successor/bst_successor_solution.ipynb) | +| Find the second largest node in a binary search tree | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/bst_second_largest/bst_second_largest_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/bst_second_largest/bst_second_largest_solution.ipynb) | +| Find the lowest common ancestor | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/tree_lca/tree_lca_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/tree_lca/tree_lca_solution.ipynb) | +| Invert a binary tree | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/invert_tree/invert_tree_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/invert_tree/invert_tree_solution.ipynb) | | Implement a binary search tree | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/bst/bst_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/bst/bst_solution.ipynb) | +| Implement a min heap | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/min_heap/min_heap_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/min_heap/min_heap_solution.ipynb) | +| Implement a trie | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/trie/trie_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/trie/trie_solution.ipynb) | | Implement depth-first search on a graph | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/graph_dfs/dfs_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/graph_dfs/dfs_solution.ipynb) | | Implement breadth-first search on a graph | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/graph_bfs/bfs_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/graph_bfs/bfs_solution.ipynb) | | Determine if there is a path between two nodes in a graph | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/graph_path_exists/path_exists_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/graph_path_exists/path_exists_solution.ipynb) | | Implement a graph | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/graph/graph_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/graph/graph_solution.ipynb) | -| Print a tree using pre-order traversal without recursion | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) | -| Determine the lowest common ancestor of two nodes | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) | -| Transform a binary tree into a heap | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) | -| Implement a right and left rotation on a tree | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) | -| Check if a binary tree is binary search tree | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) | +| Find a build order given a list of projects and dependencies. | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/graph_build_order/build_order_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/graph_build_order/build_order_solution.ipynb) | +| Find the shortest path in a weighted graph. | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/graph_shortest_path/graph_shortest_path_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/graph_shortest_path/graph_shortest_path_solution.ipynb) | +| Find the shortest path in an unweighted graph. | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/graph_shortest_path_unweighted/shortest_path_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/graph_shortest_path_unweighted/shortest_path_solution.ipynb) | | Add a challenge | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) |
From fa67a106ad5c1da87d8baeee6499aa19e800cbf4 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Fri, 31 Mar 2017 05:03:25 -0400 Subject: [PATCH 76/90] Add new challenges to README Stacks and Queues --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 42bd105..1171072 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,7 @@ Each challenge has two notebooks, a **challenge notebook** for you to solve and | Sort a stack using another stack as a buffer | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/stacks_queues/sort_stack/sort_stack_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/stacks_queues/sort_stack/sort_stack_solution.ipynb) | | Implement a stack | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/stacks_queues/stack/stack_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/stacks_queues/stack/stack_solution.ipynb) | | Implement a queue | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/stacks_queues/queue_list/queue_list_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/stacks_queues/queue_list/queue_list_solution.ipynb) | +| Implement a priority queue backed by an array | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/arrays_strings/priority_queue/priority_queue_challenge)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/arrays_strings/priority_queue/priority_queue_solution.ipynb) | | Add a challenge | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) |
From 9d2f4e5ce96e09e5532fca3be5f8f7bac6d98999 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Fri, 31 Mar 2017 05:04:19 -0400 Subject: [PATCH 77/90] Add new challenges to README Arrays and Strings --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1171072..c43bae4 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,8 @@ Each challenge has two notebooks, a **challenge notebook** for you to solve and | Determine if a string is a rotation of another | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/arrays_strings/rotation/rotation_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/arrays_strings/rotation/rotation_solution.ipynb) | | Compress a string | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/arrays_strings/compress/compress_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/arrays_strings/compress/compress_solution.ipynb) | | Reverse characters in a string | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/arrays_strings/reverse_string/reverse_string_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/arrays_strings/reverse_string/reverse_string_solution.ipynb) | +| Given two strings, find the single different char | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/arrays_strings/str_diff/str_diff_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/arrays_strings/str_diff/str_diff_solution.ipynb) | +| Find two indices that sum to a specific value | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/arrays_strings/two_sum/two_sum_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/arrays_strings/two_sum/two_sum_solution.ipynb) | | Implement a hash table | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/arrays_strings/hash_map/hash_map_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/arrays_strings/hash_map/hash_map_solution.ipynb) | | Implement fizz buzz | [Challenge](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/arrays_strings/fizz_buzz/fizz_buzz_challenge.ipynb)│[Solution](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/arrays_strings/fizz_buzz/fizz_buzz_solution.ipynb) | | Find the first non-repeated character in a string | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) | From 20308551f101db7f70f17c5888c74ac332e7be35 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Fri, 31 Mar 2017 05:05:50 -0400 Subject: [PATCH 78/90] Center README challenge category header images --- README.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c43bae4..bab68f4 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ Each challenge has two notebooks, a **challenge notebook** for you to solve and | Add a challenge | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) |
-

+


@@ -147,7 +147,7 @@ Each challenge has two notebooks, a **challenge notebook** for you to solve and | Add a challenge | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) |
-

+


@@ -167,7 +167,7 @@ Each challenge has two notebooks, a **challenge notebook** for you to solve and | Add a challenge | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) |
-

+


@@ -200,7 +200,7 @@ Each challenge has two notebooks, a **challenge notebook** for you to solve and | Add a challenge | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) |
-

+


@@ -227,7 +227,7 @@ Each challenge has two notebooks, a **challenge notebook** for you to solve and | Add a challenge | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) |
-

+


@@ -260,7 +260,7 @@ Each challenge has two notebooks, a **challenge notebook** for you to solve and | Add a challenge | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) |
-

+


@@ -282,7 +282,7 @@ Each challenge has two notebooks, a **challenge notebook** for you to solve and | Add a challenge | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) |
-

+


@@ -303,7 +303,7 @@ Each challenge has two notebooks, a **challenge notebook** for you to solve and | Add a challenge | [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md)│[Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) |
-

+


@@ -419,6 +419,7 @@ $ jupyter notebook ``` This will launch your web browser with the list of challenge categories: + * Navigate to the **Challenge Notebook** you wish to solve * Run the cells within the challenge notebook (Cell->Run All) * This will result in an expected unit test error @@ -442,6 +443,7 @@ Challenges, solutions, and unit tests are presented in the form of **IPython/Jup Contributions are welcome! Review the [Contributing Guidelines](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) for details on how to: + * Submit issues * Add solutions to existing challenges * Add new challenges From 92cc4a60aca817ba23ff9d55d03d4ce67f8aaa19 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Fri, 31 Mar 2017 05:06:40 -0400 Subject: [PATCH 79/90] Tweak README Future Development section --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bab68f4..3d2c0e9 100644 --- a/README.md +++ b/README.md @@ -434,9 +434,9 @@ Note: If your solution is different from those listed in the Solution Notebook, Challenges, solutions, and unit tests are presented in the form of **IPython/Jupyter Notebooks**. -* Notebooks currently contain mostly Python solutions (tested on both Python 2.7 and Python 3.x), but can be extended to include [44 supported languages](https://github.com/ipython/ipython/wiki/IPython-kernels-for-other-languages) +* Notebooks currently contain mostly Python solutions (tested on both Python 2.7 and Python 3.x), but can be extended to include [40+ supported languages](https://github.com/ipython/ipython/wiki/IPython-kernels-for-other-languages) * Repo will be **continually updated** with new solutions and challenges -* [Contributions](#contributing) are welcome! +* [Contributions](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) are welcome! ## Contributing From 9db85a1391db2480a69e3f9c34a38cc54859cb2d Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Sat, 1 Apr 2017 04:06:18 -0400 Subject: [PATCH 80/90] Update README Challenge Categories --- README.md | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 3d2c0e9..da28a86 100644 --- a/README.md +++ b/README.md @@ -70,16 +70,21 @@ Each challenge has two notebooks, a **challenge notebook** for you to solve and ### Challenges Categories -* [Arrays and Strings](#arrays-and-strings) -* [Linked Lists](#linked-lists) -* [Stacks and Queues](#stacks-and-queues) -* [Graphs and Trees](#graphs-and-trees) -* [Sorting](#sorting) -* [Recursion and Dynamic Programming](#recursion-and-dynamic-programming) -* [Bit Manipulation](#bit-manipulation) -* [Scalability and Memory Limits](#scalability-and-memory-limits) -* [Concurrency](#concurrency) -* [Online Judges](#online-judges) +**Format**: Challenge Category - Number of Challenges + +* [Arrays and Strings](#arrays-and-strings) - 10 +* [Linked Lists](#linked-lists) - 8 +* [Stacks and Queues](#stacks-and-queues) - 8 +* [Graphs and Trees](#graphs-and-trees) - 21 +* [Sorting](#sorting) - 10 +* [Recursion and Dynamic Programming](#recursion-and-dynamic-programming) - 17 +* [Mathematics and Probability](#mathematics-and-probability) - 6 +* [Bit Manipulation](#bit-manipulation) - 8 +* [Online Judges](#online-judges) - 16 +* [System Design](https://github.com/donnemartin/system-design-primer#system-design-interview-questions-with-solutions) - 8 +* [Object Oriented Design](https://github.com/donnemartin/system-design-primer#object-oriented-design-interview-questions-with-solutions) - 8 + +**Total number of challenges: 120** ### Installing and Running Challenges From df7c426a98dfd53eee00fe96f91a72b7fdc7bf2c Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Sat, 1 Apr 2017 04:07:55 -0400 Subject: [PATCH 81/90] Add README data structures ref implementation --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index da28a86..8822358 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,20 @@ Each challenge has two notebooks, a **challenge notebook** for you to solve and **Total number of challenges: 120** +### Reference Implementations: Data Structures + +Unit tested, fully functional implementations of the following data structures: + +* [Linked List](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/linked_lists/linked_list/linked_list_solution.ipynb) +* [Stack](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/stacks_queues/stack/stack_solution.ipynb) +* [Queue](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/stacks_queues/queue_list/queue_list_solution.ipynb) +* [Binary Search Tree](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/bst/bst_solution.ipynb) +* [Graph](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/graph/graph_solution.ipynb) +* [Min Heap](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/min_heap/min_heap_solution.ipynb) +* [Trie](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/trie/trie_solution.ipynb) +* [Priority Queue](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/arrays_strings/priority_queue/priority_queue_solution.ipynb) +* [Hash Map](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/arrays_strings/hash_map/hash_map_solution.ipynb) + ### Installing and Running Challenges * [Repo Structure](#repo-structure) From 76e26ae1b0a00e687d8cafbbb44c901238e9b290 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Sat, 1 Apr 2017 04:08:40 -0400 Subject: [PATCH 82/90] Add README algorithms ref implementation --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index 8822358..32079e1 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,26 @@ Unit tested, fully functional implementations of the following data structures: * [Priority Queue](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/arrays_strings/priority_queue/priority_queue_solution.ipynb) * [Hash Map](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/arrays_strings/hash_map/hash_map_solution.ipynb) +### Reference Implementations: Algorithms + +Unit tested, fully functional implementations of the following algorithms: + +* [Selection Sort](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/sorting_searching/selection_sort/selection_sort_solution.ipynb) +* [Insertion Sort](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/sorting_searching/insertion_sort/insertion_sort_solution.ipynb) +* [Quick Sort](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/sorting_searching/quick_sort/quick_sort_solution.ipynb) +* [Merge Sort](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/sorting_searching/merge_sort/merge_sort_solution.ipynb) +* [Radix Sort](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/sorting_searching/radix_sort/radix_sort_solution.ipynb) +* [Topological Sort](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/graph_build_order/build_order_solution.ipynb) +* [Tree Depth-First Search (Pre-, In-, Post-Order)](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/tree_dfs/dfs_solution.ipynb) +* [Tree Breadth-First Search](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/tree_bfs/bfs_solution.ipynb) +* [Graph Depth-First Search](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/graph_dfs/dfs_solution.ipynb) +* [Graph Breadth-First Search](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/graph_bfs/bfs_solution.ipynb) +* [Dijkstra's Shortest Path](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/graph_shortest_path/graph_shortest_path_solution.ipynb) +* [Unweighted Graph Shortest Path](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/graphs_trees/graph_shortest_path_unweighted/shortest_path_solution.ipynb) +* [Knapsack 0/1](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/knapsack_01/knapsack_solution.ipynb) +* [Knapsack Unbounded](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/knapsack_unbounded/knapsack_unbounded_solution.ipynb) +* [Sieve of Eratosthenes](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/math_probability/generate_primes/check_prime_solution.ipynb) + ### Installing and Running Challenges * [Repo Structure](#repo-structure) From 3acc09762ecc3e22cfe3fd7f90e0bc342b458940 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Sat, 1 Apr 2017 04:09:01 -0400 Subject: [PATCH 83/90] Add README TODO ref implementation --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 32079e1..8658dad 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,21 @@ Unit tested, fully functional implementations of the following algorithms: * [Knapsack Unbounded](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/recursion_dynamic/knapsack_unbounded/knapsack_unbounded_solution.ipynb) * [Sieve of Eratosthenes](http://nbviewer.ipython.org/github/donnemartin/interactive-coding-challenges/blob/master/math_probability/generate_primes/check_prime_solution.ipynb) +### Reference Implementations: TODO + +* [A*](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) +* [Bellman-Ford](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) +* [Bloom Filter](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) +* [Convex Hull](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) +* [Fisher-Yates Shuffle](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) +* [Kruskal's](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) +* [Max Flow](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) +* [Prim's](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) +* [Rabin-Karp](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) +* [Traveling Salesman](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) +* [Union Find](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) +* [Contribute](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) + ### Installing and Running Challenges * [Repo Structure](#repo-structure) From 301e9905e02f8aa05679632029b9c0bad8941a0e Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Sat, 1 Apr 2017 04:10:44 -0400 Subject: [PATCH 84/90] Add Anki Coding flashcard deck --- anki_cards/Coding.apkg | Bin 0 -> 653326 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 anki_cards/Coding.apkg diff --git a/anki_cards/Coding.apkg b/anki_cards/Coding.apkg new file mode 100644 index 0000000000000000000000000000000000000000..9585c36184f3b939562745b54950a23976b55d22 GIT binary patch literal 653326 zcmZs?by!?K@GnfU;;zLVin~M6;?@Gii(7Fh&f-$sixe;JQk=!1#obvbuDiJJ-tG6j z_ddV-JntWyIg^~sCz+E>lF6)=DiSgg0s;aWf@XE7is%Ohh464h1cZ7T1Uz`IrOTHu z)|Q_3F3w!$&W`qcr-7KCsTA5F%B#)4eKRuA=($2tkW=PzyK;omrRa22j>2?SQ=BJP zQmZ#k^UlwIhiN&zDs=ZHB9cKqMUgKFE!Q7>UssN%bFmqEcvDGxUpzX8f%eV#;UH&a zrRl)RdrJI(aU&#ed_0K%YC;$!(UbMiW8lB3V!HReoDsL-?c8zpPG!04k3Jr*%oEnk zs=xV%V9M({#FwjL0KW;k{PdW~gy#b#C; zPm%%Wt6pRoXvm$ywFV3}4MrBJjQTDu!YTINKglcarS;Sk(DX~?XQ+cKt;e`Or}Xfr zjBeq5!0n4P#r<>CCa=LK%%Ox7Xf7oiusL5%fRIFtUf6Z7G~jnvZAp<(Lso1MVIW#` z&9*JW722IQUyX&3M2f?AgD*8Z`X$lBtu{Gkusw25FxquQ?~C%J-mY?~^Oc&y=65Cx z!E3!7J%QTjL48YYZRF3@tQbNZ`A(7ekD-za`(*YHc4XUMY?y52-k=Be<*n>Hm0K4J z(pfc~lvJ*aP{iGSL7Nl(&$dwJUWojUdu{_?ua!*Q}xhVRB?aF609_=P$H>gEG zZ|TSC#WB1h(@w!vl^5goJTDZ-K*#)0qtc(8LVl2=)0XF=wjcG$H*RG(9v``%yfDik zT~x231}o%mR0P%~!6$_?!FSnm3M=(*f7{25<5~ynKbE##o{~2uO5QGibKGfTxb#=rY;m_{#<)cvJc@ z)o7@cD>5U_>bty)63WltSiIfBI|~Mi${mfi4G*HOpZK@ykH5h`gxANRbOx)QB;;#= z^`$W#y1~?PqXWiwiJYH%XtE~|EmW<6+}u|m<*CLH-ZrI&Ck+bdX_+g>845*fpiX|z zLRSjMbop@V5mS<8g=n!RypBzqqd(p=7ygafE zq*|hG^*>AJPiXsmcK7|8I^DyI_WjFJy=|AC{D)fy<%%;K*?2Y1vhtIAI-Jl-EDDDf zrSCgY?bh|i?~-$5jkI#SX2CLi7=#K zAk#;#NPklD(@j)cUW7{y`UZnuxRH@7kx8mJx`j8(lEH9TiPGrpkbfGK6HP+mA{ zvAoJu-5=$)F|866B;QE2i$p#S{^I141zL!-n0bRc^sBj*7C7$odP_Z+iye(MOnt)W z*E>Tz?5(*r(?JXa0$o;%=vW8`r=0c!Dmp5 zt(Hw~zD)!>{j6gbWE67q@3U^3FD)#^x&PL=Ma0r|t-NynU>yI|iTq&blkwi5>z^gJ zuS(j(k&hHJAUZv&!F&S|-PY=31+ zSte4Y;F!!3?5wtsFuI^i$sRl2lohxDOahatB%k z&Mh*_J(k15u~-6LAJVA)Ml?7_M%q09FXNzHt&verk;@V0GQDpttB)HD=&BbN0 z)3B+~ijM)Z!o zZQ)JH0N!y___@i%S)rDrKY#62xXRBemGbtvl56=dw=K#g>hzlQV!V^u^YIhQ3u3^U z;-;}_8`Ym^+&J^cobxN8zIqvoL>i_J<@rKia1D5VPU51I@S0<0#iE}s-^za#@yFtH zHTH2u8QV!%i%=!-zrZ8uUn%yq5@NVUJ{+>kOJzlgDnQ%akYSCyl1G~7Q!CKor}o|< zHN&Fq+y7cHUtdqJc12_H-apEm*6GY{q~2A28kHuj=3QdR3by$#5^PH!o7FI?d z`o-)9v<+MV)WF}LV(MOc>+X>~5OCLh?%#Ju0g?d~_kf>z zwmV0>JHbsoRRQ9G15&Tzjny^`a(TuJ$)m0H+}vuNU(E^h-?;27FfVu6>3lR;jZ|z; z&1o?D%vb$`a>zN#D5k+e3!Qhz$8&x7H~rrr0f;Jn>p-_FbzT5TJLxc)#M90Euxv{# zi7fq_!gunpqJ_qJZdbVtHSTN&5wb3FlCQmtS^6W*fkE%WVtHOHu~a7i@QITci_`(| z-=geAK?g|!2eF;$xQO#vuIFXr_xsR*qg0r~ctvv6aW*33Ezv}TL8M!5)GYwj`rZ=R zp;jy7fbtr&p;j9s2}8?25-i*Wgd4wWCTeCLwAnch^;J1Yc*?7UvE840X^CdhBR-F> z@{Lqip@rLX3!3wgP>EV5TW)aX6nhZx9)-hPD@=S_Wj#DdxkfNzYq9FTn;~#y=M-mj zy#uaV;ydJhv7sHZk$Pvi(39_k-V$CtU0~+qzzvZJSyEIHaLQ{tnSFS=?ef{W-3*4W z?pX^xs@yr9C*La_K%%6c6KA;b^)v0n6plfQ&_5_A-qjpS4Zy9rT|SLHt9gIX$i$*7 zg6&W~D<2da^=rD?#`-i%#%t}gc^Qqxccp`IF@`VqCf>h_^#G@e5x*Wa6PCRQ9K3ja z^rk{NSyV{{$PK%(PLfQLR}vF19=aqPEm!aQTmmAH5uA;9Vr2MC%l3j2vP43tjEA>7yfQajAWCAZX0{u6A?r_qY zPSkhBuO<42Zr)sW--}eXpD675pTEI#G<(-$OPwDdpb1{PTK!>W92M|78N4{PNDHzY zy?gK%O9H|vN-V}v6(p+|Xu~hy%85_MD zKC4Os!!-~ny&ez5elT74XTA)ielYbZfNPr;dZ<(VHW0_jf6NZ5UN>v+ba&c|A?DV7em}&*E~V!dcXC(j zpQ@><<+Zu`LVl-RTUmLWl}(c8hde>q2Zkqv8xLZuk7P6@Vm;6 z-L?6Ki28#u}Fe_4!LMISgK~_+wR~A^ z8Pm&6W|Cbo@KKUm`FeQCVO+Iiw^VaZzD`n&;OMX$TE{R!7!TV*Q|T@SV$X|*;aN~q zn;efDc|R9>vosB+R2p1rmw3%6m1L88T`))J&_lSGK$IzH*oONt!#(Z{HS;buI+yn8 ze&$m#Br%nmB{8M}2n07imFG9f@>75-0*~xO=Vx2@pBa<2VvkBGXz}^`O!3kBN7y&K zI%UT>f>N?_HNN!c+uOS-i<1@Tlgu$v{)q}d+KQ!fbM1T!9&I>hyz;ZH^H`sYHJMlp z!yY3QyQ|E|o0aCF7Vbq0J#Y~T-N&)iSMo1QKjR2!u9hVDVj(l|sV8{AaNAdwLReE? z@%W=IrKIE3JM(+l>f?e&UO%H4AJ?EaBr#l%)(%&CAhl@daLyCMevFXx6C&qP`AA_2 z=8ZO%&F5J82{TQuBZ+z67P0OwSlE6HSJzAF$Gq>Hb}`dfE_rr4n0FtfU~(ujiI>P6 zsN;ktJSV!@hc{(Ppc>!sdX8#}aYCnP<1D^k<9%^NjH+03@;(00C;K0Mn1a{M!+~e9 zW}Sl)Gnuf1?w9~6|Jx5TUE#c7NDp_Xqn+|*zCD#GMdIfc%e=#LFDkzx>6GMlW|7!5 z&$+;T%I&AwI#Z=1ZK=D$w<(AGnnr1!OJiph#qtvjhD9gd?j$jj(Q35FHH3!6Su7n_ zzlfVP(gBMUq?zFv9^ZV6)}lZKlo$PP#ZZ#I%CbvewXb@w;XfKn`f2VZzGYwXwHNQB zpOKiUS*lT~A!HIY)YQvjdF(&SEKA^M3R370H}uQ1&O5w%Jyqi6cO}2tX86O@=W~@p z5$^4bJlISIT?YQjG#9_7r^ikr%U5sCnI_HenvkBH^HRd+Fc(QR71>m5xk{G40O#w6vCW$II!XZO9yw>%g_vs;V2bkHLGOdim4PM+Uv zQAuOwX`7;$BxYDq$G2`jIg4=y`ed8U(&0Q17xN)La`TQ_kY+4!33_W7NF{`GXtFgo z^OOj45YY5gH!0rK@UZt@A_&|ehwEkG4w&~=>@eo`h0`JLu+kfrrh;}I?>e~An(&?F z@w$cK5g9T?l)}I}DKT^Yw3)}FXm>@Z6hIQAQLbXZOfNLsG3@l3BeI@d^oJkR-=1ZD zx^u6=;YwbAy*-EdvMXg)L+QR*X}2hbIsk06>X&s}Hbx^hA~91-s5hIE&>rR~dl&JB z?(568=asy8Yko$*e~_3Wt?05bD&(x~U;v@xjZ|Y6Fr_m8^nI{Ne_EKRL-gn6=j2f?_MX>K+ z4UxeAb4E_^_`k>Hgq&ZK$uO$_@9g~l@ecJF3VsAb@(-VI^S2}?{EP0C zrf8na9!w#UrxZ6*$K3<1BUc><6q}gc`By>x03aX(tOtGxLCSM{(eyRL>DmL#OP_)v zG)GS@U~14_Pfd?kj|%w1X1RBI>wx&!Rqa8B&fJ{a|LkUycOH4VE-}og;UZ_r1j2F8 z4%BM6VAp@IF#ugkhNWNwM|h-Vp_mKeZqMzb04Qdr1#J9V{GG(jAOo3=?a=T&;5-XA zXlaa?f|Tpw0AT)okC-AS&u=v9#g+k<>awYR<#L?uFL6Ty=%c*8A%={ejC)DD^6oaYOG%Ibbrc!TbQZhmO}e5l=hBM2h) zK3{a}>~ArO-SFyHnP~M8C+BviJH})r@gxucQ7qj!^Z==SQgtxX5Tl;~>Lyz*A(==( zTnQ>42}pj33+g%gI!POytMyh&@9jH*dbWD8dWmVO@1!D3uExDI-?w3%FhLm2Gi(RY z50it1K?9-PP>L7*2k9gImo6AR%n)V(r3SZ$(>|Nn9$l_Cb0iAeCxf_sLe2TkM25WYKE*=6-y6=2p8~)*~fBcINXnNW=HT}JP zTAvcGBD!+xMmlG%vJZU0qML9XyM)y(U8zp(3syfGHXkF1p6MxnCVELB5O&f?1yy#CTGDX)^zAbcV>Qd zDUc=OpHCywkJiU7e#dmcbHL?9rx##p9rx%V-*sff3-DP$+j(MV$1l%C$|xB!MYi}7 z)zhJJXV#=UMWcKh&<}!mZ6q@Kz<>AQu=y)5>69%m*igZCf~ytZ|EQL{a}9YUQh1=4xs;ki^AO6_7yBU$|OWCh?dNzT-l6~v$$+FN2P1}L(kLhgvjSLk5`A?f z!?+@S+}HzaYObiY5f@FWkgAA_SQS@h^#RByg4`=d^JgrA+;lkU$A6yXkuXL6Cq@0nY#h9e)DV!8bxZc8vhxk9)a$>F+ za3Bg%S0Z>5pRxZnu@U>9XhWobqTeHp?7&3}{>RY&OQrFUfQ)LJK_OYpBKVh= zj4xsjXI}6`oXgC8-XKq|x9|krAmMDn$uyD%$ewKTIGDi)j7lUS z$25(8+oRpq|HgTWdCDN~ zt7Z+$R?yT-%9H*%4TC*Q=uY<>-+M67)T^rl_-K3=dK|HUEtp>peLwNOQd+BN-2^OE z>KEDBxL=zMoPVyhF{(JQ{C6w!Z~B!+i#JwQb`*WsY&uCBa7wCm~1g5=Unq*$_MD@Na5y9q&3zOsi%Wr7l9xcoy5o=aS|>aLIFJez!)u;LH3k_V#4TzM-WOCq^Jc<%dXMwd9T zWVEZ{a$G{+O)@V;{G)hn8hV{>-eVv7Ws2eihDP{fn0%M8B9$s*faUz_=p=ZJ$kB)4 z^5U3s_~SRFC$?+R(9!$ha#8tlVMUba5|_A3LURz=PsuI2xJ!a$5ZNA`4v_bcIx@iW zBX;sjy!L|pv#{a>vpB+Vxq%g7FPXwuq)k4>2}Yw=!{y{w!dqxWy2YCKlP`e(GrIow z_20jNhJzaghIMMXwR2-sU)S8$UAWu;w^mNg_Qi)_GE>e|lvDWazyidpQP9<2Br z2Jvpwf8x5Nas^R}>_Wr9Q=3)YnfH@u5(m&^+xeYK>Z6xb81>Y_v&%gfj>Kusp7Vvi zzs9xnS?ADGIoK5BEs5**@9N-X_2py145trZ&v)L#d#eC8--`sT>XQAu?5+E`kmbbDmr8g}3^%&kcOoqM-=P=pT{s441yXC!ArXtLYk=qneqAWi>9-R9e$fJ7)UQI=ZQ{x_d(-+wM9M^L6%gq@o!r=YRK5l zTGnYSIyPeF$*(+MqkJMl0Yy)}znmMlzYj|%bfW<%dpEcFnt zcL_5SHk~s~<=Vb^f8SUdejYsLwX&9ni;s=S+2#C_zMFwqLhW-kum2kb1#QBN{6%zz zI#_Eg7q+6}`C91@QvUrPsUq$RNg^%__%p}O% z1gtj6AGNLesXR)^;y9549io53?^TZ_$3 z92q+hcTXI-ziz13lJ@Fb>o;fGVZ1B1*~vzjA?zi(-#J#7S4D_2eX3IPM5dj;kdI?} zgNSEHBe~1;_^Koh*h3Z&yiOfl=O{GdQ$$s8gLca;oNR>T)7%CHgIGvWX`9b9GyP(0 z5JFxq45-uXP`847&R1aXuMtO=#DgBMc#B98%vSC|{;75-@5=82YeS3)gQpksIZTqK zQC!=DfRHGCCeK&Xc96p}qtOinv!LZ>s_uwc z_|?|Q11SFjuuFgt?zd@h!0d-`zc!P(uF{O4^an`Ee+!n!3=RkK&o$-Py-I>q+4ck| z{DlI?yv{f`TK*Mz!<9QS`T)d@2NQ?1n!sJ>|9ge|A2x_mxW?FPXuQ}sq@FSS9TfIy zPr3F_Dtt5TkJQjh9wG`oc*ec=e$;;=2ho8BdL-e#3Y+{f|LM*pimSZsA3MI2SHZSr zwk=U^H15);n>(Aw&R;tna28K%Vit0>`O6;Lk6%}M>($lT*wAFZKu#r_S|^?J&a%B+ z7dH(W3~a}FM+G<4y$l?B`_}*$Pd0PKbzDg5#Fs_-)230%|Wq{`^R< zQdMUfq0@)ow!}Vx%skVoDl7eL6ui9Dy{rYF&Ccc~l!7n+UU>r^Ub5fYbj{bhOc`}b z?=1GQV{Hv$B;Y`KF#toFz8*ebnBGT$e?l^0^j5EM^*lZAn^>P~L6lB7i8ZH(5L*X` zOAkyZRaD|vfAK(F0py?e9Fe;E`Vcb4_S~sYX$gPt9?q~ngYrc{@RH|4h$`DNg;^DE z)QDcN=RFe<^aem>4}T5$2olToTm@b-=nW&Lqk>{#z-GfL;gb1E1^{XOup?5E>ps0` z&wB+TXoMt-VQ*MEJylOszcg$@b$q*ln1L#YE&{Mmju|wpm*9DiLIf@SF|ZF=#z?^d zsc%dBC!#=?RZ?)I^ZU*2Dh%KS-{1ub+jT+K)9`k296^2%K`Xia{)u9y>Iv&NyI3`Z z_y4DIluH>T^JOv=y8JB_2UOYzyf~*Kd5)}+ID*s=L4U{*{{uLDUfdU)bvjILKQnDWz?*00}#I-#;Ef)QO<& zj>zyDL+~0UkSPhNHA-ru_StRCr7g40tJo8@?HFPXDyRSkY^W~l3aKIG=Ya6M1m6=| zFz}{n|0Vg;%CHt zywg5MkTR|3x|eedIPRWml(fMZYyr?!`hCEX{j*p=O$MyWWy4+ygwJj+_N*)b6LvPU zhPO8Q2!a&>2n3}6`#7ZofbhRpe>a={DUOc{Dvtr{M>68v>Ql+Wfth2#)?r-y@J~JN zc~8jt4EHrpB@X_9m(Ur4KvbvA!F9ryYZn4AP{0ZzwUP4Nc49H)pJN%WMeVt5X{Bwz zLm3KOzkjlE031WGGHHIAsTg7*Dku^W6z)>qO{{4?f{bY0(?~nMN~~!+g1rB5Pt(Ke z03WNHp7+J9&;R_$@ZPwJJtq@E{S&hj^9NOOaRA5>uz&tzc*Ax*??s8A6jRavP;zl- z3LsBV5fcB`_Vl5dfF*#k>olW@#i=$`uRu>p3a}x)b59@K2;f=p zkWXmM))B<{+Z1P_ZVnvL7yTl%AJjF5v1uJ%S9)&@ofiFvkcT6k(+_?eIQlne@^Pe9 z!vK3{EE7wugDUwrWaZ* zq6G2Ps-YuA)@psx{8#>#J|6b>-!CTU3Df0U*#6*L^nVf__#?s2QP6+^;9Z(%78p^a zQ`u8oVAk7uJ?P9l9`lw(?#m|v#`c^e(q)Jc+Fdaasga13L4CC>S;lm4x{NAE;oWDY zZ?wp=PFHe;wTl^((>O$#Xf3TR1$8?u1G!i}&a0t4-%jyfi*TD0OO4MrS%fExB5cuP zeJp;}sJrq?QYL&vf!53zKe-OQi?5Zc$HhT0NDogd8y>xccU`JQPv4AU;ZQ7#d?09w zNmPzPKsNn}j$w5x+Z8|Wr}dx8;4W=FEfJy=t6l`1S9Ej>76lw7Iv=IFoZ|Y6R&z2N zk!N^|Wo5~U5bcST^@`uI7V4j8fgf`w1Dy*l?NHvX(D8aPbM73pgIx=r0paJ9ucm(B0YjM;T(T}zFMVaTnrHwNi=OC%uEmp9aPjd!y;eQ63C@>gWY!1ApZ zE3=*YuwoPs=kSP|T+JTCI*t-67iAi`)a<~wKWHr6q2Dju3NGP+O{g8*5kdH&-_L%N zy46~Sp6AU9rku#oQ@Ycsr3#bHNRGJc|x=UYfrZUO-XN*JLxjnnWqcAp(m0cecYNtIfB$E|lma|p%22f+*+W)EhJPjj!^b{{cEqPR9GQGaZ=j~X33%Bjsi0?iMhTLeoEsMklwis_pKds-enNnOU9*(bz(=@Ce_@N}W#wQ3ESIcL=XEcI2$%~5 zpZ{>d&@_#o-{z)=j8OCZl=ugow69dGAB#@uJY_*W|)pNHBrrPyvC(@Wrn~Aq^5=w}KpersQH8qj4!_R?zC|?^|!f z0)%A;Jb_NVlppHWt~mHy9KShEZ@u`@+%!mhk}c8bFd|fP0w=O_I(Z(dn;774-g>SG zmQB>IB$>Q_Jmq-$k!MmAFMig9*#OBm`HCEBvt*tB!wE3Bf{&KH@!YIFlm3gXgP<8x zaP45Ek(2XwkO5D8-HncnR11dZgF-uuT#KGS-;sq@7v3_($()~l=BBqV5UfWS%)>ok zkm7VY&lxI7Htt=Mn_p*q_JMO%heP{pGMGEAkCX5!lm^& z+?n$mp3j>QMY7cAjG`^HkpkC%Xv=w-1Q}lj9p0{f4%5!&*1KXqC}O_OJ5^?sFS}{G zl(S5qm@1;VP86g#6j+5Y92(mYNIQOw_>vE$SjyNW5i=lurr5T~C3}_6$Y*Ky&IVbJ zGP()L*nRuzuPiF-_1kEIXasXT8Edzq{;FpleODE$$hbvWw{Q%K7|3d&cet(rt%gMP zu(bvor?c1{ZMK51uDMS#`QJ!J-)beFWRSe3z2FI(VMkC_R@qHLvxy^BS^2om{4vU* zLDjU@fWQy20J4dQe-<+zfyPf~tzj;$V# zXBR`T8$OXGS(#E%{sB_yDe(<7Y|Ai9jQ;7(vQqk;%IjZj7=%GCutIl;YB#SvFLgg1 zt%5N|NohQ=%v8D3((5j>UCsMt0(oGFmyCdrjMtTcq`1GF;1X+Sx~r|%y{tV(nVuw-L-UkYxp$x0WQ<8do~pQuY0 zH2#DpdYCV~HIQpn5F9^#iTVM{KoKY;D1K>>#Z6}JSKve^Y@}Cvt>zfcqlZ4B9J=~` zJG@-CNU`R`M&bKzrH!%MX3yp-nMG!Z;Clmtgc3&8-St@&_gXdu<%xpfh@ybuGuj<= z5u|a9a59V4HUvz*xxXFnDA}}>%K=z(UJMoe%yWDPOI!}WDxWeCw`KJU?eSSnqhd?V zP1M*rp&96@YGO~NK6Rq^>gdG{^{01232Sg~DVj6~Q%Ku)?eRL>#E|sk*ZO2v8 zV?)tsHvW?=OYvodcbSRRr*GAPCm)~wX-F=Mvw7lsQ*Z1!dbJe#7#Pb(lK1lp@8;1j z-|rug&qe42@Ekwsq~Ou#XVD}E{xY$l&E&GA6yiU(%JNLRKG~jU)RVKg5JtsliE1?6 z)kVdT&5cj@^X6(L;_ZB>@W9)ZDFG6}-}ZiQEl_kc3YsbuJ4$|*V@Z;ki= zC~%`@TsMDoE%VeV_*=y4LveNnV7;OLe$RX~1Zj3~3^vl|Y z{+oXb#|`GXR_i1^RuxDvChGO?EWj*rO%P5CPZ=YZJxZ2`g1Op3M+pUjb{ls={Q#P2 z2s?qyB?c`4OgbrmO@bsY{Zd2hZI7T!MDXJN;$eVl3-aRv0p@%`*Rnvl0Y8S9EFD?X zuW{a!w}|Ges_JBM3L{pIDxZYjA23?zCMX@#_4?2FRA?X2oV` z_85v{R?NoxH!e7D>2&nt4k1g8S6Rm2%d&FJ{&KT)jry>MDq>*H*c{ECZ1b8Hjbzj( zB2!+(B0XHBJWqhnTyN<;Ixf9?0SO0w!WiQ_D?Co?ZR(R~#xYB*HSQr@#iV64!Xf6y zOTBmlroF^99(D-6UPM(-Lz^LeT6LP&AEgg0bNrQ|M@P+z*<*=HN>p1MQ)x575sZ?8 z5@?9T$Bo$u<16dS?gpq{sWUuLHL1)WO31!{F9`Y0nHS%Yto~a?QQ$KwK}762TEVP{ z!Kd%@($oVcHNj}3DPA=F3(?{ois}$f{ZnP6oz4^1Zocop{CQZSJ&n}&|35il~ z_2~TvBZir68TL0!s@*bja;Y1VYBmC_n3h^5T>Y+ydMe{F*pnQ#LcM{4YcggBD9!cn zF%89RG%bIPR%|cXd1(0l3PiqWR@r4S-HXiKpK5==E{<$XkaU;?qxC7Q1Q0^ z((mian!l!owq}YHRmR*XK{|Osl4{`8)Yg{1!dW`ml>m5p?4|u`mcNHzzra@Tf>JtY zD9ZlVDsVO*HJKL6Lo!&MExFh8ogJfQZG;Umd~V;-B@r(6`E4F44V^2|@}&R5G|$17 z{GDQiK23XRiQ8m9N7A{kT83}u#t_P=QH*1ku46Ow3gmAPI52SP|SfeErv?@O5t->bW|4s59F~3Tw4=0q?zF z);@u5kqKSVv<{IL6=)dmgqL8JW1&HO0+SIr899eiHNrwzxxNb-W#duso`BugWoq+1 zDLb*^pZtazJ2QMhYZHs=AK|*pr+KRua^8BJ$S3=&3SMa8_)O_B{Xc$H-*A=olGy=V zPq4xiC{PO(;}yuUwN*6YaNQu)X66wG=sR|OhAW}9N>J%@2&EcBE)V+E7V~A<`hL8< zBpcBkM>$WFb=G$y)+&a2i~R5EN(EB;e5y3^xMk&X#aPXsdatt( zEH+4M5zs>g5WNvad1p&h)IWjRca?EtRQ2Sj2~woAykXb@?PGW;6*n z_j%IvM`x3)&8@Pg!(*68)2}9tbtfGQ%e?uKaxpYr3S9m>%eMxU`#Ls5Dq6l|A_}U` zq8VytlEg>uW6)D_dO|}YqZIhremXPDd3@64IYA;>TWoT0`(&cXR6$FDO%7q=HA)_j z6P+LaV>eWyTlVS9)*d)f+y7E+oOfnxK(Thq{?W_*O?WTV3>}qz`X$HNGWjMhzx3`` z8jEly*e=cJV7y%_!al#EQL$H8uD$_ZFG*BLmfM&n=%ukmf_&vD6U8at9h{l{#;n~_ zJyfG%Z*!n-ZkMZ%npzyYbc2@O?c1la6lX0Qe{5vc*kO9{RrSTEfnW54o_1qIauG3- zz-o135)b|Lze%6n)rm`OHNq-noyIn1kxgu}P(Hk2syh9q_S}W_+QyUKNfLASiwKY8 z3d7+-95`>igZ%ja-_^ zaKeaZu1(KT5ip(nT=?P-~r zq$}T*uDwEiys>?G*?@j7t)!HHqP!@6tiB0#S+7tP_s_vm3ilw#r zq$dUKTep;|)w1bmAL6rmKht(+g}!0yuUxM7`ga`Qa!K`h*d8uj9o!ijSmmG&717@% zQ?|jw7D3HZm{B8jU>?$|C(O&5VwQ8?dhhQxX~2BpcivuaPVA1}^mfAfM{W&qp;$&? zJCD!`QQ3xhK9_IF@p^z>L?2Y z)FPva3n6-HKxB6TCVe4w5XAxkVaf-hLgEzA^dBeW84e?Z3mxX$c)mV_-!PVvk^U0@ zEL^)(3r1xtWcy%z3bM9esy^5)G7LP+2rTaIZ_oIDr7~({|0u!o|FS=&lqT%6n0T(f zEb2J1W#OJPLG@vW$2xsEV69`}*2dka!Ys^jBGBcHAbs8S5JB4S^Do^DHTYz@INP9% zzf#4k*}rjkX)SZsv+Fp}m2^t|B`3L@-cSh@nAj)f4KD4V%aR}dS}j>*W*zHm3J%D4 zU1U}9d1WkEfpc@^Z2SS4q1eL6-M^F73=+y6*1)vou}s#a!_RH-d;>f}^?C!mRx9g2a?KWFt(!@rtUuHHw~+yg6 z<5#8H?@N$X1^aKy^=QhGzT6*LlI60oEQ`(P^N=W&*b;vvjX`_THdWk+yJS)!+qUax z6p6r8r6G^V1KQB-mG{cG)}f3@7Dih3nP$T=ND3|~vysPF9eKYNYeut1H<<`s^_lPu zj&o#Ucj30ley|BhO7%)rB+Etw0`1m(9_kUUo26z3a4po)U*C%dpF~) zSHr2l_ZF$N!59StAuBedTIR-CY&4{IwrVa~oGBBDWxk{4oqGY5wT`HO`0TQBs1q%E zXw7ser%=4H0j{XgFMU+^gqHKXZlQ7ou~;vM1`;ds6hsG0gYomJb z>jXKrMW@!ksGSW`dY~VMSZ9uJQQ?&%HSqm{%Nf^leQ%OsDcqr+4(4ZR(6?){vmV=c z#8W9DM81|f>~PZ%nk_p#6q@~-sp%&>Ef0A!lV?FXM%cW*7&+|`ewrn}}L z9>75%X&TPwYix_6h`JLH_YNLA&P4%wYQV3lHzODU5bl&UL}bQ8_in%aF@387fSJQx z@_?$Bur7_50zn^Y**gVk@`c};UAE0G+je!?wr!)! zw!5mnvTfV8ZQC|a{r+cSCeFpg#9ZXQ$jrOUc=z6Gul2lt*R53&=gZbji?uRDH`%l{ z_vD>&7hD@Zq&b-}F84~y;X9=Jl9k1NW)E$v1?kA1%m$wp6bN8d;ti|7bOUemSZnEx zPeZgSI~+9{k+BNa)Y&~C1r%$MS|Uxs0V81SftfepTy-i^WkKDzeWE`z2C6&6en<)} z6BxxRii9JgkA}s`iq=Fn>F%0+tL`p2OPcp0vRai{-Gj*D?GfV69pRFc68;<_2}Bmo zQnY8rCxwIM>WYNujs23IG>u;{{aLDFb<9>3nvAmygiOHGnO#Ey)o!4&bc1=86eE#t zJiSSG&@!7*_6&q}JmC9UWHj3Pow0)D{-{n1e-I;YnR6b{5OfFfYW`G(L0m$yQ#OYS zEj5Q<+94N{Wjw^e2vj5#VCI!aRr~Lk@4fhQ$c)I5xn#|6dQ4m!?*`qGBka^cXWgQl zsjo^;TZIL)rx6!ph0HJJgrfu1f`y_{xhV|-?pnQdQGF91HL2a` z0@0j6k4UlTRlc614ly8_m9qvO_?n(Ixa~T_Wtc6~AU7nKa-7^fwJlg!QBBtJ+ z#)6EfZN?h99p`|O=ZA_W`pxlNupzK>r$D$WnJWAgokkAb8)5=@|FON&9}rEQobQY1 zUY^(U7}c+#G%d6J0j0hWYHiIy5>{v6^^>BE`^A_;ZjqJ}AWh`N*xDnlsJ%4uGHYAD z5J82}(AE=$ZV6_*!p|!PGrAddSSsxwNe!-2=T+@tIl(yfp}6r{*ia9R#O{$Iv)VzU z<1$HPzn5T{*a6|p{aL>8n&TFzHtXKwL(VfC{M(u#N#Gf_&W*SlKVPZJI{e@pz*I3! zmY&KpEG7fZ>>-mP&J8`#>H@H7V+J2DU5Z&+m2Kdxlcd8gCn=;@)k7KYY(_0x129IV zmmnR4v+fc3Q1AtrI-5vC{Txf^3lzOX#md$q;uH{ql+p4Zii-ZXtY<1ut5&(sKq1e} zo^z(kAx!GG5uH^6`|EE*hMkUDY^#bZ!I<44_#Q@BfZykP$o1E>4K_@o_N^d7X7^E( z%&hy>dO3VUTyM7df(hq6g7GBrSUP2N1Z+LUMPp?hPoR|CXku=71%FH&AaCxoFF9ut z#$rZ>lGKQ`WhfUJw#mi*saf1Yt_dja(N%?TH}Sa=TTo@<`KWiBqt{#f%k_XNY~w9y z^I#ZV+h_e00PJTm2W<0_B+SOXJ9U>Z3vd9{VKyp1y3H`V`%V14T>PNf&nMcMI9Ap= zk{%(DTO%V7YmKxd$ML6GUNLx|f=$arccs9x)7cih`_l>ij_Ld`kOZ znT8+==&82LZ>~$&X!WWtAr#o?9G5GcgOwEb>+6v!VvN_qH|=Isr+TLNu6NAJC{*z_ z2K6fOvqudRjRVt>aYdk2*wH~npsYeiHkH_{2bUsfk6mp3B}juu2|vPkQi?q%q#|Ew z-y)(DM{lztGj`-}xgk4?J_Hc{_%D=7sR=Vq?P0SclQ!<;vv|&v& z`5J)WpP&Yp$_AKQm+ZLcEZg^IRM=Yqvalp&yfM432vOS9>6j~%mY>L<-E?UJF?dHl ztdqNNvma_CQm>lus_Z%WR{fBu5p`c^Sz!RT7Fi7hQ5s%d(w@TzZlqRaUHCH2l z8V*j$pn}50?~fD^Bk$TTo`53zU0X)kZnYW|Oq4{?EF|^*$#13RSl(z=U^@6*=1q#^ zhGP94iz18m$VclgKID8t_1#n}A!Uk_eYFRSYPQB2GRA+)F+vOI-&uHODAgQrg$b*5 z==~rh_$C55F*ds}^Wp2LPy*3{AysvHSb%&4<9S9(AbH~H!}JM9)is06ZGezxrNhCn z+_2fAL^u*rIKjdzD2Lc)T~YJ(?{!KzEK)+rS>8DXu%Va;_$;ykaKU}~|CGm|lA^H2 zVu6x*%0=cq?VFO6E_rP%Rm!l zHaCk9*9?1IDh(3)a0i=A)0ZEwG`N?J$kirZaVoZsyNQT>sazh~|HxOzE$#?8Hr;h&Iv z$)Kh+KUO=+}qT9>8i z25Y0Mu3(ALYSgP9hV@%eNejFhwy8n-j5-kJbleh4dw4`#_Pt zy~imXFb-R=G65l8zP8Q>TISnW+SvmEri(*4OX9v)C3T#yao2O%31pMhVSQC)G{fRP z1*YTlE<|kYd}Y8PbM#?(r<}s5Q2x97e9C3v?B8a|jOzC&QoF2RB^~(mnMTc_@o4~= zPnn}U0a-2SR1Dc>p3h38y>Ak$^qG*_ESY`o?uKOd zD70JdWp=d@i}raMru#TK-D_FNwT%AyNd8iJ^?1$EJ(z;0N?|Id2#={6=np&yu>b$U zGI#-Dn)zLSSsIBX?5cjCep3@Y-)Wy>6NwPgZI_Ij@0a}Y>E4H{E z7sulWzSWfJTyuZ&m@OL{{Bm2($BE6^$|nskOKUor=9(9Je;wA}m_wRk5Ul3+)Ix2` zItsNzFY|2C4x3euC%mR6eg?3NSIG{~_-|Q70ar%JISrdot-IR6i~L;v2A8_eOmvE0 zP1vB5Rf~Mw>yw-rc>X!7jfLY?oHj0#As1`&W@e)<9xhOQE-PjjhP(Xj7mz>i&hv36 z*;v#L$NZVOXq#gbQCllIk({yZ@^+auZ!01|!`wf6xv4CST+-3Q)tNZCri z$=C#Sa$!f=O{{rp9XzP7^54o1o(C*TmluP>X{0u=V*{NLufTgW_m2 z{f~YlgDK0f#wlra0+kLYWQ@c~m#3kqD4>^CHIIE812zmt`3ucdWJVA?(;Ow(JZ~vu zCbS4C1^+;~zFP8KbLdt%ZbP0}Bt%8x-xBbfTn zw14`I^n5AE8E}Yp#B|6g`{JU_EOH?G`oOI5y?2!D^1tBUtFg^dMsWw=&)?_FCb5Mf zOaFx3^wk#Di7FyUQ{6Bpt=H7HIQ3pm9fG9faa}RMB}fpkuUYVovu-x8kF3 zW+}))OlmCf`cSCV1%8xj|L$JN0+K~6W`R8#Dx_zS0eA<07T~XhLKEJ;-qP3Oc!(eE zk$kv*@>k;a^w{UcU<GWX&zFCIvDZ+D0Y|&nxzHB#EfNs6)8pibRc@ zNmtUKuSV464bW0AGE;a#7p#I>-{J=8Q}CqA6(6bwJ1!@pC!6#@N~Cxub|oSb8*C*a z^E6thF@U*0$$pguKvT#W>>9hCZmZ;32 zuCoB|&vC%6BnQdYna|ELwI7)zkm=e1JWp6?R7yp5M$v{rVcS4Y_r1UxymUlQo97#? z;$)}hV6SHVt3jNLnm!<5XpoEVVaOe;1k;pTiz5xktIv}}C+cVb5odg$ zbxATSE7{eIsX$Wr`xZbb8w$ZeE~SPky{lppW5s5#FpYl3+*&qpiK$jAcKc}Bc!hSv zH-y#v`#4@B&O(m}?7M32^f{e+ijpvoySTcFjADwgw-HHsy1&A&x@$oyN1Zs_psLsH zbA^H$Vw(RgUhDSrJJq#y=KFtAb80SEYG#+~_h(i78AYXQS8d6gtTA6%e@BO89Za5Q z`VM0P{wf@Y-z|;*RhGv^OX!yYBlTq_3T7~k?GcIn8_a~BOOQW_x zGpJLVPo0ZkAe~_u9asp>w^Fp$0q6#A7=)fTMfHV4SKsw(`&0TSE&V;&K7B>Vd({Za zYi-PpSvTRVo~@ozzoKB39jxu>k&Fhgv+DnE8%Zdb*%d`|YrimB*$Q)bz5rV$>j*4Y zbgQ}kVXSSrTNT|QeR-7umAy=x7dwyJDaD}x@)#8Sd`KTh1R*dTF{qO!n zC>W%ieWTi-M+p8f^OL>*BL=~W!7ftteqa_RcYM;suFWOqa3*#=4)4sn48l^pk`8Qe ztzdigF4UFIlj`yL_c{*t1gTBFdvIFm(8|Kj080H5MXD*+t;zsZ8Hv|MzUQ|LHO{e# zXu{s*2cf&3zJV2UVnZW*3{E-fCb8w%a-(`+qIbUeqJfHCu-Y-z_iF`NsGBJrNR9KV zwUYVj&fl@SgIj`n%@ToSKcqzPFF|@}t+v$q9+Epw>nIIMiS@nOQFSQWoikAX)gcE) z%XoLlM7@7&Z9o?*I|$-r2zJagP;ja`vC0GC;z4K%6e%LLRw{Hw2d!ZmFm126IQY^8 z37eLt!6d~6*RoRt=OS56V~h7jM99Sq`sWapYq1N#phKXEI07>={$eC@zl_yz)brVx z9MeopvtUf#RfF+jytMv;6x)R7V2h`5=0g4}L{`POGJe~)>`Ow9?~H39fV(khKvkjS z7-6dbmgjoyk1weUdy8|E{?lVAST*Vh6a_~}+L$UxLl|zXR*HlZ-GdfJXcpc!q?~)ZogXursKTKo9`Rn4y$r(n$O}Wd!00#C)~tuDDKqM;tWt zpYV=2NMRBWOr3byQRGa zYIakxJ71+yte>!2D@&OpKtQ7tE706T4$Oc@WpY*+(3nj`BjN3n~DrHD?rOPqD*Ft;rY~dlHSlBQ2d2 z@(StBjx6g_s-1GIAdNLPiAhRA!I*v%{6ISzV#F9;b7HI`ZACUn^BZmNg$57C-a#V- z0~5KA9n*wBOx6#+nt>^@dN?18yu$_-ah7H$9-_<{B#CCD+ni|og&|*DaHAwR8%tqy z2}nHNUdlI2r9ber@KqxA6s5ELX6{ggwpH^JMkY_*g0Uc>(v2gra8y@;xX~RwM*b8s zF-D#17^ZK)ZipARF4m#8{P>-NOSl)ClXOj8ASk1TKJuK{g*WOK0@-C;^kU zKpVUvRe#F-pGry8T#LWfYJL}e7=Y()`bWd-L5bsRZ6N{U{ifY zWy>-#c!4okzCvi*N+VT=78#rdLmF@wDQk)jLq)zHh}(88M;BzU&uI?3FG2@=eIX6a zw5A~ejDl!kElIy4t3YEitM>*~AdC)toIE9%v#a62A)61ueuH0FDnqega$C9{fAQ)CJ zFnU>LMAu;9?gLmuIo3*F@*83yC$NX%^NkC?D$oTa-bxv3Fo3apMGIi|V~R)y%`w%a zB1TdNsw;^0$?+51f*F-qbm|vk#1V!%bdV&WDp8_?NY~!M6R`epBFB){!@(u?p~;!; zZA08*m!CLd$@`)MkY|1z8ijj6)Pcz+#+UJ_$)~idf@|#q?gr7k5{LyzlXTR=C4o$0 z?+#sO_T>uKfmLQFZUf4Pj${3!V8&^`OG-)aCYTT?!BtpYsz2FVA(8wcFQyW0@fcDVLo0oZoW}odT$U)GmDgw0#Meh#t_U8 z69IC3EO)#`L0TU0ZcO~(VnM=K_*2$^)ln|d`k7XP8Z365Q!_^6YNHCRC6YvV3}k1% z5lrH=sv+4K9YV~{BAgg@5MkyueJr@29uEFkbBC#1JWAYcbfdLQoVZ(SiAI(wk4BEx@UEB78@)g__nD@9e!L~BetOi{f@lAX zRSnjRLU@e(>r0F!@up9igqB;^<&+9ust-(FH0-BOsUR5(o|^!ffKO_(0U>Eq*9x;{ zJnH1q)}eJ#m7JBti(|HRQ%Fvenp;^S`R4euMVyUEpgrf993tbd=NCpDdB&`z@z5D5J$Y@2(2E>L%lPRr3{7V-hq*$`0A=*wB2pCgdz7Q-kMr1fErxL)e31 z7nxqx6y#`ICzlH(8NF(ulr0>Br0Mvu^WR8S5<}wmLFpuOM;iGR;zQL559Oq!tevxt zrcVS3I^W3=9+uhJnQnXoId>p`D?qekt@Iyg^j>@s9L&ybZH>(QPEbN8Ux8o5EkpCS zR8>8TmsKN6w9jn<-MzS?!ARE~eXasJm6lBgxX+dv;G7=u zvE-gIQ(1`;C;iTNTm?0}BT>9lSgbgN78o@u@|LA31C-}7yU|KHBrJ0I2MPx{nB;H- zD|HD=ubvH)fmBsei6&0Ad2N>#%mjM5+!~^wzX%x9tOJ^C+3{?Y6=%gOqwa6oU37d$ zvjh0)(c{~bet$!$W>92&D+Sr$8%Zs~wdGR3C}8uk&_eO`fPW>)(i`t#z}7TRQE2nQ z;^Bb*&4$6|qt^V%Lg$^p@cZqkRC{<7*D>y6`v|r@7|UgUj3BDr9{(D)eM0z?&1@wz zsWDV-(tTzlR>Q$B@H4pIZ*kHLf8g$sHX4>+C!MP>Yd)~KI5jALTCAWpBQ=b1Im*Q} z-$L7yNjSHZ!Z~H3{?NX}tR&8Y7xyf@XsM}Gd)k66zi3-alF^bU8fZUHeglP!2 zt{>Nj3v7XYG?VjhtV$|_oq=x)PqVeAr?W_@Qzv z4eU`yoCr1B)f3jJvWzIE8OL!tfQhyu8Bt{#77ALLcZKyw5%Z{c1cQ=1L*Q1=VlRjs z^qCbgGM?JG!Nzch|8Y1tCWf2$OE40!65&XNkS?s0XeSc>p$YCe2S~aAQ${9Q&5~d* z%~_kQATcF7tU6p_olFWEXz+?E1}7bneFX5)Da30DbUnD3B?vu!KS?lyf*6M^OG<;C zFbEPN1i3_ykqhcd0cp)D`T7k-nqj44-@Y+61pH5!7(8gPoZhUbZ zu4^-}sLNDkXcqGc>px*RhZyvA_O$FyAfJbuYp<&uQ_j0_$-(<~;M3c6jEtB8LP<}8 zToqABxpPy24jy28#yBX$c&bggYc?{tuF?i5!Kivrc||qm5(pnwJx_d?L{~t zQJ-wZl9&*6Ltddr@i8M%ERe^0L#b`J>2f5h=8uy8)?%)q^017rYI|hIKyVg zz9l7VW<$Wm5UNE(QwUv)F?0tHX~8@c>ovtpg{c^x)6+!EAD8}oJolsGN_*4A;bn<> zA*@C1c_?*Lk27;y-Ijd&qhN_#UTJ8vA|#F7FW(~*OixE%&cHcz#z#2x`@g1nvIeRS zCbJ(89IVqv^v!JrWi8U9Z|e9R7@e+twCpVY2$ZTz#Yo7O96|Nu{gVmA09vo!rL^NNcwbEQXBZ zQW7R>WJz|K3Fwtc{eCJmbXhV}k{Z9aD>ZXEd9d;c%oAyiNT_dQVu@h-u!GmsYF5-n z|FUD!IcrtL->IKsDu>pgz|EUrv)JVmWyP6pqFZDc&;13JCLF2Krjw%#PD7$T=YmkR zaI#bMc;=7W&;;{aWCITa|Fd^yIdkZ7f4uDc9{ZtpitV5hotHi8)XsE zldDWtJn49RhUoB~*ct1-uZY^Z*$gK|M6>c+`5p#5a;xs8(GFN*M{%L=dsz1Bzjpit z4e1<*18NbOWfK_MQjheVteyAh7e~!4dL6KM^Jgc`hq@+FS!81joBQEt5tR|a<^Rn8 zBv+6U1zv>?Tv`-BfaYenPF+WB(rkm&AOLib^M#9u#qg4GK`x;^k?SpxZ-HE9W2Wz7 zW2q4(FG*362#sjDFH6>u%Z;w)rvC?-xS)gg*Z(V#xc*m4&aK^-79-2o=AC+Dc9d`X z*9Lxd*^@WT1pNue<;BntzS&ECR%=G?B-UC+ZY51bgslHZZyR9%5TIxvb{6%+C`zcha5rj`F?%?g>OQ||3Tvp#3ITEoE& zEKqM%KTU^CN>VfRpSZeqS#M=~B?Zob1{UxQsv22>2Zo1$7fE<8#)rK4b8`vixX5A$fLZQL>KNB}oE(-O^$VkZI z>S{1K+zryef9-5phJwQ_F%!R(p%R2-4*H$BcFghCV!r++o6au+HSeVV4 zlyuTk@9k4g!z>#o{6$&Swf)=i?W3X|vys}OrG*WL3R)bPa1T}>8MAusfQ7PUhn?~Z z;Nu~!GX!YkNU=H+!6E=kg z7G^w1$V^xm=X~ojcw#@){JyniswP39Pq&73O<}5GRk995eA6i?SYwnahA)A^bo^#b zk2qV!Lh#pTS%3vYC14-;oWVmZFzG5}UQwzNH8`<{1EAO*i;C_pB*bC!vmuWrHG%~b zr)nH4AXCGZ4xvJu1{>5_H;}L=8wie7=?-1=7(+ayh07##)?zS(7EeFL8e|GqOqfWx zFtbUkNs)Zhr=%>V1#M!08DKC9k=M6kq_l0E1Sb1K6Xz8=!EdrStw~o;oux=qGV}g% zyoc6%s#*9<&jAwE1PY~*vZ!yJ=pLKVQ$*3wJ+D`Av7&CIOp4i{zid3Vj3}&sP=%Pw zK^%C>^@y+ZN&E#@P8L|ZURO)QVl6IJY?+s+h{U=*<(5_W`m2~7y@EZd>GvL(R4!KA zj{4rBsYScaY$`SRI}?C=uHO)y6=Y(z=|_##zOx(mC)dB(!^oVV8BWKmfz{ynM^A;} zV(eJV{j6)MW(t3lJMqew-XI=B?WN+NMk}EbnO~7QU6Mxfk%&uagGfuD$QC_l(AwdP z6#tDO>kkgHcF=~I0bi+Y>SQ3BRPLx_##x`ptR-{48bOdyPFb5L4wUwi&Fl$o!v+t% zmc48*G{=zf2gc|DG%}^zG&)pszN!DXDnCVHnmQk>r(nGIkP7X%b&EQ1moB?#%KT!~ zI8&WR(u^Ty9X7_r41UyXvxSB8Tzy#Ih!T!%Eqh6tBGg`aD+#+X)#6OUa>6xGo83b{ zv92Ng;8abp$twHrF&%ev`l(7P66M$Ts^>eVL<|kTHHXw$%fe)pF%Fyc%F4k_(mb)M znyZSh%Cqz|KBv%COcL&$X$bdtdS>}#S;X1cWyQqWM8%cG)!fpJEcprSDvw4N_XxzR zbCNM8>O~-75AeaeP?AYT?Hz7?L%V!ppdny4dihL=iP|G!2pRF^r5Z1+Xp+?ce7X>yr5T;(CG-|>h zabIwdzyj3U`Y2eN`35-&K}xHdtJQPJDoO%d?Iq1S`O^Xk-A$&-qC;kN!_&4bIQ{|a zP$?;+x2G=g613IflOQ2Nma@9$foJx zz}RNU;Q-e$8(!gOdgzv%vbN(K1=y z{WY!sDp!Ui^nL0hJBxp|ZggPTUuwa|;wsEW^iXQ{@9!Vg^rO@z*VwCGD4B5mK@kU# z9r(m&)R738gIvYgT=j@>-f(@&Pabh6Mk^XpEFn%KO41n&l36y18l2_eSvIKT#*sxj z1zgIaYPp9lqy%6XY+vg=?3q%ss8A33SUXkx19ayLW}cY6Hit!npd5+d6PMqRvJ#gp z>$y8jn{bCH5Y1^0;+ycU4nx9s&cw-DGi5oMLmVr7q?-)jA}B1$w}jAh71L{wo~IUX zq@)c<2&o-36d*N7e>@oB;22_RNAybv4(g!;8cT5iYJ6DZ(npqaGEb!)+1|cPGoOxv z(N7ZS#^6v2`E{lt_95XWl}jt-aUmkV^{M6j#UKxU@3uZS{Z^`xR40~OT3y1>wcHtX zb{vUgJB2J`j6B+t@}D5PaAtNVKXxeww8H2puEgJt6nb=S!M2&gQP(dF4AhUj*)P$W zL;%QEQ|xfvKED%Ha1&9GBdPJ?UIT5FNDE`n9QrQ>rPEBdtOeO=9I||`El-aCjmYwDhKx%{5{sDNOLH0b=K!!=QP1vxf=~<_gk83^ICOA zI>>qQJ12Vwl6pY(kDD*hskUtW;&v~!|3-fA7QmgTt|g32iQ7oIuPbuYnx!TeGFP9* zJzv#~onB1O-)+}`4?#_)EW>5Uvv^S?1z-d;vW}gujF|ncBv?i)* zA?TRfJ`tOaghY0^YB=NZm8-t~nbW?2Tm&b!TYPsRZD3e_$8Gv3nWPoAPA@>-Nvh!J z$KiS2Bci*E?*hV}=+W(mtz+5-;;UVazbUYU$5Ug}tHaatjnNHgUbxc{nPrvZ9xqB2 z6*$e{lVg>Nf|iPwS$1Y?ilB6~3c<+O1pcQj{g2;zTg7#SZ@m&G^-y!;F2y~P(ltYW zr%?ZM1X&P@O5P8mKp93O2nW@e?HY;t`1{MpS+jUs;f3U6x*h9AfYyp%Rm5u&Mzy-p0&jSl2)P zqL;s5N%lzBPcvjf7qaV6sy16AB5TXGQHzE$z>ZTfMRcBJ>;Y{2(k;R2l4I?*v7_f%{{Rjh!3ne;&91BBbJD z_>fSWleKa6Tpz-7THw1|lHk8@BJ$-x2QSIbssJVJQysOHGCuAby<%hea$XN}3)ZqS z*87N_6@XX+Ji!nU8~qmV?{k-lrQt=RK!JX+Rn}i0iY;_bn;{Va4J+xVgXg zzHO`FC)d*Nl_YFF7m2S!Kv!qma~tH5D+CbaY!B1Hf%X_2*J#Iy9_cr|!$T3`ItSolfY~%;0y$!*aSx#*kOhR2 zV79S$gJj05j^tMiySYi@L9~HjFrH(J3+K9<^UTUR56|50pi_U&LfW!UE@>rQS|F(x z9zYfhzMVlQ!!#X!W2gCd6Pe12XXavEcr?!~yu*`7!6aU<-euSq(Bx?ngam>l=jt^` zTkvh7wfhCER#s$Nj^Al%sO&VaA(a;>{u|zOIMCJ9_8^>;;W{efr@PFohuAf!b@u2t zYm5M7d++ty+UUxoXy;2^i85&pbIru}rIE1Hzk%@hxs<%}_V5HE(?y112xSS{Ey=iGfkuqG>LfPE3q!ZP(R!vU8<($2cz7|capc8>0c|)qvpki}N@SW?h$BZWSQ|Z0VG0m-^VCo zscF3^ScV3g-xw7@FtgT6mh3Nb=~nJ90{7=}T~RPBlnWO)bQwkN^)w}>?ZIp}#{;eJ zr84Gd>l4~wy&Hv#YeBptcxE7(zyt?XMN=bJiX+-Ty@Y z_2!fsA{`**8G*sI(Ye~GnZ{UvwzgHAwuIuUts0Lg3@{i_pHsT(-qx34s`Vv8c;27x#eXN zQ%iXYP`d6*0fLJL4X2VyV1dzhz@CvZU zENxN@6wrA~5xCR{DpKPbAaLVGM4S=Ij(;VbIjK4>^X^mNK8F`0fqcXBNZ#As4I@&E zPcx}^#3?^kXBzz%v)k@?3*kjmXd`9`m+?RFJ=xaX2ETl3lmG^q*a6qmZ6-D@_=$3@ z_sn9o*p64Hi3L#NSK-EKziRS-9nLK9)f6Al_?!{RG+TRM%9)zu#TGuq+uYw}zddvl zy?jr9evp3HeU|b+7GF&ByFZ+gXMdi3yX*-aWq)>l)%${df86v}dRFs)IG(Q~(9sy{ z4h=4r_nQ@&-G|b5 zRw(&s1eZ#lH1A8ycz|5nc+j7mqAT?_*kU!`T#*Jbbw8{|amJhh$;l zQNzivy)5bbgdp8n9Kw0&u11#@>vtHex4X~FA4SFsG#X!dvDxT=ufOeW{upYWt5&#+ z>eKAomBS0E)W#&kmLv%=FmzBd#3VysM8gXSAC!SISP|K|Y2^+YwcDG&6k|$cY4C^B zn=5w-dO9SMo-I_kpV;1v@l94mai@MQd=|&wtuPh5Gj?-X;mPBY(&`b+18@wIvqK4< zPSEC&(QgyHbwz%Lq=s*f(3|tH@*4HgQ&svPa;fGJXiKuey@Kg?Bl&VmmN~fUc;G1e zpF4qkWy40pVi2v)bXXLYU8vd4<8sFu4A()pL4|Am!Du$zy!qu`;AcfgcV2otPaK}S zi^8H2bP~F35sXOGXnY!ru17OhyM;H^+CDO49z+aT3*ISFLdP_Dg`iIk>`L7PE75*u z1N?0sU=xnE<7C~c9^p^_OZVHCR;0s$k5+ngp7)cYN;_p$h$Ur??kgiU)PCaCemi z5W-E>Bmp+gM)-5Z;tOy2iWuAF8rbS}lP##k*3T^h$_6a<#M+Ks3!mG~7pu{RxbPk- z6{t%S?9CuHtI&>%-QY07d}s7AgI(6cBU*b_p7Rznqv3P^YWtmiM7zIY3>Cug#4ziL z=SxtlZ1Pv*_%3kKa02=F-s5 z1(If^tSJE@0y&&mg-;;3tF~52cvuSTM6_Lbkd*vVhzoX$xVWornv4kK+npZ~O~dU{ zAd6xeW{z({3VHskRoR8IAh!z)g9q*PmJUu*71kR6ou<1=ln-s6lE` zx#n*%#wSC9%7bz5lpt^4^+%bBvwTg!;DTWEig6qXnoLVKMV`TEgpvF+-8Ed^fwR1e zB&7S32Gn;{Q;zJV9VR&hDuP^}iQon`ECBmgs&%Z^6r4;mxaN-8$0xB`IxrJ#w>kC_ zBewakPix(_2ZZl*{#($OZ{3!Uufk-X^FiM?;%v{a^Ot{*#b3tnJnyI5_x}~k;O{*c zy&Mvk-`^7tu-`SZd@WtR$Ni1l{4F)OE_PVxq#nIOE4M73n0N_k7Jzn?IFvjUg3A8P zuM<0~mig}u-)+bg7+(|4N9m zV88p0WtXkZSH=^| zgfR}c1P0#BrE1F43M5!$F;*2U_ho)Pn)$j#cz#ZQ+?8&h|Nidyx*)f&UeC|uK6(B; zXx965+&nK^!a!s3cVcl+iT7v*1R5T2x~edJqmMR^U&Fh@Z;?vWh0e^-)9N1OrMjYOMNgjMRVPm>3`2M)L6)2?h1l=|yrroQ-b>rjD>?X4y@n z#p4ds8%OHCXV|}01KaD1AmwP4cR%tK3vzG(6+CIVf-MI*5@4N1>Zzc&+YneC3$TTQ z4(vnW(&Z_F&T!m_@f{LQ3foQKUpx2KmzcTXA>Mp{M;|H%n1<$SuYbB=mMSwz zcA?00Qpxa&PW?3h(J~AN$v)*+X@1zXH;3aEPYE_x0t@f-OSe|NqOllY;IIe%2b=D} zmZee?C#OFRU;Vw!$kU}$$ri2wq&%SBiNxW~RkyTzA;JKSGRrv(Y&3l`5UyOH&y&`- zlswd!J-wwmI9E|nJ(~IF1P4s#avT-^6bdgZo|L7l@OWI)GC*jtL>K&k1&F%c=@GpG zzRYPn_Cp3*QSM5wtH1T=r#p}aQv>hMH6;niHNe_91?BwE9o(Gif}bu7HwTwgfl>#= z6-X3CSMgr#wT>nRFsMvue>MgxT2)g0PLV%88rG|in9$FiEd6+A3%4kafL7GEN6oBn zs}jzjXL48?&!&&(|GWG6%?mb<0b~^4lyHT)Dy@(Dd0Te6DQ|c3MFRjE09-56EsLn& z_~mtEiA?VEG!1VUi3Rjme6#=rbTR`K)TFnKi90HwW*yx+cm!E$I?_<1A0N6VVy59k zcPvBNOfKIpYD|lD=;+sCm~s}HxT|VU)FYnC@zV7-1;Vw8&T5BYT$1sWBCd&3)eY4t z;*dLQ5p5N)0r_aBb(7FBHB82mvpSLk$hl#(q=I3chDn~%h-Rd?@Wp4?5|qs7dP*3J z@8MQuc}kv?0@Qj+q{9`t#I^TFUep(VVGhk{TZQs)?dvCjNp%!Opj$fkD0b>-Uyo_* z>t#zNcC<0+RLuOPj*eQKPkp;zF?_2CsA}GnUZ*`-c}2nT{jkG)`TSni)A2qKe)h8K z*kRp7f4qqNeu;&qDJT#D^*G|JAY0RS)RjDgJ@6I#uKXqW^C;pDg`D%2*8vJ8@2x=O zFI>`v8TSWSSua5%2hHl=9SdwFC<=NJnK(f$MNFC>PYv4rpKbd$Q_@x#eH5&M^t875 z8NaYe9vGC`L{Oz}qrirfQ_x4=08$wJQnp`b7^{T0)tB+0-k==5M&ft}M{Z9GwZ|Vv# zz~#gp>c?FC(y+kj>&QK5OeEGD(du73*2`(V%7IzlIA41!7u$l1wlxgV0w_bzOuhw? zyV1Z$?ILp4tQ!!WkKnXoC3+?!mNmdP=n!qgzjn98Hl#fHH_1Vt#C~6gCp!FYRYDO{ zRSvPyjExczWj#7up%F<4UjF9qJzXdsImI#l;C$lno+golU%`m62Qt=vhSFUTLU)8= z@j;&!xl3mZLY9a<5rb?-c8q9ai^(b7C*XEvz;ZK2=I}2?ERKQkT?B!`(VTvw zxSytEl9|lha~0%6dfi-D=xe}c1XDmK6uaKX#8&|ND7G#S4=q2AJUSh2$*#5Lm~nKT z1mj_Kes8zhKVPFfZDExD=dAk-GXrkE7oE1cF@m##V=#Q37Z_!PH+>@1-}?o}ChRTF z<3>KFDMPlue7)JL!a0S-awv4LazWJ$O778wMUH{|kOoc5^9SDvB_qo82%IBkUY#B} zGyMIpVSkUswXP95URMDfkzxVuI1z2Fp!GDb=)p6$vWfmO4I@#do)?y}wE)YgM>=Gg ze>4ArW;z^PtltlnGM*a}9upjvWDH)cpSia?Dj5Czb)935xD`QN#|-HSR5Td{W6*kD zRXSafN!We}Orl-iV0xH&pvIF_+q`Ko5uk;Sg$idA;0q$g3f@f-zI*Ih#G)h5#TrE_ z^~Bh<6*i&9u*^v^LFCla$Ah-4>mz00;mpi-$Cl+4@wK=>@;JV*sT1|(UMP&Dub%u(nY>j%>?;7m-WW;Ser_krm_F8l z-y`M;2y1`=soNbc_|glLGqbcwRVLpZLy`(e|8Kpg5$5gGuOE%BqD(yM%6de#i%w+F zl&OqS>UO<+vm?epfs`|DgwvMB$vHp#z^JBN!ik%l zPh7to@vaA912Rj;BeSy;zYI773dkIr`79s1_#10Qr?N`dbN&rc|7=4pv!a*vaqQdf zPksz}3c?zW5e8AALbk<&!N)0?4XI+8V|S2R>A0sQ)KFrf)Ia0J|d=a2@w zD97VvE4w(;pV*~iq7!RotNo-+i75=*=N4s4Xzzd@PwdW>4yTljgJtc>GV zx+s{k1LbD#xZ_r~S6``D>*H(8F{45F!d9#jpA4eUoX48-bG!$x}PJ7 z#{uLAiV{iWa;wE@G}Yp3U7P|LJR;eWX>At?-sRJZ5VCowO&lr>*6WrQt1b(3b)@z) z;s1NAt0Q%s36J}4gmV1vKHBi8OMQ_xiV+hC?9)t*vlIB|72zKX1jL#PSnM@F`}*id zqYqLB3525kTdctXrHw%7wR)#%G*xfeD(-^*yZnX9@bM`$=L3ZmORUXG;Tf8>2g}A#p#Oi-hc1}^2HQJI++qP}n zW@V*q+m*IaY1^)}D{b4hZST(ioO@5-9-|++-_~CHeT_LIzKB@_ER#A6h2D+dmKrQh z93w5p+lO1uYst~HGQtdSg=Srqc1@R6U01d?dad>?*NT;dxf&q?S8u31YW0H%Ci~ub zc<7;B(zK&{bC4kK{-4_ks(bb zreAW@p*=(KQB3_jPm`$R@m8dPre_YX_|KoLe)PGSne<4ern#`0E$Z7VTc(5&VQD_oeFf5AmYNPt*hD3bs;xD zNZsLafPISVyT@FXO)@-GSw0NAN`5Gf3U);td0i(!M#epW>V+6sj|j$B${#a4kWi@y zR;&k+T4@8eIF6BTu#@P6xHs)Q>ER{fFGHt!jXES~9UP$pkcdM?ZW~!QNy?99kc&_m zkLy*IJpmkIlIxjTA*T6eN?^Vqy}ZKIM&f4DJP5R|xNOyfFkeTj2c3R4ld##$)n4pp zn^?o;2@>*pnF0e+Rk_x%QVxylm0xDyBfl?OkMN0Mx!X@mC$$(FsSd)#y1TDQKu#Qx zc1Z?c*^b^8*e{iPCrLk+ewyp!+V2b&>|(cDdPB9MWM(>HOeGG!eN->HNh!#(M+ixo zXcf!TPc!Ayo3CLJGe(Uzu+URR08#bkJ%ZJPwk^4;2c_4fg#YUE>5Ds05FYW=4|Mg_ztO9^k)GGf@bPnOyVWKGg=tZO z{<|jYnl$XiNz5JVt-QZW=*2Qq*72xJ6!{y5EE9;T>sy@))zsqw{z-N*LIDYFLQ4cG zd0$G}e-O)tf~Y=xSIh!P=C<%Q4kj#LC~I`u1}T28b&ohaW|~`aKCo4vf37%_XR$Hr z-jsHR1&$WBY}t7PC`5AB9}|7P;7!@2n&mO^#ye_xZPK`J+BivYs93*v zE)uAV*N}qg>#y8?3s%Yv@EAuv$w@(>83EO>Zez>%NfET-Y0?Z+X_Dz*K zpH{AUIty02)3s+Mulc4NpUX^oXSfvpFO*APVx;ViT_{-oj-j#XRED1;r)z%LpVM7k zLIL|aA1NaHQd6;WG#IRjlC{BnsfF^p?$rK3$5n1&T z8dKFaQ3mTMl%}$pP`f^S3&+$$rxqfRy-m3s;Z^D=beQ^FDoECO&3^j`8AqOoJ*I|O zMaL&qnTAv4iG)2%qnZ z9V_q?h&NzVM2$akelXyLg@{wp6@ozj!N(~@uJ2_C4*~A=4i0Zw+efT(JAcXQ1_bl>) zh4vHJGUd&8HlUeOqtdF{K|5o99Z7iAQI(@SiL~dZpYD}peyI8**GgzvD|?-T;9Zxd zU|qf7{t3?Bk|eY z3M&k4d+#R2mT;(^J+CBEJGHelU587?YH|6&Cpyg;m8v&UvJz=7RX3gbDW}AL1T4xW zs;5_lv4}9oGn>K;2D7LRbJh;GiShK_@8T9X1d7Yk+@fM~(&wQ-^(MHgOFd&!lJ2m0 z0zJd(WH68bN0blQ)=JtXcUROcW0E6SOZErwfi+;Pbfi8}JCqfN~ zx6Qx!0%4~)g)m?HAHrb_rr{;Zx?_BKd zf87ZFeL;CT*r!a!=M4}G#%#RpF!2a$cQ=EWcaA>&#SHRG#mVx@@VUzb~2X%Aw7GTvzKEAx7?DrgH_M7muNW$Hr)mE(7yZCs(F3-$- zgyjSLG+&`DC2ZF7R7bq+=#-C9KbMD7Xqz6VV-Wo&>ko(9)|$7cg@LFq)b+el1tBO% zR+DgLne%K*C?0i6{4php(=Sl=5;jJrJrrF(h?q)(N+@3r(wD`gYVb#RCqLZvKfEL` zr@b5(%=P}uR%T-jtw$nWN~*NB#felqrvW#U=C1u}oqV;7KMASV9DlBgyWA6NAnYj0 zhCaVu=ZmDR?AdKO(UdRj%a(1bfrNUWf-}Q;VD0pX#W2?S%-9k68LKr+5@)qy;s{4E zjuMr&aJTMo{f1%cqu*RJ(|t%Cgfeh-`5S8LzR7?}iS#~5kc(8gdmUSMa5$TY=Y*3E z)sJ=xWe6oBQn(+%hoUuZgtvZp=Z7|jSKrRFz%@Wf%zX3Kt*(pe#xGeHa%^qc*om!F zM)}Le4nfwxg}Rbb!Qis$vrWVgq4Lk-7~%c>?sI zp#U}i_klpVSy;-!4&80valHytJ)!u}5!?19*&1~83i99kBH!L|l-#4!~<%NtLD-^%UEm~1U;%1?8K8*>x}FvX2?R3R<2RLA}_ z98m}#u#=dc1cT>*-m|~;rpndJ53cHmOR9_6$PG3nXqdJs=5+qfWinZnWJe6IP>l#9 z=l*;iwWD^J=E-Tb}>mm^nFS_2=+$XD`ledWH>XGj$J#x zB+=6dD=hV`K(^n?SW~xt@K=~Dbkh+3IznBIwra0+^pKu)qIUC_{wk@aj$W2-{i;Ok zuLUs_>QpZndv8FE>>Z=WQ8;vFy&!=HkRI$N*RR6&BUT;)c@LcLQ#Of~n4 zExMtOffTNsBol{=P(+Dvvn<4gci3YSNawoSrHS3V`Drj@LXDE8M=F_=lUU7&4ih*} zuGZ@=JlL$ewAnfE)!}yu&Wl>=B~Kh3^V-Y|`PDuROUf(RACK<(c<`Sx0rMrwlW+^N=B7%_L?LuoI z<_^+X!pf?cN~2bj`&S41H;=Fkgh6(V(~vBt;Rj#Z)BA`kVq+`NxpqoIukzr}1W-%C zQe-k)M|`e&@1kyKRnLy_Wq{;&=kQ#HY&fdNFQu?BRgRRo0+F0iG(tEEY2{C>IO{eV z(jG9^tVCgy-@xS_S^G1w5sQMQ0fdb$2=9G@ANSq0`@rPMr+K6V@f7XUQi_X@vc&iF zR8z2tSjjC&1_=1cjh-ULc>}G@1h7%Y#CStHtDf#(LZ z5=H0Sbll<3Z46BXtw46>Ia@q#)1-FVp7;&P-6_EWJj1lo2>O%Uy0UMUMCXfS#sVd8 zut78Q!_!!RE_uKSXs}DRKRgdMdv!OSb`pyBem=l=j zt#N29?XV`U$CG;n=Reox+iJvQ4Sy6^;F~Z%*9NEN{~2Qxb#y*wFT0VToR$*#ZG7n# zs{CbYDe0O$w9yLFNuv@r;u@}VR=dTs-OqyqYv7uU0tLmFO;3b%4zW(e!o=tQRBIkC zqOyD-k3VKT)Qgambh_=NqKL+AsiYX!z4KEMt=TBw$*Fk}?5xIad2YCDeX?hJ(4~Kq z0n58(+-2wv0}))Z>0{0-W|MXHMd&YY&Q9*g9ouHEfE1bxX5&I0?$_Wx1h0w&nNK_ieu3`r6!~PU3D`!#(G`@7xQnyzX*t15Peecbw-Rr~Fg) zYyuH#JWtA7X5BU`Tl6y6DCsY~j5oPisz7ZU+PFR1KLEIEND9=DT7Ttz-7c+1_=Iz2 zmz%sSBC9uNmJ}4PS8kT_cU+g9FN|ICI_sqcI2&`e1O=Qf%GovkCtQi1@jv0pjF41s z!p`7q2q+)e0R=MVbnax{6+tfJevu4jog3QFC_m=e+Zu=%eu%M*wuXV6({Jz+?dsv$ z`$L_7X(-LXne`#Pb;c41SFqQ5+Cjww{oqRz3>$Eo<~CSiLP-OPT*7 zIFmNYWB)~P9>D)!2u`^mlK&<+!D$RIC3B%7*QFU^81a3+Ay3Y7t|_disPo{W^?l6c zxm`j%$A1XU9|bPiVy8ULr%$hgj*zg_F#@-~-=Y6OBJWvRt7Y5E=t^pFV5W znf3o}XP+V55w1jfU&+iGjb;D;Gqp&6_EBC>q+E5BOoG{RqYev+laYm=PAqWWClOeAV7ev+dBz9|O~Yf(m`qjs zl6IM>CU8~l*NqegPFwH{`FW$fR*dIf{ObzG5+^Az%z1Z!;42E zgOf$4{We0i31LoNe$&!|K`qwJ=0VML-xSj(@7 z*K=Dm#*8SNR;PJnA(;tg8s+nB5|EdB;CUYZEzN*KQE z!q-JTjYL}LW#^idIiW?u;sRsw(3m^wd-zchdzT7^Fr zP)pqJGY~PPOm@WE9Q@LJ&Qrk?B={|L8V3=mB^;^Mth6Zd!;Wowi3;Dg6(i7OD6ts6 z0f$Go8odTE*{Yax5_EA3rf^-v5=O)5odMQ8iNq@oCBWB#704jePkc6SA9SUEf-p&tu)VX2(!EaCN4P;z z9*?Qh`|;=BO0*6EqNi>6>`F1_uj-Pq z;&EH_X47P0M3ccCqUm+5dc24JsHpX@gn17Hry_CZQQ1ZI*d)J~eB!J(Yqu?&|3#c@ zXA|h=0hRSW9mR8$Ih1LVP~~{}d`!1(>rs*T9UkPivJy_H?*1wGqld3?dO2A9&SC1} z$ieV|wkmx8I23vp*YB1bGmxv@6^(GV|IF0x$Jm~Ws^a?u@cqTRsXz|p+-Xs*w^mz0 zoynDQkjCNeF7_oiv||d?yNQptQR(f{qQi`E^Pucn4h;jqw)klhX4@=r#0NKUl}UMf z%yOgrDYtmWP-jb1SGKIjNFA%w#vTe4hU*3ndeNnXzz6U&!&J4D;sk~46 z`iALUE?t|ICWaGrMfYjN-)9&8H`J#%gBJx))CG0@;NKwtOY$K3*Ptuyy==gtSF$sjh^i$8Mx1!s-DThj zxm!b3r#^I$hrXi+dlYC@{y?ROq<0_QsfzNeF6`iujAQdIsr+z_O@LilzRFS#tcB z)MZa#!_h%9P`?m>E_~>^)Qwlt!)MTYY6X2!Y^7M#I50ZDRsh)smiC4DEv?n0mJS1v zZHT=sflhsYr?BY{_&0Ay@@5d4Jo*w{j5fgCT=Q%>8~=nZ^#NROV&y+_0fqD{rpk;e zdggjl6!boI9elQ$#1&s0t^yQkNKBBtQ4%|m<)`!)TJc+ilM}dJurM{F2#bUXB2DV@ zd@4B*Y$HtO2{0!_8dc=wKTX;#>dJ&5qfCpPGp~t`sU-4cGvwgChtC0{GXC4v;qF&% z>guQz*&V+}x!2-0xpKjrAse!*ltBq5iiHCkDnN=+gBg*jbb-kn%>2(CPB*xML{#tz z=U$6Foo@3?EGJYV_BGpcHb;R7yp65Wf;_6~*xHu!b{B6EXl`tf!tP#GVftzVxFMFY zoI*vMRU+lkW#qYx>1B#d3^0GQ9@C`yVK;MSqZyln@8d!T&gSlnTF^ikXBqYUEN8G9 zX)M1PSGZ=E=Y#eUty4u>pZL5 zzT@{gnI?hxtdw~Mfu}XyE+6tO_2?kGXz7ks5$#rXhAdt!xgZni_IP*0=RR znSD0rU2V`NY=T$s9|r--<2Yyx#q07Rv+0$Yj>2GMWahL=S{nQN)X3lcrV`B*ky_G> zT0}io+_(}q&*we1*DK<2o0+7to}5&XAfj3i%BzZ2p-Kh9|3@Vmjpq55Gs@$rt5Jz~ z)`+W->e@(-Ovn5U-h2zujBMya2x27HJp4jEtv~A%W-<-E z-{j4KD9d|WMg$eu35jA+rD2>BVH|{OG$+t?~Y-{A~E1ds$vbTi&;sA#h_e zrhRT+2`@fwGCTbQJg+3fty`tzrf_z05o*jrC-S%GM**$#sb(hfI~t8qu0Hv)p1g9@JSlFv zEYD~3hk){Y*FqyBdVvq}p`~=553*^zrsWzY9$DM8?~^u{%-gu%{!SDUd8hl~e(t&N zhv|oNE4M9RrLdp(s+^Fb6EM53iU8yUl^~--w3NTxhR}J_({STQ5bG8ZJ@Y3G(*JFw zDk6OV%YN#jTw>agt#w{9tQxnZZiHroUt6u&S==jFd@TO{@wB*Y#>amZ^N!PdltK3Y|2DoGG>ZP~@GsIyR^vBmb? zooCm8-)OYJ#w*YkOW}tk>DYoXa$ho?858Me1RD9|9cT=OO#9JWKqB*fH>t(DMs6+b zs*Lg|zKtS5hAP=U+7_F~g<5%$yAM-%^ZYSkq`k=sWzi8ryHJO4KC;uYmf!S1vbmcB z4Lz?^B)s?}$~%`iPEM|a&2){>K*ecBz{HpFW`8}X86qF;_`-N&`FD;+OO>{_Vo#J2 z(KP7;xgFx1MKFvD+NjZbYN2nwpFKgDIiCR`;AnPwZ`^7=HbtcO{|YaNCjK!J98A+FABj}+TR(bAvp+RcVQHg?vYUxtzSle&X>{#TXIi>vb4NUEO-~n0D0l zTE2q?W(|2etzrF^r7q6Q4uJ|eDm7(;WQ^`n3jyYnN~Hq|dwmer!-UYY8ER(Vl;#es zmT#Vn(zP{-UWcXXDmVv9h56^6qbVlWyIIDW)z;}Mq9@MIJsZX)QleL6q6g-*G5 zTtYr&J&NV@OJ>x}&yAb7BgDzPhJ+R>8iFCFtTg|=N-m9y8p*K#Dx9{dpX|7CT;Vh<=)(&kLRbDnKHCB zoc@$uFoT);98Kk@vbuOa&1k1unX;y(yy{#D40=U@b0FAgCfM^Dc$NprOF2AJ zVX-*$dmsNNJ%n{`Uc9c(QrC>0c&MUsQ#7$NL2tM=ls&S_Fb@M=r2&oEA2;8x+>ccE zR{)xWsK5^@R<^f%#QqjaG*5i`D?Z_Zrr(Xcw|GAvJ~l63+=3!WuF1Q9rHWMro^0k- zw|W}QHYSbkEHKIE0Rxo+^{^9u!6G&0+h#nK8{%8$6y_2QFUqGZR@wpH{!RgLul46` zT{ui;hb|%9!P&f?++w_Ls)_dN|0>`}R@t~%wEcK8a=iJ=g(`o71Y>XJ!8+W$8kP%D z^vgef+%xkBb=6IT9?l@bkaHZO5V)evIA7-F;UiKVSdVz2GJH{l9 zpNu?knpCR|SP0HiyZoECnKitSC$9jb;MycNN;7vhm{0B zxCs(p@Z2Ebgv2wIF1hL5G$9JSRZhy@w>4O)^C$t5#nY|3A|y~6;m_9y6XZQd+sOTG zef=azp1=u&`VkH#6s{LxTC#Tx=K8T~x+*;@?p+%Yi$yS*EWI>&e*65yKuFJ^q{6@ico73$TDy>y|sdW)WvCu-J@Z}060e<`mWWsF6(6>`+57xOMxeU6Mo(a zF&_z4OsBa-pr{X9lprztPhUReHLjl-+mEfKkl%PBjt~t*xaTC6m9l?|MJrvuXv`Hh z(YDFnil^T|?v;wiAy}NJ&c)=Pl0Zf}{0>UpXbn$8?Gqz0#Drh9cEWVF_Kiv#AS=6c z8`mK--cgLqE!#3Vlia<`HR8;&>0lz6JsHVX=>R9TFq=WAas)q6``tnKe0cEa1I8aF6&-aIgaO1pFKtHwXRX` zSe7*`M(14v6v=y(BG~*Xa9chc;X7YbXS_%HC^*P&6INc z$f0)RP~=H;FXcGDjN(dE>2p;zq8Q!uD?v8;3nOwspu4~tF_#Wz;A3Tm60@$iMy~0! zewxLVsp#K=q-%f%q(HE}gRcrd_2SaJSdJGfRBsvP4rU-o)Gx;hc>#IO>Kd|3t)*C- zUxdoq`+2&R2DjlS2)ibzc|2+X@DOm)J;zSD%CcmLfqXAS!oghL5xw~*1Qc{sEZbcG z=+y5BTn0?*3bX|S{(Q&!@}?UE1->2+vwa>pNrdSd38(OV@W z#EU%r+$SB*(F$?}9Bt8x-o2iXVQ_{BIXqr2EVeW|m-)&zzO6qkFvmHI!osYtZQxuXYAS(kvlRCbY}PJO?_sLf zLnb4QHqXm0jS4gKn$m_!)zQ+2n5?I7)~V-_dEFqEHzl@Veut`y2vX3p(UdMQH(`sG zmP!JL?ClWJDX_f9?Y(I`gG?f3=hy%IiXxY|p_hc4x5$u4&q?5|n`K;g)DP|GUCP;7 zHx37yd;1Pv+s^K#B#)&SWHJVP$GGK;iBM*~W8A-92EnzA|1AQ_|F;Op_$>m~KK~;E zp5H`SpWYzWaJ`BB+z{yIy7DLb)(*`=TSgh}x#Z4Z-v+8WG-P8eY7)yj5t2CBGnG+O zAzX0I$wsuoh|Yy@vFx@1?fCai@0;aU!9jg&Cg>O#z>I(n(<3Is4AflJNkrUTyWC#| zne!J(_RkjJc79a~6pZosP5R3yqZv6<0W8@D=Ns{X#BB7|g z;)n=|7nwzlu!I+bbCCqYf3QT{2CcwlllUS*OQP&U);ScdybynWWHI|x<%43kM`%vRaNwA3gtmy3A}lK zTxpXJYf~udiLOUswBeUl?P-W7Zby@&%adq& zTUhU~^I?HIT!y3pR%)YfT`pQWfH^FP^H>|3%4H>=SFqqFJTNFXvza3~nj}wf5E6Dr zxs{20T@A9ZK+zsGNjLHKW;uH7*6-k~Wk6OV0z_BRWT5&VqwP_*3Iezo4#3(1;DQfv zB5x&Y4JTXiGgqr(Xey$b<_9aZNPk;cRG0EeG*I@`TL?3| zWD7^4)871=i3iK$FFYC4f>WVV+xkQrG`EMLD{ywVimmtUGeK32ng-%Nr*K;<-278y zx3+6ZQ>q}d{G-(Y)Oj!VL!?y}QwwpnGf9<>s5Zf}zCS)Xq9e(krq4X`V&=DRZ_&se@jC40Y{u8vJl* zl6(x+2xmE|aM{*tz8}Q6{Fwl$*!cno#Qo~Mh#CC`RC7a1qTEo`DLhDFMr^Oglitu^x4nnS-KV~e*!8;M zPg$FZ^ijhJa+Gg0I#H4#L-~VEqwq!(Jw_8t7o#HlYOC1ZW1IxAG{=~N1=gwnmGiHo z+SG$?9GV2Cg21R=6t&oeU*gcD2Hv&Oq++RM&hz_;5=DABXQM^Pl?kILVd^f=A=s+D z{)fS04!FFca#Zr*Z8Vi-czeY+Z=$B&Il=@-6aBF1dWmO$>%NjKD;JWEC+wGT`#fa` zp@+YqWQ9mV&R^*}U^WH<+pgf3y}|-iX?;koBP7k$XWNs?lTu>Yp3P~QzneY86pL># zg&$V-eir_XJ4>)ny!<2j%g%Nd9;MA?LyoO&4sJ`Xh~pidqB`SqjNv?)GV*PLQtR2a z0J{Zcl$`VLHA=oG!iXkTVvXm11p)W5-9fGX>fer-8J(ho(hNJzJwj2kD0)JHxZ%C1Rs~lPxFZ@(>La1Vx>mI6G z&|Pf5rUpKUoDhd|Whr3kpupFDBk+KEoeh z&4fsw7c~HdR6udXkJ~X&67=y*9)b3e+xW4U9lils<^+T1dmoKNYXA8XNJR6H`Go%@ z*ir3yvjR+je1bRQY>_X@z-3!U7v^msbSCjS)*8TQwN-X0=| zxZ2VgyJbI=6pnjo%X$O0UR|R7&!k<>q6=q%A3%+BP`eMLIMqDt+0>d6Q`N>|+sHRw zQn@8i&;?_C`3W#%rn{S9b28MojeQB!YTn4acZS|0TgHYvTF`KUSv=J#D%8 zocIV&WJ>Gkh#Io zgnAqSLuf5cFPdbMYSitKDLRwZVTLW2VTBkqM?#9n z9fVDh+>}^Olr%(6^lSIGg5-I~9l`Q|Ft$F@6nYm}Z@xP1=yuT#JEDy<&Q`m&x8o)J zvT{;tJKD=nHw%p*2K+5uKldozUt9bA;W#*$f6K|bEv(Q=V>6G@Nq!$T+6d^S-dE7U zsi=6IsY<_MsmbSdxa~?&>dsqhC)Cnux-(9XH~P<$48?{3*NVpynY7bx!Q;ii8J5C_ zT|Fdy-cX*jWE%0L_%sjCu(Qvc`RCyV!E_I zrOWMWvN#zR{M09g4?5|}{q5s%;K1fGqi8E~1OvuRHkKx?oSC5+s*!yjU152({)CP2 zbC9Qx)0&}pOet!rDYvtal=r^U^O-^hQtjx>3IQ?lbjOZB6eY$p<+SJc^&-WzxqfiA zHcN1Lh4h}n42$QFi&z#KQx_b;RjtAH;SS3(N;yGD$p4lnY*o=ue+c-!V?Qw83ev9| z{V7W{gtp!S`SZ!iRlsxrbdKD994wx9Zo-fj$%xgaIm3{nSQzGqaaw1x(VH53rg-SY z8XcOgf7xW3rKD=7Ksh3@W{fSV12gk$riygk3}MHrVxxsd>(x%5a4gzwj}2vFsdO>j zpQOGA3y7n*4stV+^Jjp7f9@oW*A<{}3p{Lh%G=K0`^;LYywP`Ds zw4#ek(XI*OhDVVBr=N^qC6=eAa`5(j}XY2^_thDv1 z0wNGbAX`xS)Dwb9FC)+r14+?2UQ&oyxCVu7+$7PFhfvn!S~pgVTK25ZO_DuD-!VkL zssg9;?15KoIp2JulKq)9pWyW%y=fQNRw){l(k612*5C*jLAt7zb_xZbihqPCk2+z7 zbubaWuy?{vfR1l)osO;QA!VXb6nIgYP*%t3_KINiRu&-yv>oZ}<_85m>y57kDB_nC+K9<2Uc(2rt>pk!_(o&MJ2~ zkz-XG8+=IQ8b^(1F?mBKU3z8BQ0x(|lbDpr=(wKWh2)uHQ&o+vLMX3ghy<@|AKcIO zYq5M}k|4(4d90^Wdh;QOVNTC!x-_h;9dxE-5S*$+*{KZIvSCgPrcIRR_6=Z8KAWx? zmzyL7%*$x&pS>cbO*)a&DDlyFxwEXb%x+vJAAlbHv(;l{mLF7PVsxPH@r&w5Kn0{H zMVUntAtNCPOlD%Qh@8!NYuP#Z=(|(aJ6ip#K`rlvmV5lv29DXK;2mqRIuw@DpYzhi z&ah6u$0hcHn&h@H-F{!N3DLVVJEIgQsA;I3(xJD5LW7$r-OWDM)UYrH$Er%+&56mw zj~`r>5zT^vpk}?K5q<8AaiSz|*e5w%eF)@x1k-iOw=I-vtAD;BeZmSjW|&3FI^V5J zxI732&QP@=T9S>c0{*$P2HUd8oe#Z{#~eJH5v*@dlb{zNZHq9!*JeJkesJ(YYHNc@ zkHqgA6dCP}6tiyB4nJsi}Yjt%nmyOd#o&JFT_VMw0S#_-;wU~d7f z$w(nq8RthfIduTc929Ei0~vTuxVBIz1kjg$5we_^)tUqvh~*)hKlGy9y1z(mP8I~y zZMG%uPbi_JdH>BScms@|KS5W7P8#W{AY>-3MUF6%Hh?HvS!_X_B+V%vfl%?Y+F@aY zd+sxOviFtQ^Dn4O1+8Jzz|mZZboyw-^n37!>7|P(>(1dh^f=tC6=zs}q>Wl?UMXh; zrqqcF>WwGC#bs#^Ihs_JptrT#(O)8^#>^tBc-!s1dWGcQqqiP|oA~;Y&%w9iTqrPn zAD(zB%{3kAHvF^xEg(8e(Qd3PH<9jxX?jEWKZgOPWA(m8mt%qL?tgFS{~LA|wE9MW zMvDJIe`)}+fAu>a?qH|&yY6loKex=irn}t_r{rCDHs0$Pzi-R`XInQ{@O`_xC!6>y zZK1?Qq`=()<4uq2v)t(!Rm)sVA#BwGOD^0_-FI2Q7Ngrr^jJ(&iKMPi@f*F`mzFhi zdFg1$Ay+(??LCDWTyJY2`wlUKhs>%G4BXZ1XRBQodg3ESfY z&&mHPH@lof_zEKzzp(|`-6{bwb$D40w7=bWV14P%EzC*p{{j4fSiQ6?j|$JWPJ*Y9 zqM(XiQbw{op%zt8fdVpRS$2b_PE1kpu&P+^s2J~=mYTJnA$ehs?@#2c9aY1Ab(uG| za<&(4)|H<*t##yyTET|NG`B(?dRBdN%liFeC2${^X@nP+p7!F&pkER9Qcn0T^jlAMj2qPRtgWQ8^sp?K;ByCJkWD35si8KLXV`;Mtbb zz9?)zjCJGDXqwuBeT@BeDg~q-eKQQQh_yjN7+N#&siKeu2wJj_P@wuKilHLh^gfu>l7IkfKCMdH@N$NT{ zCSOkNh|$EU$1A}#JH9cqo2}lRv|+`r>ycxnhQm|;{Z(P6@D1pELlqR`*oARDW!|B2 zY$68;?A2hi;^ZeHG`&lCj7M`h^v)VkJ1;&t-dP)Gh~7@W3tXmXGSWz-qc`y5gc+&H zVdkc?cR}g!W7sl$0X>L|;*c_wg7$uS?^4)$o5$(jP=#edU=qlv#FL>^EW+<$}j=w2B zNXq}&c!(NPJ+~7@M+c&p77E*>-f{Kdhg?I@4b6D}7a0u|p+XA+m zh$snE_KcjlR&JOP$2q}16k5)kVg8O)t6YRnY!3{6B$><8^A!i~{+!v);WTq|b2f5M z0f~#Nc0C|eI%0{F$ZBt*Ex*pa#?_}P?}ZDxUXS#YpJQliut+L-?KK45w`J!KZ{8^g z5(#n+)rQMIe`Oo(iYeIOcyN*!zaA2~3IF1kI@TcU=K{JzxnS{V#IP;oZ1y&5&kpYL z$o`I&O;ifJc7mD{`~~$GD=e8EiB`RV zV(U5Y^v+z>rn45|Ft|C&2FV;UJh zX<0ca@Z8GS#3^w`l1!uGj~0hC9F6g?`iaM(95p>-edzfPTncU#bc_QjSK7l7hsW!U z3NR&tzH`&t^57j+wrtmW6DXQYyaCI5%SD0pNR%k{SanN9ntRX1i^C|%0VW?)+WBN* z*Iq2U?*zv=evZ|`R(;h3e`I_a#1qs^zo2xI$zaaUQ$qY6kXDUQOVp5U3+B?~*5qK) zK9pJ~4}h)?J3w1;T(obq{}f|dJi`Rokp_kSZr81-2rOHlGd(DZbp6|oQq<0z)#OZePdwOl95f}Ujh??R6S#eAXL4lKp#_D7 zz0j}^M)Gz1sFox&4tWJq-JBJ+n31FyV}b+|tk~+g*=9pJZ{ZJ#M4D5h3xNofdC;^d zlwqEe?a|n#H2)P0x(HX>FNPp}tYV#$p2X07HLHU<+pdvnJN3S;3Zh^p)n1t?_qMpC z1L9yN!4dAP_f#wx)bJclkcL;Fw94Ei6ha{}{@ct=FOPP;fYJut>jY9TDUNO=Vj=f? zDU08oI!1|2m-$^f;(viYZy>&>Ap5 z-mRm$LhItrBB239oYkjgC3Pbn3RW3BkOLX+fZlls z_AeKpVtfk?hl8}9Eg|%LnItf=KU6}Tz>2gmGxP+Cw1Lm^LJN@~E~rRN1wgv4;ac4t zE65~4FS@G1SGoqW>AS4bHelG{)JpVV<78A#%3csmWZg4=SR>=GS4NtuJq|<|oEo>% zIhse`TkF-N)61s40_`ssZX}3UHbv(CW!=bx^DDgjbUFFTM%Vllxc2(hh%8;x{PCQk zmHV*wLv-EB;@f|=vRF@tT^&BOVabS7M>LVkjK=Ku0|Puu&c6!YIDtvWZl(sk5sdML zUxA#s!+p2fG;=0kE4bN^VBp{xD#(U@Q9t&R$6UANgbRH(bfwcqi3U0gf{#34*Rq@h z0Y62XkdR2#1?k=poa;PjK6H-%mYqG(|4 zMUv|ka?^SzPRQ|A**>_C@4S|0=Y!D+;Z8eX%UC2rJMoG_-*|HH+aU8_m{9@4`X{%g z<_F`?MgC|;sbzWABp*-?ZBWf!Iuie z0!K!#6L;`;8LpRV6u|_!0(<Z4g=IU*8o?q2SVmNkh}S zd(R^HVSrf#6|d?<|*hZMCL$GKM<9-B0H+3vqBdw8JpPu z#nn3o=Mt=Kqp_V8+qUgw#aXd!+qP}nwr$(CZGPGBe$RL6oIhRj^i)4p^P^|_y6+21 zE~kuCo0S&Ts0$xvf~|rOh3fSn@)2~!jRchhA0S6&qK;`v0b82_^qP#rrUxpZa?7fJ*qO{)Xeeejqx+m)jhSmct{`JcbtXs+cg_RswcLF}{eS zVJI#HLu+vL{zja;23*>OVTS7AoLh%$`D%rHZ}ClBpj*yK=ElO zIsl*nw(9hNelQ@j+H&0ist7GOrvXYr2)74CZm+!pNTW1rOm7gUjjCWZprbu}YNHZdKbnG-9bRTUhJ^xCCx{!4z7U z-7{Rw z*j&>n1^JxD0^6$A!_aP-z=piju_Ucqz%5`R1tM(Aqe#yp7DSfxjY9hAQKT5XJ&ph3 zpoX>MoLV!bQ(^?J`T0rhk)ACYIdx;lL&-l6xN$slUd?;%ifI)AO;}qA*tY5ItH`ld zvArHIA@~;64lV|Bufu6eNviaHU{T16w0SFf3vEt*7DobDJo5rc5pkpgjW}J>STXjB`gvMP36@U`s7GyD z;P)>^j6&C$Fc2?epLgd*#`PE^%X=*|y!$KMLA;s2puw|(!z(*A=b__@*Lgl&sf(7} zm9b`rK?Bwz+FTRG-9MU^5Z3bN7qh$!EtQU{<|?`BO*AAqud4RS2Ll}bF`iLoS*l85 zUILw7og5R?HyKm1mYeKM-!N1^gnLv;L({&XG4&%^pBK-BQ9){P*3@M>axUuM8V`Ru z|Fn-?zomN#2vQ^v5G0oKoK@Lp&i?bz#|`&Bo(fvPn5EG~AA`Jqq>pw6;(l3U0@+fu z%j&fBY;XdrZsmjBonJJLw;-ve#pu!(Me|;QvaOoMW_r}D4E(jCpzw}ztQ7&^(08Wd zDj{K#sLzC~{qxfgnwjO_>u6QIaR0$JHClBL-N=Jw%CzDwjlnWmbt%r|SxVuyD+>*a z`D%z^-yQEaHecm9#1Ovl0-X4OkTW}6sckDiLQ2(PDRbmbK?7z`swCW*rpUzcwtuG! z9wdYrE(M|{AS`x7fFB6K0|?&V%b-XJcxa9j;Y;8UBbq8U7e9sqiq#47N=fX%;#cOp zhD~AFMVx6#9^p$)Ge$tdre_QzhpqyP43t>7D||g&Q=pL#NJ9r;M2xj|LSmrzi2nN& z`ojRGG75%D<{51IFI4QFCrsi);5C};sLwLvqTfJ9$^CIdlblJd(SKx6l(IybL}V=+ zZHsF&F{jR0_2!VVXfjjjOGF*i@u}VQ3xX12j}}d3paVs8(imvwOy3x2GS6s!Z?{YL zzmPDivo~nUL>)V3xIduESgU7=PI%bt8J3s7EEVuQ+71hp)3x8wSsus|s&Fs-8OmhI zVLtN{GV`vJs-nkADNNc7AkeF_^PdM5aT?RR0>q{53?N6ANi*w zVN+FTNbUw1sE@spi?Wn| z^<#e^T(0^Wcf_ha5ZVU|1LN}A$u9CFN6M5$CClK~Qfe%9S|6UVH+qo7Zp=1%+)Hkg zG70GJIimZKq0h92&drK=w~#C)^qS>)suoSW{iP`;H;%atk{TO#X{ZF_gr=z?BV|nj zSfZT*(smtb29N;%gsp%uEeZwC;)a_~bBSt+=h324GM`jS0Vq3nB=d@H(b_QW-^u~~ zh5g%YjRupyVYL5pNCN~FUB33yd9uob90lLl5xWyw2)XzL@qNGJeRKD#gY&s|=y8Vp z(eOF{RsO%89O$?oEy(hI@cC4I1pO~@h3_E;hu8Ich)N6`r^D^$=tkCt_oeT|r}kmM z=DUjTmm2;}vkze1zKyYqG(&oNTON|_{y)XAiz0D{`T1_PMbD!$E)CG?{N2#;1)R0nvQG(yaM2>BYfn2S^X21{tKN2F&1d}j7KLt?6t0UBM69z`Xi z-SdsI3LC2vxvyl?r1*+%P|rvkS{+=nWE_I% zmPR1@|1ejq{L%ZP8AY)&GK7w2e`5(k^)RIu$QSgG0VQlBH^nplD`iXmUu=#r|36^_ zs4c6(yBAllHD&_`pqqQpr59JM3+$o3|52-H{Q~YdvH!*mkf5}rscssVw#wMseGbq6 zQeK{su{OjlElp@~Tv@fED7qLZVt5&-==xIIz9SRGa$Zl1%GNT| zRXG=I_aLzYs)bq;B>J7JMZB!Q8(6gc_q0;NfvofO6E6HnOO7zXBO{zknnGe5$A!!UFb@|VX zFZ9}kGLW@Mh>~Dt*|ymA;-9*uR>5s2u+h28JW&Vc*EU!9#*xL&W^r(#hwNKeFH`o_yVk!h??h7#cL>k10cb~y3mrro z1SYb`TLD{5)I7KEy8PppvN+(+m$^i_qOV&nC9ZzLq}-kT9Lq599!^#aI*eyAh3dFb z5EL7gQrr{(afFz%oehyp{Wyma;v(Q?VWFy;3>Y{1C_?6@Hi0TyVnncKsxR-mT<(0c z&>HhqigDAiroN#Z^JQS}5NduBSgp-~rfRBRd*Zfsri0USte11VEWdwv@RG+cOmNs? zu`{c^u-zGLl7iU3H^PSRvhBE8AoKyk;o1}>#@2W^HL7wL-=s>%le+tjuS7bjXGgcw zVE410d@M-d`h{RB06fiiRa_l~&CK1&dEXi4QCsI%d0b{^DK*$1G}@AgPuPFiVsuIfPl2IQgZ+*XwKCYN8`Dy=!}S(iu+_Q+B_Dwm(`e` zJU3&V|HTnj?0%t}%?SY(rw$EMm}mNIv=>-U84)rlpfxp>jyA5FiD@0oNg+c0`r!C6 zHaJ_x)=Cb(`Q#{CisLtLAn0&ubjaWj+A7z86%3fwk5s@C91M2HPL2%B;k48e8k|Mc zaM4>B{W8ypE2qz3%m8ZOdzrI8M@9OcH8S6DW|6Z z^?*6r2{KGJ;(E1tB{#vtg}v!ra6PU~IN1{%T(}%nsMYj87IA6Aj0RK8j$DhQsm9wK z@sv$vvZSM!l>#rNl<4E;@&^t)p?>g9;I|EJ=vK$Z5hb9Qo)JY7)ogc*MRXxcd@09M(j$A8)`JCfJdE5;8*F!3

uM}Z+JgU%%q>DSvqH%MjF<_lr^ejb)dVr2WKQoL;&GvlUFh`9wgswYL=zHd`(S(5 zseKs*yb-E>J;0nC&{B{EKYV2BqE)7%Ox;ZUd%tmrZKFc?o-h)ZQ3XE4x+7EEb{w_l zH54!Yc(0cGRH*xg%oJw%Ap0x!#h%#r=lYKcC3AcqV2rZSo<*=~{8Kq1{JpFyGiu5M z;O=L>oSmx}`$d4iQ3RH-GxQnWpp%bQ(oi7*O0rTNxj)V}VlcEySzkW!#%=U(ij)K_ z>QQ>IOQ1lv8;j~Nw@BLomC-ySY%+tH9_Mx^CUyFPfU zhpyBNutV=0?wfGfVuq&5v1brSl>VtKmv&E8@kKdk%`RZRhhKNdTFQ7$tC(O*t}Z{- zeI7UWxjXY(vIOcJ`U1)TXs&lSlwRL^Dty01 z8t=G!9`27R@EP8h&j^)S!<#Rz<+-o1n2$jC9$Ut)&*LBx&%eJK2Ex#E2tpUaQ0z4H z!#8|ICWb#_kPKp96Ayf)h5Vz_;BjeydBwQoQSQ2GVTFN!=NoWs8a}yteQ^dr&IHkZ z{Mw0DO_`vOe;@|*kj4PK^0}cv5U2e?pg{gb`w2sVF!%=(LxJc?1c5*W)&lp>9pR1q ze1(AKCX>*MA&&%Ga9{fsvo&f9U9dF@2noSjt?pXYi?rF9-q1k zX$*iqedX}{l)&+Qe9vV6Ag{!H_7Dk-#_~J?sKCa^)Wod?k<*|X-+Vhw-SF4P+d<}njX03N$HX@Sm-K|nyS0Cy&&XM?3V=di1 z1pi!W=je!BX7^#wRMcKGR4<7l8GVcjvM{$Heu{jS+3r?zpTXF-$~XWtr9?A_H_AM3pZ!)Mp_?TDmY*@hkqyg>fv!(p(CPjlTay7 zZyIe`i!m?sC*C4Yq0$zLo`sv4lwGAdf5Fjc8BQwVnKuYSUsvk^qE@42+Eh!;gGnoA zyCldKONmffvvLJ@Qf`NVZbeS*46R;gXGg-NgBOvqBk9@QOO0Nw%;RaJ5_lX3zZyc6 z`$8$L;%UQ3r)npj&uk~IOT8xpqy}_ng-6t63m0=84(dw~Zy$ivrIF^veazpsHx}_r znk0}Y9BM+;yN~BxKW-&nfE*G8-U>s*Ju4q(GxR!A!+*j0I zEYeowEWXG<@IE0D?XVdojnN^}z^?xaA!FGM6A5Rhf2$!0YL-c-z}A4BBUk1eP?4HT z_#PmAha&sJ#7@TMkgy>g&rW`!)HKK>F;5Q`K>gZv&Z#E9%t%kE3KeVh>9Se}(0O|2 zqx91+M%q7kQY7X>uDgYF?Pn}P(0_#%5_wPuAw0m8)9&haHims{;P854Pw7W$l;a#x zeyM6JxY_M%_*31R`EL}WTnm_Z*N8=q(F5A%y;e89MEGvEDmH%%!BI|+`Irwj%>x9@ znc=@)l;=8cXnVWb2=nooBV*eLH&@{uhrkWoiR^^g{Z}%L3>DxSA-d>FkLN&a-GL(#&K_z5RrY|w>Wg6Oy6+R->oX<|w6oLhp(FBuRCc!z22Ff{9=1?n*b*ubt< zuYM#fVdN7ZfEfN6xiF#Hl>TY>7cZqS?noFKh5yWOFZIe${b~e>U@2Io#%6eL$P$hj zwHemGkGi|Q4`q+S8b5C}M}1z&oW8WUfuGreww9OF><8CG8eQPa9MLvUo1BtjGZJT& zOcIyECQ4a1CGs?hP*iNEY%NnEx#OeqIf!$l{#Kq#dNl zLY&cEhN9&ldEcCp?Uy+7;D?z8W4y7@$69@!i;ZTkVil=5uToudHMriKMh|+(464DUp{w70LWxQjk3(N`LXq>&43 z$Ilr?J-;6WC!(E%?$S#Mjbq9*6HMI&BP))_i2Xd0cw=886m0$>&c>jdr_d z(;w%H|G4ls^w_@sCn#jOhVO>yI#^5EDxb^O0bFNeG5 z@N!cv+B*$iyq)s;1|;9%pYzp!M#{VYcT|4)&-MR|*SZA18iPgTEZM2!#X(j$y=Bg@ zjAa6A&RMYFjh7zl5zi}cmvXLAA6=?rK-Kwl{k>hQjef>`(DV}DyU*$hjBM6At(8*& zdefO!^_lnLxtA~-r<7hISPRCdZpI3^Xn>ahSMBA(73+hZY{FHejA+ES1jsj+>C#S} zBK`f;7u;ULvHyWwOhbFAn*!^3plZJ@#xt@Sl})nByq z59NtiI5PjAto|T5>I`Y|Fd{TRGNI@k05gI9pQvVJg#(Df=Ou?VK3$%hi8?qeT{yYPV*a>yv61X4f$!dZky#m%~xb zhMa?u)`b%~{xj1XbZJBd*pfd$Nx=I9xfwp0+7PG-s}W+uu)TpY&1{Y5+6P$=OetZfkP10{;zXkSU_;-VS}0! zh}77LEdv3K2jWxc0MxWVg%^cBs9ABZ|!TMENm*O2?}S*zy&#)G6+ z0P~;Y^6y1E|4)8H(V+-`1B9Ixf72MUU<6*Y9JYQc--5Vc$4W%6TbwRQbjN>y2HO^Mq+MmmWYfuV!$MAM4o9ZSyW1tuWq$Jya9nGR zr0R%Fc|<;FeB{s1WfycT@t6}jYfC3EJQ?jCgk+yCyXl{2smVz#SsR~YpH$G7=WAaQ zRF~Z=T)2>-dZmRd9tEx~rKs}rtgHc?L(sp$g7rrRtHnkkH0jBvlAQnU*`&t(`{C9a z#pRSo^GPKCQ1rvZq4p7x$l)b06P!#`o>!k&PgYKjmse+(b!TLqU5`(u+;n-qX&-;c z`5r5r8)CajjI!_-(57ggR>*ujd#=vFMQ+@7@ODrf4>O&MrvVou64UxaPMdMno_ zY?B<2BrpxdEprVjVj1=0&5I?+@ai4ldS!~7olg#8Q$B*NS@wkPDpHtNm9ENmL>}tg zZY_ewxIIGGi%Br?!wDRXgq&syvPM_q;1LgsNhlc_>TG5oOJmZCf-p7?(q+>Ft4#4} zo{R*#B%M@*KFXmMc%mH4n76p2UJ!+gPodEnu{4s`c zkY*29$k}a7JLaDNIJ-hj|35TqI~+1fw1ms>902UtGmM>ZvGmAGG*+jwsKV-qczH@p=ZPs?h2e~q4q z4tiY9MD&%Dm>G~$efAxd&H9%LuZG(;l}JPtl6t~ZdZ21T_yT;sHBk-|i$WqW4u!w2 zNn_9ukwirPffL#<|8?2|o&+4d#%WxRih{>`o566&CxK7^HKPBpJiT;Ccy2>^ev9Y0 zHRj|2x6^RJ?sRw^9l8g@K!t>`M-0?VTOco|wFbIV81=U?=`YPJ+^uQ{0hu8aHi{!) z=*Brf=$j?pwSXc6*&FolI+vp`Z-(cFG;~*`7BaA?|Chb5hW;{gi!JPF0LcY=T80r~ zFbZ@=v;l#$_V;wXw=Tw3%#b>)tt(m?JOE#whT?saUW{mkp<@I?W$j5jh7goJ<-PHM zV}Oj9mlid~*gc%U>}!pH8uKJtB}{;)%1qV2|J>s%we?VDx5< z0CZuL8#9X?Ylak=ro`s>lb_E!R!2iN7H|8XFI^!9DJB59|GrXq&L76dO)!Lbf{0bV za@D>|-cMB=yjx|W3+P{l6cf% zVu8}#0jmjyoWYmj{-k5u;)~-*fs*r4nJc_4uHxDnepKw1ZP0#gAPm_FA+=vWCkj#& zt5#`TefPJR#qMdhIY2Lp$cw@5*9J!bQNsJ#ENrt zq(HM7MQ%s?)-85cL|L^e z#*T=9TV-a&O{K}APBMdAPzvpsrsR(>a4DG6D1CkIV(5r{`SdH4{62$lKmpK7*dcWZ zqL62Ls%Wx(tmhr-CGSpsLsHeU6NO-JKKW4@2#=V1Qtv1X6!Q)QB)9SocK;{@#i;BZ zh)gy^;h`~?^^c?&HY>fh=8B{6b`v*fDZaWUN3^F2g_#<%iYN+OO(XbC+l(0|cvPBR z9TxO=su~NJ>a}o#o9fY`5~!zoWC74PlqkJba)hd*rna6TBEht(|4?T)O#A6F| zb$!--aRm)NTQKDQHYMkhP;dy=*A*aoYkC4xO zZo6cn27maTwWwZy&RIU*zhpZ zAJ;cp#<4+xVG21^$%#&s7=%wqf=kPB)R*A244>XEn}nPc>4W5hlpq(%1J6Kd^e7dG z73KOBn+eNv1IsK$79BMDnZ4#d%7@LA4iu?$PwIxtwr@{CQP_r1uXBd@`0`r6>>MHH zh2WiVQX+e7cD=CLfNRmto@*?~P%X3Sx$1N;61T!4wmFYjpZ_fN7&+_rEmL$LUu)A}SOvAL z20;F(Z^8O2-kAF{Ro_J3wzKzZ3xx&M{v!;J|OT-|eVbbS(&^dif)xl1ycPxK?A>s@-f~R`MVWCiuP4Cj6FZH3T^|TscWII}8=3 z4b8H6)Hu6BsU|KtJDf$_{3mqg(z8T}3MD=>qY-5%SR0dDW<(NUFvKf=)QA!L8KI{~ zXI%&%!b^&K*Q)di7K0~Smc|#j`a;%%ET`Xyn6V{ME}@0#`a&xrCe16e^GdFO2IJ#t z_jJwxZ;1UyCuimmCd;3nAs8H73xBw?Ov-dWYDstA^)!|%YRT-brEi!TBX#*NjBw-r zwYcdaZ{~mosf!0!u=>laCnsmtp2B%2)bpIdGAMu3fS28R5gcSA_X3P#Tedpu3)Q77 zCw)B7_*M0!v#m%@>G?eK;jJ?;1{B)X1CMc@5k0bL<39~9c^WeQ5?bYA#g1Y3=_41* zVJw0?6PiIVddsodNsOGU5=|*R6G~Ib@Llfk?L{C+#H&j;a z?ZRW>9SoRE1$KZ5XH{nVIp7HlA^JVEZPsj7Ucc#QGxacJM`d6Iqa9xp!W=s(_TT0y z>%Cb&_bKjz0gwzkHn|^jT}-DpuAb5M<0TJ)yX{*-M`HW7_zmCP;O7D(LMY zFN&(j4!G#tI%hV;8?^kkv)OwP7bz$p7Ao$EhmujP!4igGQr4p2JcK^ z`m%svoJ{~PJQb3Lvxw;sU1g>6d+jQ7z=!UR61H8!^w`9uS<>!Y9=M6A4+r$|zDb$I zsnr(QR@mvWsT?x-UzccddeO_^J3Hxyrd|lfn_038lp{$k1CEumTzDvBBQogG1aT-K z5S>L{`@&W-#5=D4V-WbF(ior!324J9V4B%?yEtR`LR2PQh__0_xA~)!Lr-zW^h&vckJfTnKmdQF)SxyRZGT1Z;I;8 z+26dw4OW{Tu_MR|C7v>qx*xG~4w7S?uM*LsEUVo7B5oFJw@5U-22Z$JQSCR!oRFwR z&xT4yrlZWa99k}@vzp^N-f;X$9Lic%GdRj|hk{TPD!>$3P$3@_X|D3BBcx(_J6J0^ zL;Z5D+I(S!G*QX1BvAFzD_Z@jM?Xp6$U)beUC$S+BI>f|8@TdAuVDzrG`2W8bEqcE zZ$+OwJLcXR=D$wFZOlsY2xr*mvt9)Nvn)%z2@nZM(q2zo{oC|EetP2Nw_iu9>14CA zo*$5*dTcM4ZOX8>!KIVu1f<@y{7`u!T4?Czx`^ag@ni$mLn;uiztwY7<9bNScyun@9F(;K8&# z8=s$@3rUkG9g7f>$KhJ$sjGsPq5MmE?$`T@x~-R_vB}uo^iJ8=>xX z=v^?+Bw|Q1JlF;{i>OkQEw3;6f$uhpF4B%!U2tFOHJ+x2Hr+NeX539sVYK4gO!7S3 zj;Aw%(i7Ql9QZiVF0`z!M^5~&JGag|A3EnZ^Cq=O*Ckh)XjeW}s$d*asy{L}Ha~)H z?vCaxmX5PUmQzfo9*pg_uP~b0Ow?|U)(+f|xbWKPJ~rLt_d9307Tv4d2^Ej1{^`Xl zZiSFr<`r028|#NFZ#~S~$e@vJr}28F$xThGRe%tY&FiUvL)%4P8WE)+^{x2{S0_(( z>gbA*ZsEFgj6OUgU#B#JfS$L{>uH*m1p@{+z*fZ7E4-X?sK@N~rgqVe+in18EuMrC zEg4J;BeXg>b2f7bXTi--J?!#lM;iWf;p88e32AFyGK0h!nZxcRJzCZ2*I4a*LGA(j z7$4X9lI?juH?sUa$9C`N^!=#(UoeSw@N}Bv`TQRo>9Xy>S9_mFhTG%#Hlf3W=QV;X ztL<%c(gC02UH7zG`L&95^K-tu!uKEq&+Bn!SAXOkm^jaz+k4!HQhMgrbm68_&BIb2 zlRk_VT$h*Yu&QHWw6PQ-qtXcEmn$Yqhp#!)~x5XkDmiU$C`fuym5;IT5(uhTq2+M#q@W zMGxU;sen;W{kp1-&S8vD$0IQ4>>lgvup65t;sD{N-(qU5dw-vTuW+=yiR$(5JGI&-CEBL)B)%4j``-4&bfC)>fvriUzoP+%_ef!*{mf~^M(*ovb_i|?8N3QmdXTe!D%AQ} zLX|-|q*uMl7PmW_4%@)4sOI;c$Q$`KlpJcKsQ$UU4*ank}4*;ktI)G z)T)qm&GqwMT?&_8XpVY6Na)Rt)fzLCVgIqi{SB6H_Nj3!Y^VMcGDu@%ep)?7S>8Fl zH(Cq0lE#6Q zzxWlvyt!-;{L&Z_9-%-B8<)S#B6T>=DC0U!;Bqu6N$2%~?}>N+=SS5F^hc))q( zr+^v_NF9&WWGADh6s+hdzuxwS8>5#L&#%?TsBGXl`fdi{S4nwXZ*7|QQ$uW)5|Mo} zQ#dfDZvV!|@zF+2I$nOxmOiZ4D+>PtzdMAu9xz%)50l}#H-H|3kmJX<%G(}4W&nt8 zAJj#}XBa7$fJEjgsMU#RxK`?!Qw{V=0!+?pHJQ=zkp{$Qz(_38w#o<^a61J~sE2m8 zF|PDPKW&MW`{hZUBSM#ntB?ad zKP^otjD+&bD(0i6;R=SYH4}{m5Y+^}33J4#?OAh}kn(vihZ6X2ZA?KB!g_#WmpFRk zk+3MZF5ErTKh%2BpaNQa%X=QOgQWd*wvUnrGnXJy2Tr1(-}i%qio5JNvPfC5k^bII zPB0q}4;La*Mw?8JAjvO5%mSY8woeNyQx2?P6RoDp)NaUAwG*hnhnOxE+0TU zumO{7Y9pBr26oljMLDFZsey7mN~fCtl@LzxvIYNy%5wTe@X`q5XnhQf{ww~7 z2WA@_YwRm{6ih@QE{v=1dAYiQPf6gJ^a@7wwapMW=ErHsBP z-X5z7wZOmBbJaRCZA;4aFQ;`%1IygNU6U!d6UsTt(WDAg zpZWAmOf_9jqK;>Dxoq&$_u$^&P6AiREi*zetHs1`V=tZdSi@)YzzF9 zLQ~T<9D487v9M#X+@t2eu`tf8?^V1G|8gygc;u%_aMW89_1Ex%hs?PC2VDRsQD4}Ge_!;doCPh zp(4vDd5FC;GRlCJGENuW&90V7LkxV6vQoj5igY!UmSJQWKCR!{41AfJpA#142zYF# ziS?O<9!T+^xpr8`;OJi(Tw_C{WJ0D=L+xT3tB@Y5@(EC_1zLR(V&WhgoVr3~t9O(P zAzEZ(@5Um%FvZe}m`p?_M3;azHq(AURE1?*?!k_lcbIrdxrba6tH&u01#=n05`0WY zeJzjm)K{;)8DyUBT?yB{@ED+H#(xo0HjYi!^lzChq)vAQ^PQX_hmCE(-^!2F$GUpZ zlo{xd$p`o6eMEsWsqHgvih&Lq2k9-}j-u!$a?A*cNwXCwNs%=|I|xylT1+ytYLJWW zFrY0>f_{O9h`D z#=mUNdc}B1!G5W6FVpB?pp11Qh;ZZZDW5P?%sFy&6Y7Y&XF3uJS?V6!^4r!?{`?BB z`dfTfIK=uSTue8M*LoXL|Hd>VF%dNL2jV$=4Z~bnwLbHjrbkd=ndXzchsHBFr(CNu zRm&6gO0qLBpb3|9KWS2}vC4deW zST|)T4~yKHBnM|)%WN#(UF&euwv}ean)C5w??*h1y@Y{4#|lD)+b$*nOX`$I<$cAn zaW6h6iX&k83-v0Fdi5!Q*8VPCC@SNRXeRVO{vFoLc^iDu~&1(SxGh?!YCxJxzw&M*rkzi@V3> z72(WA%uXH2h^fsP)TR}wR{Q*$U2(SasU-og9H;M+Db2tW%r06)(P)%<7GgnaKRC~M!qMC6%Op&y>2_?c%TFj z2~r9!>FNU+nxECsP_z9KVH9_R&`*PslO+}9?niju$M1>NZ=XxAo)-U~n*Z$_Ds|EJ zUr6$Orv2vx81gf<4X(`lICzjk`_miag!`38mfiU|cB0*?{!#f9a+NT$?fRqA(*2!Q z^YzL`_jRm*clXJ3i@=4I7eK|kty$yD^SjMUM4$dGy!OvJ#!!3gm@Z-!ggpH4nmpoQ zQLZ(QnpEJ41%QvXv=`qoViqi&L`Eq%y6V4S&E5r{bWB68vt?Vyt~d4Cy?LU0jv$la+d)1EK34fV4Fm;ay=h8x*wU^iu)@WDQr)cL*{ZM8Edc>%+kB zNbq<%Foo=G)DP^~4p#?lojZJ--N1 zo4>wyb?{-!NT29a86dbZHxu|^){;d-8Wnx71&DqTw3twg6gt#T2~Ra*ldCm@=c_*H z(MXuL(PeS~2MTD}@7v?^fe$pC-@Y%0ZMIQYypTGkRA%;Kq}axFWZHE$z+Q@nOpajk znjyF}Byz#PaDa_y#m_)s;GPc?k4$~JG^x094%UGl-zn&Fj(t%g6`V&7X87iD#5u)n z{$Msgo0S5MIR>!pUf3*vYj<(FRnL9&K-h&iMGQJPtO<$DeOttMhHu+-%>HiFMe_mB zo(F<56+^3id{KGIbSZKxeXbJ2$^aP1GHTUC{O?lK^hE&C$WSEPByUbe^O44JuRAhE z4&H&26h?Y6Rg8>qCnNrBptGJE*ddk3K%7r<+=mW-qomyA7$i@Co5+_Z-dT{LNz9;g z#S;Sb9a>GUV0iXgIq1yg6K0by3_NeTA}^6Coqr0b(*#n5ZUZ>Q(g9b!p0)#Q^8%>u zo|Ua{d-u_5Bt+#^Bcdvgs*PFR!FHgbr#qL(AeTY>%4A3B<%5*n zL2H%wSg;uPp;VMp+qi5F+Y)x+Euq&!d)Bd_q!E0sU@b9?R{g4E@yU~xP=5#qohKL7G4h96jA6v&;9 z0!2Mo!7)>4yCz`jxiH|QhzxlWiAG`QUS%<4xrDe-xqkzfUqozptb!ntXB~N34N~QZ z#NPy*e8qO3y<(uJKk?%beocR!86lK3gcD&1n<(_d69f*;;)vhq!f4{b#LU2-jn;`_ zo9_E(t;=BT_}&b}QrGa&G~qIy!Tc^1R! za7IcmNKK;v3^rnfz`A&HI>Za5zCvT(_QS5EKR)K@f3AP%L|y!U_`FEiuW-O#+Pv&@ zE<#?~Y%{DdLz1o9OuO%vZPh)IXhR_^~}(*z6G2*A#9K;TH^jugaFW8J3`4J9Mv-467c+ zR1M*A!TT6GjaX)Vk0Qyeadd=Ke3+QrMJm6HET3UyZSiq6P~CDo^ke}8=E7z}bXY80 zAIFUc>n6banKi3Ry*L5^{s)mp>}d7u=r*AK7pP9%L{F6c7r#cAaz)d5&*E_Z;@63r zYO{Wy&g)ZTJr|G9>sDz0U)Y*TuPwx_fq0+aXi92tABo-}sDCNwb0FZw-P;9A4WHd> zJ`7fh@gX{M-`=-U6`pE)7axuw1 zJdUzlzFV8gD~sbT&4v4tykwy@*TW0f>|^OB`kOrhgB5IX+G;|v+b{9*6YBGQA~CNV z|C}kQZZ<}q*l0##!1=4WA}b}f>WhE=6k|lGTPc+*-i4sDTL=WG_@k%Bb`_AkWIDm%04}+cAG!_4w>{4L zL~|egym~0ha|6JWv{Xvb%?PRC*jN}BpY6SN_ix#kh0r9cua{#8DgclK{MSyM>w1>6RN(VF_kr2%dEXw!$Wor{7Bz<1WtP9ktY)sPbI* zsf0U2kT#RPKi2$MHH{%$kL~w3PIr=hWWN@5Vg@;II8C1^kcwSk@TMqO9e(-D9BiK8 zQVuvlm$~C>G;tk!v~^dKAI_kFvNMmVwoVeEPdY;0d5lKJD?O&;nr%}2FN{I{1n7QW zP)+GhkX1ryFi}cd7q%V=9Zgj?(dCKc_qd`jPB(cXVW^9hACG~iYAh@|@cbhyNgV?+ zP4Sa%43uT|&jM{u$=Pp+uRHbJc(X znB$B|oaPpYuZh3lPuZ_s&0m^Xav^g+Q4D!#X67Q)(R%#^{_F*`J%hh~BeY{GM&zN~ z{yA;-Y{#*VHxk>E$zk+cfQ(zd%tKbF50xp70RQf?xo?JkJ;}BWWB>?l3_eo~sa0S~ zi9d^@-B5tf6^Yi&CxR4+pvNYNf&A4BI{)GqR(PqHAKw3+FUzqf5vO9Q-|w=2&@K`* zZ_q35Lw8S=B^=txzJ~GX9C%6t@!?VS@9CoK!3$|1di&CzP28zbIeLL!Hc&(QBpLZa zx6s>_u)A5)pY_q_C1XK^QYCUwLC3%qlozf2MOl0FpU!X7ENg=GdnLivgGz#UGJtI&;dJYnQ-`}k`e6t|a z%PlqZOk*-2JysH_2=HTg_O05YpF%u-0SI&-7TL=AKWx2IkZjSqty{Kj+qP}bvTeI& z*|u%lw(Y7}wr$^9EA~F;KAe{x8Ik#riqZRM?Q89yGy@6uBiL>U34Iu?v0*tJyWj82 zY_Y+bO#loz7PjKR6N5V>JIldg&xnT%>?b#9N2bD+N20NhuX3I?9E@@dDi(Jv5>DWk3+CST~XEvL1Vc3v7{;;3M%l$_WFXK`G zigLk+Rm&m^e+?2Y;8>Ce(2WkLX?~?59m@D57YkbjfqL1}`cS?SW{z=dBb~DE&V6NCS>R z;)He#FobDNi0igSKzA7^Q>3Ubt4x~wbuAu<$AS-;QlLKJGNHTLY6LTP#Jn>iRpVOfFFI4_>mSRl&LVa%`3$lm1W1e^cZ;NhpT@GO1H zaOOC=GF|>e8CNmUAx%s{>B$d;%(Qg<)cpL;a=$b;@tU_jlXiQ|OT)~5`4#8lA!?Uh zs~`U=ebz0IU$g1}$Knw?*XVD4DA4q6>{dEG+W~VN-N*UP&h5l*ei zH36=pnuT@4%;Hizq52++`oeWFs?vxn(bB+aeS6!AlE5vrt%RIBW-3284u(iuOe+;< zlQW_hmB-Ca(kM`C=L27KNRV|fI0!~GNU*JUxB*xvkJZRXv6|zj1o~9Do3i?^A1A7p zB`qjPMWrg(=FL}h=N-?Sw#w~rbI#>!7n?a&Ry%DL8$^m@+4ssnL;EN=TBd6N?S=QE z{hBdtaY$MbMAU%X(1rR;>hT$XcV6+wWu5)uLrd4O;r$ zCt0}}@TJbld#;i#(VUyM$R{9(Caf7KYLF-hJ_eR}xAwbO5f#Y{ZLq23<*4nL9;W;1 z{3o*W@#@Cvhpw3cWRTx?ee<58;Sx}%-#pd?qV~RJhif3@=+%WhO;Y87}YeQp~ABUJLLyD0+KK((#W_Z5*U_q;W&x zTqvOuZrNUn%#%D$2jz`g*BZGv_vDArH8461C5QQPv)D)%=x;p_5f3w3w$vF=K@$0> za!RB`L=p^(ms1A%Ph27ztY)8a2xpFr8IQasTrmMq1s!f|7L`S@1Lf>f;R6kKa$y}E zj>G)2!!?}F;#MRIpV#l_iH0t8NOt=;@bd#RQOE;Hy(V4F-3QM;(VT&RNPY!nyV20V za`tqmml!e;_(YPd7b2Oz-vW}KCu=^ZFDfV?0aFR~Kd*)PFaKM=W^F~FyBZow|yn&>J(`E zCq#SPvTZHT#W04Yu3|;y;NPu+&;5rRRoNL;9@SLR8 zsKFSf20T}>9n4|MN=_j|YcrEB%BifXk5n-$o^Ys~$j$Ivzf47WOR{+W_QHk}ilr)+ z@>wO7y+4$F6*8hBXIv*n9xI%r_3vb_Tn?W+oRW#~Yj|pSl zVW4i*z8)0;aQ4E^i$}N4(><-byH+%oZ&PA`ymbnUfbLyOE)f0N-Mfv&;t^7Sv0P4K zwIiCC?DbsqC5FUx{8_^Ft0vmk0OxEa2005ASo`LtZ$r57q9=JSpmBoZQps=sZYhxf z8O-l9gB+=lpKKb)@8MH4@>k#S1}~O$2EwL!(j+!A_Ut&YmnBF?MlIK_J_m1;BRlV- zvlpF_=O3e;d+GnMqQA8EYP%=zJGWi$$Bfs{UGK{`eAVC6@KJi5?|Z#ZHGJNWp~Tct z@f|NipLrbEZTHLQ7hji)i`=)V+;@4^Z;*I8d_t2oSc(}Rd(eq)y(`b{WGngT^TLw5 z;QTW@jpv^EGU2k3D`?l|KdyKF-cX_P~TitTKx0UY>mndV|dL<#5bp z#MO?NMqr1$R;|EPMRA|9t_j zkDInD$^ZI=Pai}|bHCQ%^Xpc+Y2`3EsJG!j5-cIDYjD#V=9g4EjRc2fE)-XV9hF|NNHc+g3wnDIIlbPrGg7dfv!+cz4 zn$7d(-eB3Y4qHGgz<>Z;rdYB+Jrfs5~Iw)uDPIE^T8& zLlqdQDLO<4*9BNyzUnzKvJB9O`TBKe>gpc{_kRm-Ga2Q8pLThbo7=2#T4zb3y5LMU ztc@=7>jE+)p^a=FX;+E=jMUYIB(uMTO$VZee!$9+i3&T&wAP<=b(1N!e zkP;muyz$||)WUYDjz#njv_xXR`B@+cK0}@>N-m8G$cU=w*tnK3I(w?_3}llH9OGy9 zI0Vc=*eTMf(hGh^Gbp(4KojubXa(*T8Gv!T!fd|X4N5Xd)uvn&B0|Bjj-j^POu^Tm z5BKTo)PvaUON+96UyKeNJf-a+^6y-2N^NL}zpu4j={1a9E6w)CYKd7-OvC!?Zr+oti)tOtwU0 z2Xm*T06yR(0M+lt;O@;nu7)=NNW*MZd7SDoxgZU_T~8vc4dVV@13#l&M(2o{sWLuYzt*pt(q%+0 z7Erq9IUFyN#;eJr9e)c&2${s&4{8EOEfz zlrRR_n$Ai-WokvvcqSoZBXr1{&lRL$1ajT-zM!{x*xJJ4Ht;1JJ0H-i2?mPr;kIAs$?(MHZwit?eosU4$VT2$3kmQ-L5p!0uX|V9Rto z=F?2;*(v^~ksNf~X@^=83*c@Z;)AY7M*`39wLK{X2B49IN}S72#f|XO8Q^j04=FBy zMg{aVxP9OnO+L2I<{N64lG(wJ@4K7N4dBp^UdLl_@@Q|wPx-GY(6*ZTJq5nwebUYU zRp=J5`-uN~#*SCbe|LCvW5W`2WktT>7Ak1}zUuRtnd;M6b+=rHGwgqcWN^mb&*}tG z15Tz}hE||RHA*|kK_af4u)MqQR)A) zHvVVLyb*{45@2f&qyY7^{thxF;;$$|fp|x*tFsmgF^xFHIBU=_m-dJgJJQT30!KiI zNj~lk_x*l;Q}#}wtxvT5BQA9IZ(5LCb+yUaGUq%)4b59#T&(ocqnO3m`QH%m6;uCz z?SXB!Dc0WPrhnynU!ecT3<$Q`pDaSWSWu0WEhVkgg>tr7-!uTy(NxtyJzZGnC>BUP zmf~B$_a^GJmBceA=b3BXyw>-BYdKD&Xn##T$Pq|DbtH%$9q^^68eeotEy0A$^Xr0L zwcBa)a1z`FIjcjW|N5s^)4#|2z%*YEZZk2g z@d|jD;*_!&3GuCM@mJZSv>hb9Z9(nH_;_C`86_fWFlshqvORDk{t|EqAE~f9)Os|u zOVVuQoT@O9c0HUdTB>%;1CDy?j(ht&b($(|^4hgz@E{z1fq75VS4^T7D;N4F z>%0FP`PcXthp52(_7}qVG9$|^6T${$#$2RodpZV)`e`WLx!4Oeo@-&t$idlpMoE7YO$k zyfBDl8=Ih!e?1g8)p{_3*U%etVGb}K->}%PNI+(}9_>)!B<%0}4;3bGC5RG6MpJ`E z{>G+jiBzFcB5Mko$`rNt8v%|sI%TCap+5_^5SycHRarV-q;AI&ttyn90STw@77}}* zBX?ytxCKP`3Yu{g2rFGpNy$tX_$2pKjOBVEBda-+`-pu93tHV6d5w1RbPe#n(J{dQ_t zwXDE6)}d+EUZYtJTDwAE&}OlAn)EC&!^qPA^REp2+7sE>KlMcBdER3^?j8c%NS_D~ zVwW)-;LnR`0WyZ0i%rtgnL`^ITS|#V|AatI%h_gBf3Sc& z=at=r%c_ZsAQ=`chB=^Tr0~?LX@j_i;%!;zN; z_zHqRr8E*+|LTbbUm6t#lF%riNvd)|HbpULe6V_{55LVUUKBr1+6GlLHFm-=T_d_a z2ZNg7?Y1)w_IUkZ9F2I>9HJCAbVvL-%w*fYpvCK0XRQAMxi%Pf6W1{xY2Y_Qahw-2 zevR1XHB*ke2+2y3QHyW%bUKA;L=wQnO;2d9>S{F*W#Eq`cl>xXk*iBXe8xqFcxaT$ z$4q4q*e+s~*yuA-bb@8A8!rWLsu`AjV^`y($*GlgQ+ojeRtC+DNxY}0;IEZ+dxIJW zUG@^NY?2*CZXiPxkNa}g{^~jrdWegBX(M-ZlS*ngh0b5@7dkVs{p;J+I68?AwU3;~8)Du8Kd9`Etv(dDO42aer{M+sNCC%1Ph zG_uA3_e&H7VjL!+F601CjRatu6M;8%nFaM@;+*DW=(%Yo+Dk`* zeq$7{TWwBkLi8Oq2Mz6+yOmb|6jHC_bWGn zejDNj)4%VROv_k^e*kbyfAzH-TZT70jSgh%gD!qZS zi}#vx0*9$@u^)9}vghVi#&rk?DP zO@7&(w(H<@FrmP0&0Xk$OFOiJz@M-dPYJATFjuO1=;3FG6WPW^-@@f1PVmZf?G2c0E{egTH2$6H85(#5JXB%LGZ-mzBv?fD}y(u`$}LHIXL{@l18c;m3)i1 zy&xI0=~)?LZHVZrj&q5KA{P-sXjk)@h!r3jG{d3ND!tw|b9M~JYW#jboQq~I`NsaN zCtU(yO;7GG`QOn6ayRL(ES z^N1gtuRjN#Oq*}?Dg6aG;5005o<(aOO?iL-R_BhUc0EO2s``&0t!JR7d38Gvk(Tx7 zAX?S|OaoX;T9a#$JYB^EBoU{ADe39>;#n4>)4iPJUBM9yrre24wN>PWGaUKu2abQ| zP|B9^7XvK!@1Nr<7|X3_Xx$%B!~`uqN#sepuW0u(GT7afQCZi0km~l-Ck~eOZN@}v zCmVXYAVSut1k;M?#v6mxOp^H72C;Oxe!Og%%CdwF>*KnKELx|!)Dd7S4j8vKxq3|o z@86*Saa#ieP)b^IRXQcGd&d1#v&Sq(}hvJgIfu0 zK2y>(xG0VCX`5}_DE;GEOg+d`q23#pqd@h7Z6n|Bv2BAE976}G`QL+d=ZRY93)AH` zx&g8#lTc{GQx<|Y`LbiP^Y2|kBo`}C3JJ9?`R>?~k_Bi5ws5hm%0H1}RGJ4eMGMem zRRy_ffn2e^HSP#i@HH*zXK?jp;sf`x~7uO zL!$i36XjAWT!O{OG#hA&XCTRP_HeV{wy~W$haMlwH@dV~{?7>c)6}ayGaVKL?k)_0`|G>|QKB&p;kRty|*4)erlPS!^$H0&&F{y_L(i&+GxfzC>p#_?kl) zoRXn(7JZYPk{dWo)B)*5C)H$dxnimR@Vk2I{whSRzAm`;KR;bU;nZ?=WmW*If1 zJYxDQ#YJro(}>X6UVtsHFg8X~>M^^luxSe|SOR`0ffx$ElY+jRUyiyk!O(uPd#pzF z)i%w9l(;b&U9*~wg!O*h_W8p3MCn`6fL8>d@KTy0)aW`%sA%(iz}F9p6E?HM|FN>0 zVauvc(E#Js^m!XMUp{erW5ulMf|;zv%oN0t?!Whnr0I@qI)`c*Nio^@bxi3~ZLTbK z-jT!#ZspZH9=Fg}O-B*Ww!R zptaw+JXENC6~*aLKqbUzz+@1#Q8;}Ts3lM{MBdsnKQ$>z;GoH~UZy9D| zP>*jjM#q}|^)T^vNxx#*QhATwA{1cWDB43nVcvlM2M>Scrhw8%U<~(kr07@=<#}Q? zngX0f_6yw`h?RljR(Qn`gJj;+8?rWfk-&>^87JR!WX`JzJi(TWRr50ucTGcLiC7K*lG} zXeI&)+!2#dD;v0MXt2yq!8LT$VLJe{k8&9hL0idQu7~;u*a=>CZ(2O77awFriSCuh z*c$5ZooM+Gof};1=9{BvKB=8!0tmC4PL~7k>>OGvhwfn3XP;KWy6uC#h?<_R zNqasDBkEG*b(;FB>E z^{CBYiPU9uk47^wkmIg^$vnaY*gDOTVcLUVU&r~&j9bBs>zU+*xVNm;QELfWE33#R34sT}F=g=aNX5|NFtR*!Jyn4m zf7EhTe-(rwh7+&R=<`o)5w}@s4Hpi{vD?&)6?K~~x=?kJG4=8mk8%t%eZ%^17NDUf zNDU#eN`lh16Ve;aV}o|rle8TZXSg=}$xiSeVnFsjrFO~{34Q`vvj@izOvM}E{zjx+22$u=a@I`f*Frp-L&Q~cLFBmRZj6- z{7hh4GXEmL{4H6CwKhl82t$%Xq3V$+HdvQ^sDTVlYAq6E$Aa(>f0-=piO`ZzIQ0q- zYP%y;WDt2Qr*q9+r{eOR(*RxIb!26&^amT}lSu|W3i)k^(O_een4;VH8S-hRhjAR< zZj{HM&mXlK8%J|2qNdBsEb=|ch>9b^&!~S@O-b}T>)4ah&#y=3!yuTed_J_K$Rc)A zbO6Xx`G%fcwOk*&AUW%C-Zb0}arB%E6*`Rqt_FCY^z&4c$p!-be4#Ld?YD}l^h6=) z^n(m;2N~A_{yHK4Zc2+Ejs&8@bTpQz(fPVn>DKEy3d@0uDXE0`yI{BAL zv9u0pcszVpG2w7C$IkODvb5=)TM3wXL~8A^$I$ z@)Jm)v^|^>lYM#<+`F#obab#)34UjD%HXx-@|HcT-iq*rllZb~%lzf#orYq-E?q%? z&v+esbc~BDM9@_q0chRSbIjFAU@C>lupz3W+uRX>!Zjj?I}y?qI>;FOo4D}Wqy#8x z!AyzpGX-9>F&{8Whe-zwqmwdjUWT4PLqF|Ts2>q~tp^Svd zs^0rFi~5djS05);7g7=L=C&E(-7p9Mn)-h^E1Q2?JWA^b*7t$hK*#Va!0ca@{?{RR z!=UoFLD;eec5d`8H1pWny-*!k-98yDp)SqX?#n2-F$5laIo=E^T0Gn8vy(@}>4^#r zBY7n*m*NE$7JWGX>&yfonTn2^pKKbGn(YqGvXeujonrP_?_&)!Kj$#pFz`6ibZU-L z)};hE@D|`ONK!1&Zr5)?7T+s$i()a@GrXyY|O0O8adYh*c33zp~TU1GLxQJ zzNo!pRkY|ARRS47RYb7Ox!wSOZ$>AAEPXK=k;vlYYn4zE&zzilo>lwW2wh}Sx16OI zpS(yFCkN{+4{*r1WEte*jHk#Yw-Np!#5dev1A#)2IgkJ^vM+j-dnx=VW8^@V`whzq z0WeC~<&z~g?J}DuZUYfNe0k=0NQTA7Cq_fOl_7+T-0`u#QwJRB!|lkDF?@UzGgx(Q z=rpY=i+ssbCCTz@&wPQpZ$xiBy&zx@iD6d^!5x-l0W9@BAsI7o-mvd$%)Bt4V`z$O zZ*b#g&hia0`nt{`=5PCo=-4me7M3$;P2-B)pMJOYY_J)EBOTu|H$%eAk!0mP=^!?8 z#^~PTCi&I+6$SlOvgI-;Y@enO_x-tI(-m+VNCMn3Zw$X6$1EYxe$9BEqoC;G9&!c| zJ}Qg#6g}*%EDF^FHEJK?X8ix&e0F#`-YMP(j9UZ)xOFBf(2lHm#vs^ zQNp=U{H~(_^<=aD%OVg^I*ZXEg%m_|8Er4xHe6e@N^q&gE=!O~7$qVn#<6HUC$nL* zYv)GTxTpa~!8t;oaA$w_VU4%1Lln+TXE;yGgyIXYNQl(0CLY0Ql}O_a=62QAty4l3|j5Xnw5U0 z5-9ex2{^kz29RL!c=^Q-3CD5dmd}~V(XWAF=-(z1$(#BZOC#pssC`AKqDR26(B-!z z^?I`I_|ajkOB{s5^}})IU`O2RAIBZ&Yb_x;X6YCtKG;najf2@{kuIAiTH;&Go2FA( z$O*^1QPCo^^CYXHF$39o#&#@v$((4c^U0==HB|2k!AqfXwop%b>tY3WlxkTw`CU4T zIv^=xG%zj5=m)69(v`6qE*FQPr%Bf_w0tAUKY5-xWa1M~%cubNmx-pGz4;ppdI#2;tfogb;?9iy$PRhrD`&Zs-Pj2du!6 z2`X*P(!^21HvEx~q?Fe2!$L#O3i$0V4GWs7Tn0N2$fnT)}bO*L>bbfp7I z2qJ2u6ELn5mRI3=yE0e+%p(Oupybky{<0Rci|KjQT80b8>Dr+_tro`MEr{Av!>i|P zfa%ODj6=%^$+xwhI+Xkre7)P8p7u?*L=4L%7gV|0u02N{XX4JOXC5COY-BGzRw7Gc z1oQ>z%xi(v)STy~Qq8PC<@mgOklkH6u-6S{6h8ww>TG43)(P!j3Y@u~K;xVkMYzG1 zqW=>qbf9Da2t>6UW2exe*kW32uLP#~GWb3K{;q~_Se%JGaE5+dk=z?#;vF_Dl{Y3j z5oN|`a0`b_Lg)SPfoMemX0&OgjkT@K4TZ2g2GwP?;P{Xu$CLq6?E$LYf6|_QntBF4bC%s)Q;BhHgq@)J_(ABPqObCQ$#lClA}|z|Bz)Go-5p-=aQFl8numd{ zZON*M&Rgn4QR(J8MJJqPo+Iw@Dk{<&%bRnKr+u0o%&z6_!1<-buQp&{k;B?V>%TrE z;9(1?V&U(wpfDKCGNOPFGYX=3{z5R@8WFj*bPRozsuGZ@Seugq4F0&~XdqbDiC23* z1%{0SLNhY{!U$Hp5OJsW0#Dl}$Z4@{8blm!RHKyIyf;tQT0>4obsYIA9rdamB_Ll{8T)Y(! zvpO(8ph=Y5HrdjgO(dhlz1R|ttDdR&49wpWE`&Wsp{EHsn$JsNbwL6|yqN;1yIS+pI zzmw#kZ@!T6^OpH?6epnSI_(W!>&7!t(^RF9jaJYm>O$~ZQovRCR0;KfTfMU^oP^VH za83llWd8;k!U?rQ6B?$unBOt;+1Wpk9@2@|<)aIYA7u>-N%9DD?ZVHed)nFOb$hx< z20tX(a5R3z2}M!JUmg|4&n#T7e+wxZgeuf6{(+h`I zkE?hTZd!kV#zi$&KUY1VJ8q1sp5+&6v7^CRnfhRJvM8lOz$~C5Ck%i02{i`i(~tB$ zCPKqB6f&Y2#RYfcpU+%rXZsKfXL>`t363McN$VX8^tjqoWSX?|N-Svl-9++uk-?%bMq zA-V1GPHUFZb&EfnY%NfZ8)>;uo2U?>6+FGLc&7V2l%be84EebTH z#03k)BrHiBJ`taNU$*z*A2A?top`2=@VFUcU!8aZD>Dlbx8>8RmChh)5XJPY_yZPG zB-|&?p2iEJxW5?kt(g0P`3X%MuXN-Z(Hht3GU-|Nh&muaDcY!csxxtSb!BfTuSo_4 zEHos*Y`jGCX?q86MDwc0552G4D@js@irRpzGQ%T4?G+4QSR^-;7!@*}CXez=J7s8w zcudAagCmjz8+!Ds&Keq@9{xU>H9X0?D@FRM*`Dn;dfcEVRq!XTlbzS%kxO$nJe79L zADLaqyW~!47Y~zN&e=wr-V%1tEgXjCFk^g-VQ9e5_hE46fR0~uy$|C5eF2yF{;_*I z|DWhab62f6|J#4uWbEE&<|hKqx76*A8u~4t&+$p0LI-b-I?o(T67rvavAz!rb}6jg zY!C_#hK_e%GH1nh$v1!B9ykf!yyf| zgv=k-6P_r?=HSJVLxiTXT0afQHv}fCx}&6X97gzo%FLc{evlqMoiP_gc87D9qD53O zCg4=2a+J?+({=L(XYdrBD~u8Fj2>r>UW@8)NGue7M}N8EM!|OM_A(2g~mlkxLv_8ej7U? zHO$g#XO3WrGVyV>cK7m)oU%#MiIQNAwis65+L~?7IDG!t$SUwl(PBVLiM_k{P)~T7 zFR6K_VeBU*=@AF!*W+gzrJ;QMVgxXqbpRY762n2QI440Wul>|S#I!elRouZq7@=e4 zL0a{w>k;*RBO64S-a*u@tC@2Ar}$O?x-1ki`6})PL8DIDck}gmy($tO!)$Tu!!)~tF;Tr;;fopkB=CG zc$Z&$)_x=i%zo(*2XU(W9{PJ0`~o#*6ZKQmeCgvl7dd+ch%HYDy37K%SW7%lv|dq` z3_ZUoTe4veu^QkQlm{!KVRPeAa@MxZys%m0_befe@>@<8eAio;5ny}SyhhI!k6CxN zvSwtNLjh)7HxYXu(jKzwIZ zKON?@ByFO&Ull%fxesr@ziJ);)O{6<_qeNh9L91HvZ|8ks~M4Tod^e(%ntrwEAM9U z=23F1HwNbBmpCJExPcr=FyI~%7@~+sL%6lj7uF}i)6l6}ogV<(u3vs3PXVEv0d9`J zP^d4B3>8u*?h_kd6NW5-velQpX7`U5$eC83VG6JJ7!OL!870Qp!IfOb8DF+8y$Q}l z?2W@239XQfG{dH&u()fv{Egl##1suL)WnfWs^O3zbWKZ+FPbLnW{{xOs3gJ+YaEyJy}~eCOl5Yh{^N< zjqKfq8Ilm#R3SmORii+T8BuMP;nB?#DJ$BQ-n9qoWF6R2&sYxZ!xoFc2cUQ&UeAKZ zK18y^G?7RX=7PX3P-h{jCM|F+C<5xElTsy!nH0BgPEgN6(qt&CPy1eOuEh;%)7_8K z>3NRG**a69Mm%8-ae>kg0Lz6F^hc`J*Q%w?(H{Q(1o9=kb<3vPs_6w;!h77h$NSJ} z-fDm*Yarl@UkDa-T?&t6HAu|FQgp)`dy%DW>Y64C|7M8&TcKv0_hG{I_lB z&TPvhjVFiPdRG$1MIWG-ZZbHB*_e1v95;k{Hes?ShQc_ z$>yx%zK-bq+qK5sNM!(>z|DX%Era4-m_6~Y^3I-c{5N*AW|mGxa#hGtWK*d9p$#&O zj2xuc06=5uxSe~8sSx{wsD+qC{W7G9w#8#u!07&7&CldgpPRFuK`vdd>=T;0m1{vHq9 z(NEH0o>`OtIO}suF_H$Qfx}K)0;j|-DHsJBvYU;>G8p7VTEi+BWI(z`Rf6`fxHbw; z_)!TpW2tNJ8G{RD<({E;Df&S!j*%?q5JXmDa|#TkWw5aXJN6Ii!Bum3tZX!lD*Rpl zSWnhWDn+Wu0^@BOD(l)4&e~g%Q|%)ucPx!A?*$+s`EOM>D?2(1F+*BkdvJ9>MR6J2 zq=G;m`-UA?1=^l{tq9uO~%L3x;Gml2!JH9F2Q;7{^DkhBKnj(KaA zf})(cj{b_WdYq)~N(fwHme8q5*;7*81sW`?a#{hd{<)13FV*XNp!KX-Xe#==;<~<| ze5s%p&}Gy$Eo~kDRDyC)5O3isHegrjV6P!4js?oXNBK$MaO5ef}Shu^->F z`m1E}?ZF?F+N4_F$9}h+w-Nqt%|v5O-^=^LUWdox8_(aK>Cvxfc`f?bot|Fg-)FAm zzfXJbP`actHn7{IvaG)-)15Ey8}SG>Zsi&AZN(E1+*t?7#{Yz{2y^l1uk%(y+uE=m z_`voC+Du|7(X(&dj%*oah#5#gqk6@;0C{%FdkXw^3x>1i%61Q_*D_V8VJ<_@mc0V>`t9$IL;zB^+~y>4 zAFHGW9GSi63Q(m~a2qLZ&>4tMeGrJW_4qs!WLW>)qa2wq_>p$O56F(q*i_WI1xKS~ z=}0K}x6`dr)mG@zHH{h&EVGM|z;GOnKG&;~C7>fW=nMpR_KZr%1Bd`Sxj?NbxVl*) zHDdg4UBrpw7)TuqTX3m8qAJ5S)0oITv8^06%N31HV9_XR$x;G_$**}x+@YzR1yYqu zY|s!jTOdhoAqw)bZ9ELStg2nJw0pIRtZTV9azJW~y-yLeIGLM}+G_}N2X6!b7SqDM8gw&J!9~(mHX3!L5JFp-5^PeS5*S??f zL03={&W(E~1bC|u9pW6-w-Talse{OI3reW(y_ktuN*!tc!1ki$^@k&cwb!?>js3qv zyBOYEyjY1+{1ypFR*$-BrA@f=)40Qqg;Mu^}@ zIO~Zn&A>xG}ukt4ACs{MfxzRa^{2kT#5+(uLH;M(E(ATP2sf`_})m_;F_mFS%)>yygox zNZHen_ncxi-oglv7#=#NYVg0^TvIZ+$c)!^354)8oLGULF(NM(y;3CKrM6actok+h zt7yhzdPxvn;*#qWAznQiG0sk6T^u5?ymSVh0nF#qFOoWkDw)R;daP4kF2y|*qRU!-R| zbDAXPK3JK(u4QHGH)I?qG6W?hWg9Nj@C z$BN^GalI$OdcCBKs2-fYtZWu~Ewo<7S$(vRiJWhJ$7J&gmL-M>qow0=x! z)PTom^D)c+Y?1Zm(erUE8bUm=t*U{>*g-`c=+8jo<()$Nswr?8(ubL0JTpD^@RsEx zgxrV_fL$Ttu+Y$`_UzL>2;Q$_Di<~LXviaGvfmdM-;+P+l*r)?zO#$SA|E>505PLJ zG1G{`U++6H+sL9s>DP*oV{&_uG_>24n5;*fV8ld?K{(sqCACR=Dzd#_74vsRTP{t5 z!*`_Rgc6M*gi}E+9_>$2aLH(7c+=Xx3Z;pdrggc8lQk{Zf}!|V2i!tJW{Q`kN7+Gk z264OAosMD_sImGiYo8(cv<{|qZ`X=7o8f+R3reurp|W-~Fv3oCH)Twv-vz&bT1FgH zG#)hPTnfin#g>17a&2UG91zMozIsNy@b*=(Wp z6h<1pZJam0>vx&aiNS6duJ^yd6QD|T*Nmbn1O3_e)~4Vu0$XheN7Nq2h^>E*24ssk zOJ`_h$&ipqr~7tvtjQVU2OATO6&u}0cQA-BHv1OyG?oe9qS1J9brB_k#WG3;%c)AM zh=TF1o1(PwpsMI_m3L2bL4kieGkICXe}ota_K%%WuOC&LqXqlavCm@U(@@`z4fV(Y zocc^%*h&wkuPDx*(OuI3>=v_VZ_q_`nRaZFNhAn1$`6>9Um|PEkqG&fqXa^POM}57 zH%EffHYbqHzQ=7!7jy3lgvG~TYub4WW94AYg0zuwe0W|#_|G-kRy}{*ej@Qh-hE^F zpKogaZkXi??c9&v;V)a=)r!Z~bbrjS`@Haf+_r+dbl|yk$n$u-J-#oqgIj%A*~J7h z>F_`9g46Tr9!t6P&30TsBi+#B8{~k8s_pu8!NblwrtWEKcwE-M!OysnkZ)N}}s+ zOan6hL2apk+GvOLq}D3JR5j~)qyFLAUjJTK(}kXh;d{4w>1m2+ZMnhrzI;lg&qJ^6 z#k98JzaQ3Xf2?nSx^>>jh$0i-_mLeGJto*(fs6NzxGjY(IAFH|tFI5=_cdQH@e0Ys zYabJCkwgJ#~?JygprdiRODcfAPvp#9HTfo3}JRF_`)TT1THnm*ny=T=}T? z+dEoIbnA$qUfSY;hs0|<&$jHMo&B!_E}OlxMI)dg1zO&!Z|mqRRTSMZ3A#pJaZHgc zQ6ORngee`QLMSGZ;!xuMNEtKM^BL#_Vdkad05XOA_EVywO|u}Y*zO5wrjfv8b)O|? z8R*EOKLLSVeMuzsGay-bSslR0Fa*p4nPV;xb-YW>A!{MHoRQKp2yqU-edE|{T_B@u zZSv}D9vxdu9it^yFje_~wxgi_V1b69mpZUke^|DO>0u^HR|+c#^B7et zGG|<9yc?ySC;g(>JzM{v8hMrqK^GTD!^&>1uC48CTC`-hgYHRLsc8d%itUK`sersk z)oqqY%bl?AZ-W_6yjL$1fH`G;#Jr&g=&G+7ab^vy0~LTys0vBh2WUqz1;v3y3Tu#+ zob53Hf6>KR1c1XC*p9z`+IE++d1fzLa?LL5@D=^4%1Gg=q@jRlOkfqFQa2Udo zVxA7W`E@5^U1seuC1zDtO>bNmJjv5%ji0&_WA;U5CE7B#)`MNDRwZ>i|A{E;Yq2%y z*9dZFclwqt;v4B2cknkac6_}2Dro3BHv5qExb25Ia*oMJ@6UFQQ2dNv@9fK;@9bt- zs~&a20Reub3w-!)&>x3Sv(7oa9w#jhexkCrOA>pR>#8q*VHPFbPQQ|~>>BE-aOpQS zc~+O0)nLbAHK~KNT9x0RAO2N@9D|{n-+q_ul2ht2NI};D|IshLXdGwyV~Hk>=s)%- zlReMdAjl(W>zay7SEtFWdQthp63w9)iKz%HQ2MU=U71BOPsB%)#n@2niBNlh@bM-? z6W)4$Y#wxpM_%wZ`N**b86(9;5kY6(FSo5gXH15!0|fe%Pi zidSV7O;T|k7i@DHy?&Cx)vxlBRzM7J#tvRrr+UY&YMirg;8Ilw%>B9VJJK+iI;k!a zi|_K*=Z}{j%69I*ZT8`(ivdE&hyb)>zrL z`cw%MgDUYHuvyc*CoOX!zjf^inTYwxpZ#|tcJ7la7kCL7{2A}KSCfL=zf(WFqn#mjtV*q&A+3CA z3)s}}@Y>N{g*)k1<}Bvt$i^_o0=I^NH9aH=9dZa2p94Lrbx1lKROynCUCe4(pPG=- zxF%O-X=|o{+&9IjPftmm87{~xuMIU@kc-<*Iba?b*e!$TIj>oo!)^qST$(Pe35dgt z<4Z*CF<&c*Yc5}33Fqv}Fc&Ka5mtVsAIzVr!RZy;m4t%Z2uQoFfndJIu@zz$u8;uw z;?=-0{`-j$;Q@CM*=?KzT?iR-*zhHPRgSc`exZ{q?rx4Si9 z`nz(4NknLCyX+i8??sLnDfbMq=C?cGV9N}$=k=$)+fS>Pt`=4Y_Wia-_v?h`S;p|7 zH-G=O%3+Z|;1zR7Z7o(hn2Ym3a=CNU7z@p31~_#*`E%E*n<$B5b^l^d8D;<@(Hh3C zJbm9eDX1?+EloGOk>qgk9ovO4W>_9=LSW}FwRNC0zY?GR#Xc3|{2Hciy?BcaQV;AB zZ6-V7PE7asJEoyy=wb}~v`F;~$?Q6Y+8#bVWDtgKCt2&=Ml=h-yb8&(qQ}t1Ana2r z5#n~GAn%C6Gkj^C7aA5;mK&D*f0_VY1iK^bduJ!!|P1~@#NK_`yx2e*)?FlnbN6uCC@X%-8qb>-6f)-&< zwxv{)MvJXn&;Tg6F79g$H$`ccZHbdg=4zvHS)w96*C1lBCF3WedJ9{%D78`x)&G=K zvzrQ(T1?SsR;2nS=$y8tvw7OID;z(cw40xu-Ht`obN$lqqgv*}s_!HJAuSUPF-ZB0 z2mYiyYXlnC%!ZhVC|X4q#x=nC8n#XNanlp-B%q9yYvc?EVT zcXVBj1ZA#^1O7Msf4aZ_EJ~@F`i#tf*@mV9T6h5~sbp1C%edT2E7|T`+%w1iTNl>r zC#%|0&{qWNM5s6bLEV8-R!XNufQ>{N(U1fii6o+dijJsyTm-5*wqz1QWpbVqCew-~ z`0?w>wi;8L)u_yzCHon?!@76VLqei1s4xs7C>Rqr8+BfRy-G+l(a2uJ6jAri;loI% zIy=%;?>Qtt=%V{Dr7;K*#KL!|8~gbNFeyE%3to~(z!C&Sv_>*qkepH8FtX=?G9nE- z9qvRUoqTER8Ka42*uDREOqb)@g11=0Gk3&QmZ-Qu8W_hdVLg~~_r>sZiO;)2CGzee z2FQ5-SK+lYFyUE`#ahg_rX8Qa@7kX}Fz@D)Ib@%jb|Ve}n?C>tE|7o|gyw zD+S?{bD-y|t?@2SBEB{&@sG_Q%Y(;1u12v6^UP|5MhjS3y}Z?-hGxx1-iK~h9wweP zZwFA((7D9W$c&wT9;lV6?_O#S(_4BvC29+x)wg^d(;YltD3|7fIfVyJS>!v^Hcs}b zo6&LW8&fXs2qJbwUL^TBD2ZoPWHNp6FMs1^#sJ`eK*(PKNw)A%knZfuI<<~lQ8`GR zwC;7HJY_-YhL;Qz45A%H+sB{SXcpiTh=`iC8N+N?O%j51qbs$hq{{zho-;k;Jvc$K z`;kpI&2eIy83L4@^zo`~^iCggKkF2lZ~>vfkhb=%y zj(4(0yTpNY!`5t{1^t`vcN^6*XiUPq(E3C|K^L+r4)=4Y&jYXExubr@`=y=KC$X3U zProb1D1lN)4j@1@BoN~oeEi&b@=EqA`zWJ8^}AWhG{)X-ow2={;zNM0h>~QuTU(i@ z@frkcuI8~3u@XQc%qnmq?Qov9q&9+5kyVBF2rW%nzJUZ2D0H;XTn{9xD zhQpo1LxD1YSTd({i!ak6$Su>y)pqN&Qem zKT{2)COdE*@NLea9#I)~)cmbtZTmk3E$qX43gD%q7%%mpQC2U2d)6cmtbO+MW;~AS zDruSRb&eXV47N`SfwIP_FdDsZlce@Au5hKiub*a;GMFL{D2kWsF^|3WI_tPpwzz}?iem79t30pKtX4kSd*Pfj zgKeoNNOM1I*?1K*CY7nuVFtSG%f|0UitaZ~O7Tw`hpbRA2ksH?1Mc0b( z2wAqyz1=)0DD45}L9bP}nbJYG3$>s~3Cq0n8iBV639YjHHl?X~Payhptg(ZmTpXQ= z8Ldrv7m*J4!T2=fxl8;}@-8&4Xe2@H{69v0@p-qbL1ZAfwjLn|!e1Vrj>IO^)>oxn zzh(T@kNq_KJP&AIdGUtkjX zGEXkgiBdj(IFBIGncJdW#*t}3f`z3aI(`e9!)Dhs!D2-+YBhK&qvy~`2}HVUdJy!! zznptQt25IENv0cI9Y{7#D+w=Xm7yBCqtCS3og^lAHiH`S=;5|5wvFh~wq&DOIM;~e z7VUo}!utG&XC0Bs8W8#VGluBsE5TukYY>$$U&_ve= zX7-Ozpr=qa9y(7tNMI&+D_3Q>AFScPAWVvBd5rToA}YXfHm6QyHx=VpdAK8RNgs;> zguG6U$tGRzN@GD{;MU`H6lxmhu`_{k4~}}JEBC+Rb;Y_^a@u7-gmbwi{{i~bhSD%? z*W9vZCKoA85Xz=~$?WY`Ts9G9a4*!$`mxbz^(-5ycb(;{X+Po|YnfpI>=pGaYpE0$ z5T-JU{}}-GG}zoT0E|Ue2t!YR-yu9Hrp&)Bi5S_-_@{PO81GM?Hv&mnAh^(U)rUGA;^ASX)kmvVsM@oq#D>RtWl9+XF!??HnmrGUJu2Eu7GraN2K&#t8lLCx z9bYu$uSx%(+JMve-n++QWtqqMy~#Gdj)#>OpZ(#4Q}Zurd~LfR>SS(tL?xDKW))mr zlg!=+)khX<+u0abSyy!w)q2~i=7ID371n7zMHO+~#mi+?)+N?jsHkHF1(iCWiiUy; z9p8mgItnUnKotcA7GF?>jog9)i+87TP3S18Qw6Py(B54cARgXdo01D{y2{(IU-i1b z0~(c5DFqVOxFDRV;`^G!t)hr+NpprG209447!BBpScNzv6uTf`Rd{${XXkOPcwd;3 z*B!)q9~6~1>JkAIN1dJUv{+UAK|K$T=$QTWb5nXsZeNUS1^Ml)a;lbYef!B>i;gYi=75%Ed^~|w)bV2 zv$e}o1K*qzwMX2<#V0u%k&&$uy*RtZ7qU&y=j#~zo>S^#=s(T&TbJT>e{Q2Jmtf8v zX{E>4;|8kC1i6p_{1#e|CZE2Lj-i^Ury&c!Vyd$QVhc%ek@<*`15zLjx1_8Dez(rx z4(atp*K1D-u!HNv6t@9>DAeKR<4G-MIRq?3vmF`_xh0G)N{PprM3R(mopK`KD}Z(hYLnPv6Acst zOj2)vNoF(ngTYY453$H(3*7(+4dN4E62_H#YIgXY#&Ua{eZw^>x{&&-mWRfS5eB2xo3#<|nRLcu zD2%*@-WD^JnW_mVsm(VZ5x!@GDQ`)ju6Mh>C0s1?&U>fkCqh}F=mM{69AODMr766j zUTb6BSQ4exrlPoo*dJ5rCin(VA!vcpXgt|-@ zWSwB@)ck3$r~O7m0QDqj;!4-K!R@D#9QY)NSM~0^>799QR^T?Ap_xQLPiUB+UPIe7 z%}H}DXG5FVZN=!3-i2AsB{=o_1$TRv6?Lp7o8>Q9$PxRR>$<{zuFF6Si2i4 z2W{Xu{fSs)dC3RI&vt23X}nSSd#*|bnvU-D!O;EJX)U*OI(a(`t|uA2$?!^mF(Wy1>Qg^y-H%(=qJc{tbPFj;vR*QBUiI`%mYc6JWZZ{^hO zQ8=M#$j%DRl|3#v4<5JS(tSn1&=ZIWuqm*Yr?w9{)#=NzDyTh*83BZ@^EFyigAcd* z#~5$e;}ztutEKROTX4FCi@fP7&!7RT>l8ai_yP$_+iKux=JH<0I|9l^U;9G48(ALQ zj0yuj9{p#aD5)`6!-boslUn@|_QHb6)ZW;~sv42=f_+Ra_;Nr! z1|w|8l`VVPY@y*4FF{9fpk8tW;xF=aLr&dipTsI-r*3rE$$*W6?I!Qjk!KcRMVch0 z)w1MVy-C2E?~oe!iZo=Th5v31V)g8N0IWyQ7cb0d+Qs)E z#j{}@Y|S50|J2q}BQpUhi4cxArB;0TPWhBBJe#AAq9ELF^jJ$1>0@Hj;pobeZ7CD7 z2{*>p)C(i1jjn-``)(GNZj6F}9%OC#nhphFzkDu{B&LkGWCm4jAVFn1O$lA>V zRA(`5N{g7**aIDawhDUqtbG$lx!mQQ4LGR$M3_3NG10tl&O;P>ss~I{dLZo*b37EQ8JY9tLvBJcO`lc$#Lt>a z>PypS@%I@GIsfwL$fED#d)wr*n-1Uc0jRnG=iCi$*0`#zWi3e%c;gs7S?8o)04HaS zg545PndNH#p7->>@70k~WwEIYPS%+=rhJcZ^j6Z6iNdZE{nX4-Do&W?-#?WDxMz7u zv?)5g((DQ-E7_%xsfUgQc@GOTji{+yptSFEE`B1#PuHkmNZ0Gl0C(@^}WRTwBO_UUNq-?K_@n#crI-2Cr*(l`?qAeTpOhR zy6&bn$@WG&vzV_ybv7C_cU#7Yl`U$Ef;L&zo;8i{L$6a=S2E56b&D+kuQx>opwWBJ z%`a>}iPlZyY>K$zDm~Q6qX%cW^U{3ku)jV=g^4i)MBTb^i_8$P&E=nZx?`|@e1tc5R~g>o>7$Zu|K*IJ615*`~@A-e0d#W3Levs+A8}7;Z--mVr+TKx@4X#QP1pDM z?x>dk8O@gWKYc^5*RuCLf5?BUg!k@0PJQ9OyU^bGI;a2kuAcoml&)Ve00<%k@?K>) z-8EZ%sqYbM#x;xUwropB_7twZ%G6youC#zqG4)~-D`#A|4?pV8;$_qsPzsv|aiL`! z>tp`#UIi0m==6OkJ7bZrMYcA1rYS!28fCxRoEE>XJj3n$8l1(Bv!4Dn8;zYaw?M%4 zwK%3(>;u;*D}sYu3vO5NgA^8)`#j$2KZ#d$y}eNB7A@Id&O}kCV!fG|Nag={CmsMGJtAT;*$lO#ZZ(0 z9+_=oi3Y)Q0cLX9gT~8H4rRiNm5E<7;}2kb!dZ-FlI|}COw*ke*k1#D>q?4iMyjf9IC>;{WRXxKAGt{F!O{IqkTazv>Icd!+$% z&3fjB4%Re0UOxX+s(i@F93K8unEeCy{+mdu{w+gWmWs5<^Vtq#ROy^mHK=?DI6Ma^ zo`{b^%#RZarx3Fu78|8lY@|b`0;7o$jq+O)VkZiW=D@ii;azC!ttV}7T7t|7j)eu3 zdp*gPtXwfIkn9luDW@gaHi=KgO5a&1>7sw{ay@fydNf$O4*U`fgi!G&&3iu?!MHvD2YJ7ohoIZB?q zRivMp0)vuCP_Yr;9!OtFIoB@aM)7>v?UgQ>o3(22s7HEfIg8pmt+ zxK>b2d(S(&SK5WOOB;XX-vAre-Upd4eOYa71FVPr!3Uc!dguL#jU%}YFcqkxl!O^tKU&j z84|sIB-_W3fe@efk_D(*eE2QOy7<&*Fc=nixkWf9nM~fP*BORJ7INh8wO}kzS8l0% zS^*p&P=pTAs#si3tV#Z&Fa9yS5U>N$52qDh8uEhvNZt_n5a3zDnWEW661pHHFch&O zHcJrWrAf@SwbOULS71=MujxWB8#G#ux71teX+yAO zasNSaRYR&|@<@-UTYvQH-90_l~A?k&#~7YyCFrjcI@vQ&a z)`CN~IWzw3=AE#Jr6q#)wJZ{ckG1}Qw;h&fi94PI`UHmuO84RRTg8v94+49Jb34|U z7bX;*{f!|azPoXqV9MXWFx5gmDoOIG20p4E*IUs6{u6U@XNL3sCR1f)%_U+QvqMgZ zwxSJ2N=bxJ70_`^po(I&$pyb2uH0Dg_Iz%N4Du%w`e^ zVh|2My$+B>bxtNcQh~=s7LZ^lV0!5oU2pk#2#(iGNwz+RJP@NHjl@dP$rq>|6Bk*9 zJouzWf|L8Ok4+#nBQCx&`ABMPLfEX$&~WA;|c+RBf9 zmhqR)vtAt|TY(nA4^k6|qHDtaeQNZ;Y9v6`oqyUW%P)Oqs$tOdGdw64S=_^;!6t9u z{*K*u4Gsq%xrh7PHho^-8k$qHaY6W})=KLb82F{X26uiUZuIJ$3lwsz;^pQ-vvDC; z+;c`#cay9xO~}xM|LWi|{ECP$_6i(mN?UO?*PI=>LvbV{w_Tp2^SR{u_%%yhm)+W> z^OU%kG08Ord{n-SyNyEgaF|rgc;-!nqM!uL59DTRx8Jx1|52 zr!+f}$H@SmFx18_1rOPbMrk>r%gmsaGGm~4c29BY(X$5&b^;7DVThQoa>@ymJA5Y# zCRWuN9G?W;s=EV?f$rtcJc2;{lrxS)PxO7iMA2%s zN#;q3=xY<7ql)o+kkv^FP&o`jH_&SX6Fz>;&Jyd=K3iUh%M&v{KWL-@PpobJ9 z0+#uQBwY#eWk<{H{RH<(nA!p{9u-b8_V<0$fASpeDBszq;LcENvGW%Mw3a5MHjtc9 zM5+?|^)3Pk$dwWQQ`AH`qdC?GEzT16;uLWVxegRIrgm*XCx&1X0)jYHk&f3dcF}v% zf%kKYL*p7U2=&6FeA#>GJWktj`F8+WG*^RKk9MhS?5-M^n4~^PQ`ZR@k-G1tZ?fwbp!YXd$+W-~IB5rHG>d)cT zMh2I)wbjXGN~QBh2^T=k2589(DKu!ts|0AkJS`jlt92n{S)&uUf9Qrj-D@?XZ!uaN zJ!8FVsn2RJvPfgytsMj9vut$j;E^OX3<&lZvuH`QsV0#Y1cjwZ{L#%(C+*k@m##CX zo>-d^pEl>_fbI#NXT&dnby|*H)16ubF0hbPr^2XTW7?+2LwM3f8fkXzn)BE;{y4Tn z@1x9G%&tzp`4=qtC?l1r8Go$tOjmDDnuKs~-fwS#ksc)L&s-)iLgswjA1~f}o5OWn zojqXr7m0SweOeA3`df3cM;FGzeIR!Cz^)wSUI+U`w@(-lJT1A|LxGbpZRk*fz1$Xp zy_QzStzK%otycBQ>HQ;%*fR+o16Kz|QpI-#X0tU^5;UEjDl)QE)r@9AP!*%r3Ta() zlpJF0qb|+TfFKveC{UEce-iS=kYEd}s8dfmi6G~z*ccwgcv1;7cQ%C}r?dAFb`JCT zp>a%{twDfiAs>2KK}XV4hZcW+9)I4Osj1b!S^a$ugOM0>>ogH>#-Y3@&3L(2uZuG# zSsi}}X#Ub?8<#B-WWxr<8DPT(y**-;FjvxivVQ3zS-Uc`s{6ICk~9xC^2izcBdIdu zC2n(8z*a}gjg00o;uMYQ?gnX^tn(Tr^aW&&Nq48FSl$9|HwbV#a}b4{V6rqP#d{Xp zENYg%d|R&xR7-}SBT2f-pS7L2lnV-}8F&>AmU8pq$?&F_R`d!};(8tm>rtnNL+V2fOWkH@&1mLuD!<-54) z_3cwiYP82gFJ#M1rk-&{sMPO8A#Y4!v})1OMm5n_wPHYQ%QHP?Y*g zw}=oWLsHnh>1KF*%~!>Vr7Vm6%H=d(>ox_%K93by>mF)*Wb5TEtT^lsNtz!SjO4K> zrQg*IxUK5nXV36gqss=J-t%QY9)B^eKb|E!?H{AE{j{HhXKXR*Y;Q3o#E-ITOJGV} za|hEhy^y&}iwKD7HmjT?!C;eTculcYIL?1S_@J_v=x08)8Z-5|G-pTypbRZR&Clf0 zxOvuoemavhG`Tr(FR6D8?iI!@?9jfTvz^aaOTU|8m6@4+c> z@AS0axjP}ZiN|jRxbG7>x>gqmojrBYO&x_r3o9pGpr?`HooG?a{{4O z`EX0E@e>v9?NJxkw9UYJZ7z>V{!u3U(-V}LYnM47j4?PbJ?0&xdM%e57Cf$W=i&;P zSiCrWl~NdmE!Eb(7NN->jGo-MoY!^q)mK7alt)nQd(X#Un-PnO4mgVmf20fQg4p$;^k#$98MOC$|orYomPRTF_ROw4HBkUv)(+A$iuH(;WIM};e=e;V)6 z@H)asb&9H=)-q`^yxn|c5_8|~7|v*L;G@@bh0t9}Pg0DNK0oIxOWR!iGtz9hiw8An z*1D!BrBK*S^H7-}JKIE@-)Jb#*AWp>+-Xzc@tkGF+#R(w$pL*wz{z_uD3tOx;FVzO zy376O%<5^~Uzpz|LZW|St43li@V-Cxuk;@ksWrf;(0r0e0Jqt)1XaSBh&T>ZUrM4B zMW5T5B98Z>@@Q2FZ8h36fxgF1y zsF2%BW`~uPg2G?V;ijbYF=^f?`8~)B4O30e8xW_Gl>5(-jZ`J2Pt!5=LYfnKG++_y z*$l2o9%j5vMpi>HIq~WXErUH35&&N^A2^o{UUr_o=tf$`#)&KXxRJPTN)1CQG|4g`HvgGv#MQrf2zI z2VBKaKd)okcEWb{@#~!7G?J}gg2TsM75j^lEldZR3Z@-=tHWF|MOzb#Bx>SRKl&V_Ur-K~hU=y=0bP~_FB_l?>*dxDU@LXtm@ zIYDfPaAnN-)uXu(p2x18jkscHMz^t?me$#O`UM;ksA8{TDqf}wXuV1UvNkzmD2`g5 za5_#~N}w<#uC~X;O2ey;ZqFmCUh7s5^mvE#-hmbjH1tL)8j|IpvgD(2(SCT(uB?2H)@rU1O6K2iO0xr z2M_oBc(`??GpJBj+8X}Is|c)U9MMM~1>{+4dP<@st8>nu892;hs>?Pm4Bv;ii>ag0 zDX@{DO#=6K7+ErF#5i`yjMD~w4X^0yV*5cIr>rf|T^4)Nfi=k*%59Ci$q${CG95Zf zkor3-f~0z}-`!w7L%PcGv3BRhlf&{dGxQW5h1l69KBSGrb_=wY6eEW!_gGPAC4won z(N1dcPtHxnk8|xj(}1nf$G29R?n3F`O=56VysB^%GhU*k>F!I36-WZcS}wFq6pgXmYG@J^D)gil@HuL&r{@O zC)quKB{|3{wILT7OtS~$z$jme|9oMJ;*BNX&GRJClZo^QA#8BMQ?7?|E3AH)1ZX~s0w0I`k9{43mqXrzs>IqAGAB`^GBAbVy`7H6An^lhib4lFuT%Bp<>fntd2Uqd*OfJ zM)|o36{o)S|Lx>Y)qczMIrZiKU&;Rgi}l~x^L&yF$^9P!SQ(F`xBCA~i%xSuX*F8HskN9+0oRMzZ=D#pY8K6}2X2$4tlS zzQ&7-J+5MnG=@1Y0jFrt#T3(wnLHWbNP{^}fy+X0|M0`}=U4R`<-5`*`<9t3Ff~b0 zi%yPF)uPZ6SGOp&$heidDz;7mgYEb4iukTVPi;k%M|s3UXhoM~G!(Mn9w#!B!meRx z$g#6ylv3eTM|xxkAFQ1>p|lS*$j3V8XP67DIYoF0^y?!EuYxzmBCitgLY=>P#LS;9 z)kf5`rE%27dD;+wQA=yW)U?>XEL~2++%?^564UKBap7zRvNla8WX5QT3^E2Kl*VLx zTKa~^LI%&CfnNxtgGK3CPr4V3NFL2lk+)Ja%!ew_E~YO ziJ=)uCx+KfjtpL%-#MHV-8tl*Ec)&V`oTJxaE5`qWm$Us0~D)zBi=LuxZAt&5pf!H zbv0P?)liDC?6a@!dxUXWvr7`EbBqUSz=NcLH?V#BP+Minz4ioFPo!&sh0YZ$*Z^4R zCuR;PnhOZLo{tN(=BU#^ISpeIq}<(mIqzHb?pu+3m=eh0Er5^#m#VhkcLJww7JYsm zQ-f)P$j@gC*U{ORr#;;JUIFBE9c4@U3E78IyuqgU`>QYtoCdD3_m&0Q)>x`6rG@(K zs&_HSO{va86GS3r9@ttvK&9t}tSvT!&4QP;Ab`=wLjIS9rwM({VaU$5W_9Bdk`ooq zr3Fz`?IDQj98Aq!#+w0}IQ3M?wcwio6v4>(8{QwcM+Jyixh2-#qnB}(j}geoSk>lY zklYs7oV3pT3_KdwY=;!gx!bnglUkvCwQX39+f|pm$M0u*Pa2B2qGQ?x4`BhPGP&XY zO_vr@fnKfu4phx;W$V!4i5pgi7Nmo8{-xQL1%cj!kD0`%aq?Wo3pP6ZLtz%Leykx+ z!Fnqs$IzhL%P8C?;ou8Sud#e!H=r57;UUHZ?SKY4=gV{8Run(t*Xu zgk^!FE`SS!niu(u!MzrAT+-BAsF{hXO_+pwRbv|!;_&F6tt z&CzIBqcoDs%fWpt&4=}Llg61wc0QeVRHHsR$Hbd)qzO_zvV(2$&a27vSk?8Voztdk ze^zx8#Hwny@g8ZWFq1<9kahZ^5jtpIWvxGKNU#A;p^QD?tcrY>hleaiJr|{Ctb8{T zh-$PrrP#r?>lGu=KuJbig&hYuI3{v-6;xP+0ihB>Dkpi0C))cBW{tnXh5s~n6i-qP zGBt2OwzsxM%T&8bs+DLny+R_|JF5~}hrO%2`9UJIuu7Ecg6wf%{Dc(U((c77vA2b& z$2n586Jqe!u;C9KLmwqS9WBtV8YM(PGq)J+u0M2>R$<~;=tue)8|@u@o7Cu+aXnmCb?{?LIVG;)CE?>;Zz|M6PC?A1U2wd?2-jh|lkmmmP{KGA*Laj-!1~g6XVC zg4HRGUO6o?Esj1PdQ=fdTaI<~a-0_hZMRAOixx{)1$>}_r;FnDjq$_JnL3H4)#;A_ z^iM(z^~Qf5;&&z5`i~&D6%6vPE0&4(yKR>sTlo$fwuv>ZRqgV2j;%ah1Z(w=0^M%h zYKkFu+!ldDxCzhiml?$v0Ajn}GM>eNvs4pF$BCuw5{F46Xv~zs0DlM&PbA>-H+}}G zV*P04{b(gt{PF9W=UlsC`2+vlhMf)mksV9Beb&k0u3NT(KmtVu>=}BCvza8-y7vb6 zX%iedw@#PUiPka{%_u=NOyM1B1LVrx9kfwV+?`Mmr}zxD4s@<%i$@f?U6^V@kfvE0 zx)LyE(ZD_qYe4?Fk7hrVe6CdrQe$YUqEV3KX5-_=PI``2w11p7M{S=}9A&CzNob-; z5~w|=jbUt3gz_vgz*us@z${(C$Y&38Y{30rF?OhBvI-m~vTeRpzPaFM;MZn1a= zPkd(uqG=oe_#;I4&tV+ly!g*)>xVhG2=u!iO=Q*pDeD(&Fi?vmp{=WhEwy-vBN)b{9Jo zDZpYEzS9aKMK1gn=Y9n<-hO$&!jwa+Qat$qWfZV4IuRo#w0V0S{#CfySyyC0j61V1 zbs12fMq-E>ne%#_4HK~Pr&{X?xvjp0LE$xK@bGXQ@#`V}5~4W8o@IhPIkhnU1-TE) zQ&gGIYsREBNr{Tymk0%C{Rzlyq}w>=75*3KY|+nOGY+vo_ZAsyC5F9YNtYVBUUg2h zPaPA)oc^?G2;l|R@5AS~Bf%_}%?T)ms8H?oH6~gS z-1R@Tpo_)H*fwE`hnQ)zBnp z#WUwg%a_8XaD#)~^NFR)ZM}B)0+oM;+EbD_>mpOcF@7ds1CGMgoCX;K2kRsH#Ty@j z?#{~l0-Xl>ggjL-G$tPF)Dt3ydFL2XU zes13-0#dJXYr=Hn0#+G3d6feMv=3k9)*-7s*nmu?tw*Dft#%%H-J(>w{)Vc>Y6^8g z_&2$$@z73*zao1EkPv-_`p!EsmQJSaXQWlRw^|YSp9LWUzfl_y=7)bohtvrHCqZAt zU&%;~zDt|?aINfQuOKfHf;G}20JO<(m|sQ0&KV%`crb<`qN>>U6U!tg?8Sr-eDCo8 zB>Tth|M0qZ1O2SlzpE}?ob9~c9>n+lwgV|&v1m$ zz5fq0g4cLXbffGMC`U2$O+y$<7f*utpMelkgIE_RL>O0EqMQx6t4%RagcMgp%snyH zxG^RwaAk_F;Qbf}(?+dZ^zS604v6_@xuw?BN@_1?^m|S2-PnfB#Vw{77YdjciHrl#6(_R0f^EG5eSMJKCt1_iF z?ovB-om|6~O{48b@}hIQ$ZBLcG%Z~3{cQT!tK&7WOp#>ETr7?Yi`^|U-pQLVe>?Ke z^`DKkTNBt;?>b^h+3k9>k8=Pn;$`Bod_T^}UKR?fIR$QLLEG`Vn!^K43sL`q>*o+(`S6$q zXw6yRe1JYl)<+R@*imltS#VlZ@LN0P?5*O!nr|&xmhD{^>RWayk2HI zk0C_C-M&}%%zM!CEi1u&rKTeORT3~p5G*iv7m8rQge)U z@UX}gKoq?MF0o43b8TiigRN^U`1o=-jIs%jxsC!hU=0Fc=v`|{&|PWIa=c*f?s9%z zXaCVfL{;vu%;9CEb!RF^kKNeo%p?)T{43NuwNGSm2`)HPa7(-hbV;UK*!cylg(kqO3}9W4e`1 z1aA4-+1;ho38A-PnUo6_{GFSI`8=PmX$$HEblB^4x!mED=D>~&W^{Wpm+4)iSu9&ar4j6 z6Otyc=0Pjrv#Jb+IUOB@b3f~h22a0nlbjYf&kr~)hgi{!2Iv^H7oPAbXyJ z_1Pl=qfXZJ@qsHITSA%{QdcjcuZ1Z7#(cQkqapKq1FG*@9N(n`j5HCCI$l5vq}EzE zO0_L?<1|7Xj2{AREwbOWh&*lj3dBt8N3j6e#je=edTj}Qsn#ZB>-C6#iq%!g(ngZ7 z?%An)&K|hrREpka&*IweO|5D-z4>zg>%Ou@Jy<>loEVD zdKGp$Z^3p!A~EX=GtLV624oJ!5nm|^oh34E^k-!uW>lKKS@f*svxp-p)DfwGEo9^x zHoL5yM+(TAeLfY!d$OV4{{UM)Z#)?7a5N|egx(FRe&&}6iL69v2jCu`VI?kX`aPC+ zU~Z5dW@qfB71~?x4!S^zrkqYc3${3;s;=4aJc)XYHp?JS_cGH&qo#mVl#}f?QAT=Y z-pG3ECNC>-3`3^M2qTI4{Fpv&52lrBz#N;Bz#IpPSP*35!10h+HoVh};!F)Rp`T?? zPu*wC>Nc4F0RI5F-rg`p77Rj}%)LbO=ug#K=F`$`bD&b+*Q|01#6HSr4Dp*d{VV%i zSfenG3oHn3$_X?*H)n~>F3ZeIuifIJgH6w%nwTtyE*vxX@mDUHx+PF==BjSO3tsTs zwY;iJ!qQR>?~(i-9BEyxYt}}yTZ%d+o6KDhg~4g4&TxdN?>J9PP#W8qnWEAVcvlhs zxPCF*b(1%M-eOK3yN}JOXvS76aM&7_Dl24* zn5gRCN0ntn$MgUUHBk;1k`)Epo(hA(fn$2wnE-<89*^&*lV|#UV&3SlA@W{Q5dhca%u7Unc17l_<4PD^(1g=4VIqslj zaW$|T$#EpTz**w-;Uoq%3m{rNNI;0-Pw6SJ-|JMN}jL zI1`GoP>G5n7_Dy)e)|iD0+&)(vH0=mMX?mXpbtUoPL?AiXo3O>xA+}tSaAYtz~q|3 ztOxB#pL!=r*;ukbf>mE|{mS%2w^6vxvg{f`q>TrXT5>Sc%c6(;kVQ|4_!Qh77nm{Y zD}$2_z@Qn=i>bmoc6)2rk&{V?ipV*h9Hu2&+yXkOrstNXIFm75=bs;mng(-M9kH$- zOJ0st23oW(r3PB*i(o!6hl z#SwjH1GVR`N6p4>a+*fyI)ig4N*(9$MfqZ&X*7IQXm)bC^j1zuXbz-1c_$?8*ASB_ zG$tnHId%)vWf;@5OZ<2z9t1G$pxH(cbxe|52gJ1(2eQZ2#Ud>xT3;C>R;HXiD_Bnh zS0J%X!e}f;sI{raJ&2`_!v(aILm{p+q9_OS-WD@RJ`ko_@y&oalNI}Wy;j)8L*alnrs zIpY1Ndmk97<@`r`!ckdMRIu*v zS1<(kda?kzE-Lv<0y)p4=@#jhvw@wP2g2f?Bj-8M#5kYCHlM_SKc|!+y$}ZGNq}Ik zuRdHd?Td)Rm?&*er!uA-n4*qiWy>^vU^J+Mwjyf~u?Zgv-r+o@htZ*y2y7BPgM?@w z@#=IuwjZw1X4xIv=pC;SZuiK2#WEQ82pQZzvRlvt9ZNB}$i_}?-vmUmIMlU4q2WB} z1Q3aOw!Z{A_VbsIxx5+l%NUxP_Up&duM@N?+Vwk?L|g;W(RVGY9m0Q$w|+7sL8JSV zs_h{}hDKRa8Bi5=_C-bYj@LVc53vmAQixBcxX^LLAQQhxQ6h;HE_t3~Rl> za6-=o_=be}_zp!}tK&&D_$H6j4XNU7oISF;t@xSojpY`&7n3VaxfWa_tXv>VU>CP9 z<3K&q$VbjH1JB^;Ed=Vj6cuU>g%r%u#GTEK^7;LIzi2Lh^1JRoXlHkW{m)mM@4Vh& zYvTpqzklu!{a=%58GCO(Q>%C1Ck0<0XZZme%spL2gA2c7)1Og6-=i z&c=6?9)qV~XBSYQXyY3l!#r@#t{(rZ)^v$!Ug&ISAT7wtRS$<~wWJTM#Rg3Y&d4BHat;R*& zo6fe2{`~jHr|EQuYc}5kAY3g$X8uv9ID6Dl*D3oRFjd${KlpJ)SG-KQznpaCl1yh? zsDo`@OygmaX(kP%Mj4Sa?_&*o?~bM(WuUCn<&{YmTk?K26LP`Fo0-}6wlLYhv^$!f zYJa^i63PF(788~(JZ>%jdVT#o8;4~y@O3uq{k~7P;~5pim05J}`3j%)t*_D>r;tGj zQVT=)h13Yh| zH%n{L?$TQabg6YS$g#?_6>)^Lt1P{!TrlrL~N8QiS2i_uLQ?T(|_(4_{29uKDYs96ag z7W&R13*?%Co;GtI!F;JUA*CG-rsKmp%8eJ*)~iQRZRO&ct1ES!P-?{b(v%v|O9NH? zUPX#p#<0GtZQH7JTduiz!*xcn$d!r=uAi3QM^@_B;%hFY2D@T(I;EaP;lTI z=iLjP*p!7(pUVznF!TR1x52B&`c-^834!}p4)tCj~hM|gWL-TG921ayl$mu){O&GfIgsK2b{Ob!w zhc{hM&!B>(ad)nQweUmiykt;AoPINed+?eyN-C0fC?BmUNbnskGZ~;j>BMShhCtng zOUlTwn-y}`$>|}JJHpFl%0N;u#U2s76w!-pvYYq~RYfS@^gG#g;WtvfFy6ys!ZLbb z4<1PLN{Z*w@q?uS`r-ckR+9=;asX+JB8uDu3*}A6V4`iFw(ySnIdYANk6-WK&bL74 z#W%xN=Vt!c0D~pr9;+lxc$V9#@10g*q?pf5PWvo{ha~{J!+e${tIpjAwRgMQ0<0nm zVg3BBl9#k?vQPYV&IzIT3&}FOPEhfNLk5bTfI&bNcgG|44*T1la}2|0%_c$qb?v+* zTYmh`n+qg2R<}7~6a<1Br0sFUoJ@5E>B9=VV9o}&Wa2ch-~~aQ4dVS!Yw!*Uv!G4% z-6Y%$Zq(vS414~sXvfL3Y2p`ntlIfqXv{roy|#h{CXuRJUFqP^CCopi9=>JD?j}7d zG+?QzIv_am%0(YaD}m^cybs>O-Y3G5^@PyyYWA~`K&wUoc5G6cA=H{xbu)vph^=6Y0qqmH?thoNLLEMZEH zkOWeRtwg0Yw&Jl+c(#|EJkrAsc$Cg8MF|Ay()D4HgVSF#Ie#+wm5vT${jyB73fr1n zD-22;#IiRPd6==Uk?oV1%n3B+@=zgE9_WP=Re5`u3*BMNiKDi&5i1{3Ggme~0HGZv zEX+zbi$_YJa1(8lG(H#~eoe6L|0irQYVlw?zaNlg&Pzrgc6bJyfj9kzp*igf9cYp~ zb|O_UHiWV7nl4S8?+cGW$J(q99wv_7ISd(u;tspL*1nB!jS_Sf%Z2Y+S%Zl48#!{4+t6cRP$ZY1-{ zgo^vzy!*tbpfeFW&Byt&+YuyE9j?#T$FOTu`f|-j&KpUsk;^D#!7(FLV`}_QB_!5J z?ZSmS6hNTcU-Q#rx_iCZvko;FME2&pdYT7D;Pp;0yw!qPQjjbSlany4Ja>hc4o`36 zH;UJ(ScE}f0)^s$1ZugD5^S-5#)A4{_A5y8W3zf@(%LE4Kf++wNP1saVYTG0JdfLP zDGnOhX#;A{K8dI9S7}+hrTrv_YCp&u>zuiq<%X;6CY-Yqxj+hc@!6&UVp{Ib#mb{f zk(kQ`I4`5U#3Ez%jsqSOZM>#O2edq9r=En!;vgxT2*NrJoWz?{9H;JnN?9z^gp}^@ zMe5uJ>uBkUwxKc*j2C@Hz9$E084pIucK|#lXy$MRak2tvgme}%`$Cc;YMjymkwvWUr*ywRu|@66fv3HhT8lESF0T9 z1#fNUIU=V-2*|hC=6I95-D^)V3_Xei8}GHAH%^JRddp`#%rZ>+=^-XMy1 zwVFoL_5Ul9O`IyVltNB5qNm{1Cntit%3(HUlD0-$h~I(aRcedgqolhT1Ybf!IiK=N zd{UXKG{0x)>0%7u5fMCf_GA7>(3x0&l~mi|d#f)xL8+p=hxeZZd{w z@*B%Y1osL?TRrt1M;MRZGAE04jW&{Otz_s1hms=Pi5tnt-oC|M|10nm0N##Kb^=Q;I^UE-vjKzVU8-Yi&Q$!Z)13bXdVN3Bc0%ZuZe%_D!yRJKsBcMeF)RoAybQFu}-=uCDnSgJYaNpd(=1oyggc7SQ+`Y_3z z(c@<|={TD*d*CZG9iPfeOH?nXmo?v^i+>DM7y#9uyx5SPQjF7nEz>4qr-YbFJZt>D z4&$j!kooq9PT}vhF%+(u(m#ozX@G8mL03;Ua3w-bi=U3;bmyR{~< z4rLEICH#jV)LyB6j!_79EWpSGwL`;aGLzfGkqF)#37&zmpkFbIf=%>be|x>bZ}KeD z!nD=iv!L2?vpmhV`N{e@6MlT{8cNU*B8dnxMdU65GBl8VN)ykR`mkRL>S3FBuJmTn zCr@FZUBXIqT6N0*SeXnot1NJr#!?;TljH$}b8QVH7waP}q;oz`SY1@xFc?i-R!gtZOHg z^OA{goTe-ry+>1}VX`U{mE&FVW_>3)*Gz7PqMS#r?U7EiY(ggNYY}iCtuS=_YVl1! zcFmJN=VJ>Flyz=offF9Ofs;C96aa|wm@=g46RQ9tLuR4sJ{s?c+)ri8y+fLvmA~dvl$67j zZLy`J{6xm4^l(iJf*-6UMx8L^upxJqtsK$tGH*zAVhoZEg3p* z<2W&eqYLC9{S59~Sx&IC#tl~?BJX7=3S_{vcZ+5gs2f=a&bnBEQ;JaCkFSZ)Bk^Qr z#BO9ciQiAUEgB(w@E*=AQ7%SZD$+VsA|?0dz1cusZ(l!|nu1FGT{&l`qhJK7ROG1z zRk16Y`36n4QeJbRYtLFD-&6BqFdyUSR?Yy}Mbc3x<+egub$Q_3A zTON~D%@cQk!nTJIf28-*?+0M`;H77C9P61}e?=Z`s5Kk3?Bb?pO=Mh$ay*;9TQNmM zhJU6Nv_qeI1p#lLe<#4fa{}`Iif(gIU1GyJ7)Mxu%}(yV?*8QFo4oq_TY!Jp|5Eqk z|3b9$|D1pEKZzH70EOGQ(fu)V{{i^y33Pe$w_k6q3A;b3#GXGm{eOZ_1XR_Bcv9wE z)C8gB1QR`0nf%yee!(AT4!$n`-?1DhWwc5*@Ge;#T<(9aHnt^CZM%vl1OkqcM!z#ybLB-UqRWG18U8*58Ii7P~b7C8NMdNN?-2ZJ-e+n}i%1f02GX69>1(-vDt zp*%p)ag7Aiz}DSIvjh7U2gh*I8DHA<9rSvnJ2aufu0!V>>7o>iE}y7R^;rJlTzslPG#SDxC8@HvJ9}(1>Bone%$WdWTU-JV8!ySo+2#GJ6O}#mXtbQSb|3u zZ>&B=m=NFwK9PxNnCm=!Yp>!F>rC0~E}WZ^;H}K7P;E{qx8bMR(xzO6m~o;{*oB$7 ztG(kgSO0C!B$Ma_j|j)lvWms5y^Tb2W#HzZ7Rn~fAl)ZIBLQ7-kxK?CRu-BzK^n?2 z^so=6TI8T&D`;k*7Km5Cj`UMAPzfP`=UdFFEhHt7pFwem+A@6IR*vU$#|yaUamtq~ zAT!bhr3HIL5qQ#PTZ~PH5=5+mwd|+JwD|F=9s(ZY2cw4%Zcvis2$8^e=jgu}#PwhM z21}bZpRfVv)WuUTK2=#Yj$=-lL<8nFRFe;oh6^+wIbv98Emd#UzgG>|_ zMTj9dL(toG8HiHuB-t>u3Up-llKZYOLWL#*cfNe0gQ02(d)Pr&JPk$8Yjt+Yhl6Y0ps;SX=~oh{VI)$=UKD?IHae3vLAM-Jd|soh zOz;8Mi3&NDz!^Xwu~wypKRE41GIp6j&-BROPz&+<;flkPEjY>Rcg}}gslTm_tKy|I z2*}Zs?Muou1mAt$F)-|u$>M=@4*@JGIIg2?U7}C?;P;Q~G(A};yi6BqSy;KN#|%a` zk#YOLos6UYDwv}czA%u6iu+p}wa;-0H%!$#0>yFtLKygwN=MFvP2TC9E>V?n@mO5x%aRNepu#)vqI- zt0dm0I`B-Xe+b?}RU6p&+lX6DrDhy9(Xgt<*&8>#RQzi?rOgs$CqYbc9+j1x|Gt!^ zMW0oTQIyvTZrQBFd;#ZFCr{HE&D*f>WQrVCNTm{zaZtrB3q>{23-vigRP(*skjGhskGfgyCI2PP^LBNL8mL43Ja&287ZbAJ-LHaa^_&u#_BGrqdvKwQ z&amui8#(*!+BHCVG2j|%y${nlz}UQ0=MXC^tjSTmjxM16S$C<{OiQ2YN$Bi8v4Oju z?7gGY7w(6IjqW^$n8K^0uF&?uQ!VxRqtn> zMT;hwy9ZtPddVKC<+qEkYX8grvH&nbj*mda5jYgiFO>_$067>My2bDq;=rW?u8~fj zh9Gdg>?eU|aZoIjKgo&F+x_zo`S{APp%3kIbvUgdl?l2A70z;fwzlT8zxCJK57YP0 z8*Mc$W6#6DuOC!F7{YVS{fo2uod16Ne{LDSZYc+EKYrfB^LsuAqBSLV*Ld&zuU#Kr zx0$}bd3V2yPkJQ7yNmXYsj|+PU$whGC|SPBo`%4P!>+i(%sdIA{^de+Y!Zns@^Xv= z30!PO8{liFKFn6|QM-#d~uSgJIh~FRXqEn&ey>1WsSPwfE1Up-2yA6om zdl^5&{*{hL@l_)3dl|5ZbG0^O_szxZA=f%wRqX$Q5cd-!o|utJo}L=GYO17v@ystFRw8Ovbx?kc44Cjk!h| zE#TI1PJ#N{Zbug7Nz@1kBq^0*dGzA!3bI&;oy(Az&u_UhMe-^A z>2Vk+IhA5D+H2EJYK!;~L0+5+8j+@(%ZQ zmH_1AHa@SJlW>0+h<5H!UowRgvKlAP0#t<$XWAr|V zo2D6XTs#-edShpZ&2L<-af+3UuQR|IC+Hk; zwp!+x4BRs&PEu7P7v|Ebx<040`;a@)2qa{QS0h>O5Gx(u+js7PrCSbdCsX7j7KrKU zfg}cG%EW)4p8J?+>Do^7y9L%(k*_HPwRbXZ=p-bQi;G+|@0y1=9pU9>79w{1d=kh(x|<7>75 zy&dnng@oVL{qE2Ageas`uAqsZnWuBd1W~BfEsk)Ek-E_on!pEc}W{jaa z%GlxoE!8^OGNuCCvb-8FcZ|}=GK%O77#*p@;vZf{|F|Q-V~d?1bMcBq22%ajV?73pIUjtV~s5y6iVKX?WZ#~vshz6n{AW)en0 zg3&`9I!Lx)ALpUC$ncPBT|NX|Rd~n|?Zc+d!dZapJx9#Im}Vv$C5DBv;z`)*;suAN zB5|zem?2W;aBWrs#~-f*=_ssoJ;wA2kp`_I@k5`$!<56$^QXx$D^|9?#-;+y`Z|rT z{L4Pf`XgE?2)Vo*yhV*xj%T~b%-p3|_EN*c?Q$5H2MCqzSnf7D?;{jFBefKY*XlW% z)}758va@!1VSB|85eOY8N}%T6Q@P0K6V&xy5OolQ2@D#{uK{CaV~sfS$Y95&jUuE8 zurdMKQ#48-yq*PM3*7k#v^TXidT#^bu*M6=KfIC)&mko|z#h3k-3mYk zUE`~E_ob9Tvw2ug3lx`r0g<)%pv|K4Dn@Sr&cnG*S@V?M>fgID2v?MU^7*!1<>q!b zUgE}Fc0V4NI=o*O+|KoBi$A_u-~4%g>MI|Fi;P*scJf-Huj)-Ca)YN{rV-lYgE%OV z$0FQ2loZF!3N?xA%`q1llyf)>diLF4a!H_y@-;x)6H{&VhVGF2y~|tCH{0q zrzGn`gI7U%%ZQffIu+d)nIq7@m>DRQ_aU#?jfMPUOk5Tn`2{Tp>UFITp<1h!^BFXi zcck4~88c1pjSd;MvmDd&7+?Cd3D<8;7r+2B9YrTt0k|~}hgKXMrzxk+E{v=(AO2fI z2zt16r~N@MXlJNF%e)K%2crm8n0RhqRIxa-aJYN+enq!sxRE&>cWB}q=mhlC{g}P@!ARb?Dwp9($$}7IX2hz$*o{Glc zLvj|6L)A~;1XOW5-7=lK*|o^ z#h(CY&UTwFa;ZH>UhTAdHBThR@t(hI0>toKhQrkdK7cNhH-shEleS%$#!s?zFw^~- zjDEWm^XunirP^YtR`Vka!|W5p<~QcMewM{eO~Zs^Io<4zHjWW<2DY-ifl^bTXE(6& zV_)$q>!YcBbZ>4F??cZ1fIrNgS&tGZYS$BuwQ0tW)qrKqHDL`q)Tc8x7H$_@4Umav z*d4|y(@3Hcl`D1_!`XZ5Ei)PfTZG5Vyit)pVlNbD7k+O9kvC`kSeq%bdFg$#;|@w$ z_sW>E_OmHbPA@yXEO!XQPq%amN}+=6uP7@)Tymz#N*);^&6I#n@h&YhRwIvdr9ko! zm=cQ%9ql*7mv2TBKm~$%5ku>W63i3X-<&)oB$<~^L&jV&NOC{IYzh^f5NRR>ADsZZ zLB&ZY!VyPIPDkR62U^YW-+eBu%@j;qWz#Cw+hd#E*C`w>hO(j6ee3Qn;PrG*>I?LS zfq982O)~(6Gcp)Tl#v=ouT_LNEe|*l!8`|V(|yl*mEZ*{Q-U)KKpdr6SDsz zjb{QKaeK_Gw~E!_0D+)&Tik#1lNfIbnNR}94?Us5{BeN@_l+hG#cJQPL(eEyn)v!3 zv{AJ%4}8-y5ih(`weUK|k0#5?A%jMT^u=Zx9ZqqWdVnia6Ps!D7)?xfLTbw#a$Do_ z!I%+sI$vS|*P**s6RPi8l)$AJp`AEkg_?uFWWd_w{kdbe-P(^ob?LVf)Cq4(ZJH&i zYWm9RYIODI%r~^x6I^WbGVjNVaPZc)|I}aH%24O$huQ_Q=Clcx(uJgS!G1zn)ahIu z2F7eX^Mwe?1r&n{DZADC*P8L-ZZHhSc-_+R1CyhfrF*LB%OJ7_CQrRjrOAWR6v@9R z6}kkNkgZalJ~frBx^fq8Va~u8oXbltNi*G@+&jjUz3M9 zCy;baHDr)M_fdmYn}obG$oW5_i)Z)?)-=x_XD`P6ELhPh4?M(?12Aa+ShU7l3~`j# zQ^*9yMvstkxp{Rd-3tUsrDE>prAJ?Zit57$FTG-%wdVwQz>9sQB2HXJ9KH-wh53Y5 z?a;^yCP})2NZ!#yOl1?5|6vTAYwIsk4mK(dqcYyUj7fx&10b>V`jn{GD2bv2^AiD3 zLIJysH20~o?Gytd+hb@`c_~$gl9Z8X3bed?02^Wx*JCWW3)4a~?c@OTEK+Oj zkfVTF78xJB6+*~yhqRDnwo7h>)i3ZFpeLyhCB zA9GHQ6zg=ZMjsBCn2#7j2if@lBuQ0Im@;sFtF*W1dwiC5TU4@v>bTCTqArg8=2kjl zDMbfiEX}EM(kA6mPc@5?B5kh7;un#u4^gXHlXIRxL)a2&GZ}@|X~&{aR7_54&w(p% zq7B+cLfaR~#k*zohIz3f5rL^t*@SWk{GFsD`PrMI5a}_=I56x(O<{DN<4}tep+20C zO!8^SuoGo@F(GcMl0dQz_bbkHSw&q|Jy3<5B~i?!9Q_bfu@voa5OC=(RbwiaSBj)@ zk)I(=tguNF*w-s*S-5Ylt_0TR;vjZLU#n`40MW?>QNVMLN^85sJ3G*s5OHluBOdO) zglCUO62Qr^^cM(+;5XH5-*GoLD+_wJb=-Bt9lZhbdZ`Cre4*t)J$<6Jl2G8xgJN`nwzau!SJa znBY`}A&;2gCc*Ki5H0}2H(>mfVJ10X{HZvj3`~~^oGzWFf1q;TK*`oR7u8JM}FQ0|opFYaM z#Wu>E@!(KK^?ocLm>w2nC8iy|+LO3$^IVN%r}E77Am#V9L_x`e+;I}oyt?K_J5Fl% znD72x@36a3i2(?jwcoAKk|k!Z_y8n|&)oPk_nk021Tq3`p7B~UDGyWMZ4z1(v;RVM5>zbP)Dd7_|R={=+eN*NzgE%&cCL zy-6^Hec+KI8DTA?_gRkc`3G^-ZB3q=f!~2oLwAkRi6>yaX)etZ!n$)~Vn1#sP=aq) zq)rT#PvS4h(T&m{1!3^<^X?QVhw!!QyQ=0g954Yw{=B8gb#9p6HxezYKbZcxT%~(2 z^$Cm}R7Dr0n?k#*!@d6Vq3DxQDvC;}14xm=Bk`9ZB;92Ll@T2;opT4IS_|Wi7td1e z|8$7i*rsO=v#GE_NeiaI7^px%qk|gy2I48RZO$YmM{gCk``5UQvIM4T;-0+A=8NA! z^?5)S`*HfB_^r$--}Oh^6y#Xga-8>rD!f`iUCbprx8r9FtXBJY`k7`4WQRuAXy$iB zQNzB=M>JkSR-T9AmwmHyQ4oU+T|OwV<{KJa@-qwwS9 z3&7Qw>@QGI{+i2mdwHx82Ji3hD48mBZ#3s=`E6ZSx2H35CZB3l5FIvGNB#AAGSgHS zlOIcGi^^7LEsaSRVvR(P3QFL<>uP8R$q{M`DIy;!3w`SgZQQ7J;R2T9L8rH=Bm%*~ zR}%M?i7!`1o;}`U)7%-(ZT+Mh!}Qy@mx=mRqB<)Qv-#}-S5%U5G10tRE+4|y4nL~aM^7zmW)e{#Jn4_ zBeMByE$LI~0um$}uo6IYuy^Fhz~Wyl6~jco&=^B_$n~M<-MspbmHFV2n9UXBj!pYFZ1h)NQ8Ys7#ez);+#tFYWx<5Rw=TIid#)BZ0Sv9?`xB zBN3f`8JbqS$+FLw~*31(GJoM z6?9*L7HEGH^aVzo;XS#^5}@v-@lgew;w1hO1FjW{paeXc5{@l~$$+*Bc@XZIn1g$i zNbUt{qO!dH&Q9yWa~6P>0rsvp+rh+`&A+j$0iyzEe{gN=Jl<+h163eiWDmnq2(*Gi z=L*eT*h8Zbf*j z{qDKnfAg@{Ke+jNo^PQi`CWifP>mpap8wtXU7zo3<>p^V`}n&5j|TF8y6FAb^8Z+< zMoSJPQW@M~2McdsfBLenSFT8#f}*~1tAwJiH-}8h7*q`IZDun0qlg#7&Jk}#bklg2 zk%ye|3iO#Kaj!jt23;t<^Tcf9XldlqDj!3vgl|YsN7BgcPX1w{eniXGEB7yZ5}Y&* zlb)7RWzD9VmR7Up{!tg~_`CNNkZS|X@zVSB;QcG4(*AEF=Nh3c-?cwS&inIpxaLlE zRmAW7_ekDvSr#Jz4;aR8XEJ`SEDpUKweh-Wf9OoeaxxyZ9JE#eR6)-!fP(*qE-^jc&>#I9FyBeVTEhT7E%zxC zQXvN}8*^zOn;YWO$Rk!cyg^`6@HPyam;N>?&oeLl5%bGth?w~?XXHkxKuwPEM6*q( zNKAJ2O%~_s8Vr-J@CIC^Ht@fr;-ou{ z6}c=!%)(J+v4q-i2R4>5SX_Eq#NTlk_DKnkf%BwwCi%%2o)6b4^QVL2qJ=k|Zni>*lxdvQ`1%e02 zZduF%VlT$=r1m6kV#Z*zCi&p}(jttewpWQ?u@F}5QeFS`3F|f-n_ah87(TXlcu7N% zmZ-;eDjzN}EFlw@us<6UN`t~`s-LTX0+Z(Ct*yOn-HXdUm(Ink%8FH1UgaPTC^~Za zGV})5dCGRym4?KM)@K)C*L~n+`}PYX(3kmATGw{ryiXy%rA^8@yTwh)I=#g%@fpJE zk5rfZ2L8eh*%kf54#gGV1P)A=3y&lm8P^ZSWy)$?$6IT0>C56HuCOb;Ly90~I7?1X0e?5U*&2@+P^nmNFmhOm9rOsDm3KJ~RL8 zg?=_3NERFH)cC>s5PtBv15E0n0*z{>xK(1Q(9)k>|K2e-v6+}q0x4IL8Y!3Dh*5_1 zZxDFN;1|X%Bx}f?ks&&Q`@g*{l%c5-u{|SxcltCl*~6*m+>wB1_YnkaZPE-KL*w9i z)6cqysI>3UG$~~*N{0M|9xe38m72k zSb9_*+IV>8)+>RyMWs>Hm~Ld+g-+tJ#bO4u*)XPEos}7)_LeF8EDLT#s3cT_QboC2 z=}}*it7Rz%89J3q;TIM5n-UC44K;ACr6sh+HVbzYg*GXpi6^;yLukb=tmZHIHx2HH zQal={b!~{0cq#nSL5;b_*@n2{MNJI#adtJ8O61T|$|3A@JCU@=24!K`PLE{oLZHbK zn$QO1ED1~m0~75zoDF$bw9E6X3+x~&^s6T|CHTbo9v3lquT-IFenCmR{Yza_mirna zIi`Bp)zyE*+H8XEjKh&i(KW{NXTLeYHpZ@Mw7Mj|V^RyB~i5 zLcdQJsa4iA?8hVz=^Rf!!Yp8HZN)8Vk%_|Pi*;3Ks8UY~aGA1s zf6|F+>=hU9w)PhHyEO% zW)F!-`J-n2oj>!_G&LcmZ~w}=Q^vbtAXq(NjXKD1AN6QW+cO8>QJl+#%;>(Yz?0Q$I>_C$E2}MwgX^|!NP~1pf|=Tt8y+#n5f z1X+&E9qq`K{X?n4Y+-{k*sutcUJF`4;pZC2tyF)i;Vk(J*~<;yNN&dDDJgI-{LI40 z71(xPMA7h=kj-*Li?(Kh72X@ata6*w#@%9`AP%s+inB@PehD&tlDl-tm{>vdjjikm zJFyl%HL=ChUW34~Be*1_KMw`@7$d;)*GLB|;K(8$m;a_~?#8&tY06^MKF?`-%w8~q zM8D@Lzl2j{uy>lU;3)?LQoNjatDU66!!CQ8(F(YFl3#QK5G|>t={P*7gkkH-t;?84 z9LpNS{9Ih7#E9vv#S9mFsDlLA%HNw><(@uES+iHyYeDc!k8=~l$2BMn%dJjKEzgToS!5k2=rfDa$I2ql zDPS>M&iMhHa?24bRS&L#@YFG3fg@K1FPfBq97j#2J$tBV)?}FuE|cShnbGr-P_z7J zYBG9Lnb7y2UqeHo>9kgb`;iB%2`x-L&*KecbeI>^z71~jVT}qp71Op}6aRRuh;dU( z$?0=&qj;>0nDvfwua=}?lSgV5bf{EI6m&A8Z=I4cW~{>iZ@Eq^4xr0y-m%N$2*=O` z3(Wf%{&O~E$f8O*^eB1O)p1Z0h-@T50Ula?00 zPv!m;LIKmA*y#QnY1{Onvl7(WX%PfQK))*3PZ6$b28TnG>y8DJR#|@7L$)~O_d7)*4kSyq9CEE#?(=j zz&e>ab(Nw333c;z4!Ky)>U5LJ*gyeBJxN?i^$@%Vf zE@eDOiD~2t`h|^(h>E4jK2w|4QqW{^-*x+WQ(oOo_=Sxfn*{x=@bcEk8~WF!osu6L zg>(Bw3rp&EG??g{Yg>Kc%00zF8OC>D`nhd%+^GU+Gk+_W<%~yITtob z8{()U5lRLUG>?9=4`Kz&1T36oJNk*J$EkHlx0VFdHT>7^et|!?&b*&nQ38awxEH2Y zm)HMo$xK0KRX7h#+37Yq@h53XRU3T0SyY^kb_+Aj4g1zDDdF2gaJKSq*=y+DH_Y?T zl%_OugJ*wFi7-swTTPr;>QLQxhi_l$(;;y)HbdpGm-FvcdP;h&>8rsuKRIMg zwWy#-oE^WQ$|qY=J;q*6;o9SQOWEI?`Rrt<-K`+G_~($E&DsC?W*B@qjg%H?r4-oA_?P@ZDz|%I4n>PQ8oGulZ=AB>M{sCtB6%26#F-z=s2TnSt5%*45W5$S{JJzA ztNltX9!-bZ2ajk;Ay%+|Fgx}AjlOm*ulSPmJp=9<1(e&w@l=2q+>pfvX5BpI0Eb^A z%vWrYJMVoAwNC{mU!*7>m>3}lfje_s?jSpD4D3OZ(IV6-qyiVOCxu{w{x#x?aekW< z%3D_0G0V0B0HaZ|0$)C5Y5sg?)KPYDf zIbKWm{reJ3TFn5sH!M|cxIXdfzun@4L+e7LwHr6UA<%Ca8zC;g&p}YB3;% z(OmXhOSOa!)p8$Vw~*f}Xr01)-}x8VoJdPMdP zrAKAzRC3gnChexi!)lMw*f&G-yT4-XN6cw@UFq>fBjQ2|9%<_%WK{3+s^UWK2Mk?o zK8RpjBm{bzcm$)GP{$~qUS32Tw<$M?%G_cl=*5}oXZyTw(=sj}Qp8#cdVr8AZ|j^K9qe*0 zDHA-7aA)mU$R2@mtNWXUUbn~<{rZ}njg_}*PXdf1Oo?q(%c6>9<=;xRh)Hh!v`T=) zdD!wn0H8M)cw^6lmL@bX5l#l_iY}1evKnOT3GC_a@ zqE-|byBf3d)z?5BjZpjhr4;?6m>#Y5*&9dhi}S-6^Z5(pz*+peIW|n@@W(E1?8n8A zQtZ1~Q^e-}ai+ba>0b9|{HI0n;OCc*>wnU|{{bZaZ+9!>`(<}<5bt+``E5iy5coFz zanpOdzucT2pYQWc8(Z)5>J&6|3~ftHffY=Shx{aUGGOwL0M^?4t2> z#SsW}mxzDD`Pd%?Mr@X^TI#jauOt{pzqHEe>C#xYj0%GB$#Gb%Zcl!ZnIDuVwg8^( z*1HPSc?IktT6Y~BZh2RZVf5lT$fi^?RYoM~16@|7^4toi$5xC`XFlBRu@JB}i4m|7 zCu}Q54AUE%Pp5#ZID9jHo*weswwsAYpL6J5&qnuwCO4Q+T-7`8W-N zXSB>DRc5W=vsXx!@mSd+SlwT0Uk350=bACg@mOoB{Ca2FHYV;n1Gic1FW7^RBoVEa zj5cgwk)W}cPMNNRMA`l9Mu$6!&S-_0ssm;mSt`ukDkoxv4AG&zT1E5Ks&mYduKPC_oL*HNgW| z1(&+~&BAf|S+q@3Gf^R+&p-OpsQIk97u4S4Hwbm+1|LP zSn-sh)F3p+B2$pTz1F_Fmc7?86;_HfNm|g6YQN3;0yqXphYq!H7_7n7f3YSgX|uT= zO6NY*+gt+D+o=&wx>_He0w10ZJ{4|UrskVAW7j_TZ-%~kSZ!$eV%5a~h|8g;$f@$s z3XnVMuo_g%PhM^p5<-bsnia`$v`bn4U^)oQ^e{Y#h1F!NnxH>^42+OOoBoao7@Qdb zq1m#Y9Bl%MVA6$HU>1VtGgZ-GIiKJf`yav z2BCGaIF}k<=r4GnxBUw(Rvc{ti6&GcmMM)Il^D2hVV!LYGvXAePC>0yy_A?h3vN?l z5;GwgftQJ2GpH*Zel8BWE*-km_sl$zt0j)cv5hxx-zV<>0_75lW?APBgm?lnF9!oAO5 zxfTi$F^E7#-hVy)e}uhbj3`YPE;_cYJ+^Jzwr$(CZQJ(D9(#{%+cwU;-#s_?=S?cr zl};tyKe}plt@Xfx*pjBbDVB{FCw1*@5LC&t&8!x(^|+9U8}}-6Yw5zQaT~QTY=7Pq{w^z4+I}_=_Lp;#k`A$}+?7uc0^ZiK8`iA&6CxB1$roTb>e<15Gu_5SzLW+vpQiY-+)MBn^g8~8q=6E?6 zWz3KQ%!e8$l=|9_CnkRGv47U;6h41We)3RXd;gy_W^T;N!cX_lomlTnny<~RM5yj} z&5rNS+4L_j{>t}!^zVwN+xvDEf%hBTH|RwNTNTt&jFd2^a|eihN6%AH|L}WZurY*{V^y0wifn3F*Py9Mk;Y!%&gfR9a^@+-qLu2 z8uBNi?G^P0bR;WX^)1`zP8$ao8sHyfmC@Yr$_}Q8mvY6rUQJCd(oZ19h`pQDpj9!5L@WkGmf0Oy zzR8reOAI1>mJx!BtC8>cAG+Qkj;mDy> zYE>l)6U)&$oIgQ}5SlzbJOD0;Uy6z|2 zCb2@IEUWjEsA=#Tc@}lwX~*`JzzDGj3t-26yv9^(>jHv7)(c4NcKNYev3Tkf8 zKze17IbGY%Tx=H6)MDg7FV^^k@|ZPy1{1Z|t7s2U3IN<#z<~cXrWBsN8PG&jw$Lo& zQ7VJjV8yO57nnxm#62QRT4t9ro#Anb!~w+LV?Iwply@8{N;nHS{m@wfURvoh!;SM{ z=9|P7^o#T9eC+QnNA}sK`3^xs0(>#}u{%4}rKskuey@6hiP0uNi{jL<0NabFcc z(HZ|#h9@%KUe=r!RN}n9{+Zd{0GDtyelY;X(GQZk(FrERP^1quHH>S|S-U#BihBbC zJNY|2dbM{eaJdh#={{S1U7Jy{bza$1d7kkH$No?dsUn}(on5k+m{B~kkbFI#Ce&Jz z)g+^kI81sTF2_bBUGX*Q8IM)sLz;^mwI@_#0=5i zRhR9vlzA4joC3TR@U=+uDm;<^#2E_8;5F$Bo68Mytsg@OekDF|s12LSn18vo>gHCV z#c|B4HuHw)HNKBzJcg?q(VxrOUWve!z~;U#?9Zs;#(797DzZL4oVdg$RC*}(8t38m z>SV6XaAe!Q@d5;VM;>HeVmi7^!5Dj2!}NU*NMO=a4MJL_XMtV&WTe<(wN+|y|M1HL zVs>&1Ncoc9CCdk3Zr?EQbZk%p@8b&{&#AO%YJ?^q#d&_>!uD|(5yC_*i6B5^^PIX0 zl`~REQZwKI`Elgg5Va!@Mm*j`eT3WhbZ$i9QXj@?$2&OL3~{uON3hxf)h&u_s(9?F zKuUVv>|yOF=jr3nCVglfM0>Qi9NCD|tZrQnanC27hYs7BijL^qj&K%X&5&!3G&;f; z?Nlm4muc==ALqU&Do#woH8#fNwF!Tb`aw5OEE4L@OKdzH0$^-brsTb*06o*T_3D4b z0vhz_jDRe+&?VY;{G&p^>rie z9e~=$WIp5Q`p?Ldcy(1u8^?~uKY5d4i}N=whVlcURJ2j3^5dVP?*A|Xfe=4%gW>!0 z3SgYjWcso}Y#B#kb=a9FY=r+R-~Ixl^IlcLfBYWj__W_rg+ArJ7H5PWH-FyzPKxTj zx8c8;P7bI+K6`)WvE%q09GnlX%kblLua)Ayk19jyg5cz_0DX~Zui{tj z)YzT?Hz`Ooi{Vav+krY!nM^~AYy68Z1-2=JvhjlRZ@BlC*MhoJM4l-j-&K)pa`;ZY zidq`)$F_bhwC>*T&3s>hwDhijM(#lB=(e?&kz%gFoeEbW1YEhbUt=4czX+O{3@)5pjuM#nV3 zuR8HeqE^h5(ZQ)=b7?WZ0)d;+2b4eq8rHgVE9blZe!DtH)(QRcss*?axUWrN)`H$_ zhCwDmyif?i>~>~U)b51yO6TR?L=Q8AIx(5<7!GU)LVU!f(pG<@cevGWEGZLcO2=3L z$)Dp+x&oEv=r1==%v8GCWS;|fONfo&g(pokQ|gr1WcUV!le8GBB;csC!xg~9`WKb4 z%YRj!0_Hf=?0}V`CR={ZmBUhOz-m6~Zou302VH5E3_aU~)7uIwnJc9Z?AxaqEfE+C zo;&b~nizi-48!oqBT$trwW8~lX^MVDz?6OlW>Sn%r6ArllCOXigw`(Mcp2+7Jb(8D6fb)9W1cKO!XuAghCRYU0cdj`X!YMyYvZMRibAK0EDyEAH0SmOyR`JQ!k_Nd_K@|;KF*_SsZt4 z=>78kN7jWET79Z$f2L*6ta5z$F-7K&i#X+i4SF(|&=pV&aCbw@qH%Kh&U*R1Sor=_ zLMd>MkXH}@e0}{GKU29$0<2wL+yx2I#YN6tUVJk{L>#Nx6##Di_0g*3bu36Gmg;~a zD-^Q88pX$&7Sc{wXrgbtW3GVVP_VBDC0+O+(Dav=PjevDNlQu{e7?*+(a81?8N^I~ zzQ48!0I{$0cQ31~!2-}(QK87IW4=1D190w_<`1h4IUpne6^W6<2&>{wLeYn4$fz?2 zF}(@rmX;BBZOQl!%)VS4Sa@=6|AO@_3~y0&d!~3hyAuobZ3)vit^6{C#t6z2z0%se zHN<1(z<5#k{KX*kF+=SinGS77N&e7794=~LWkk@bFa$PT_x2ZckaTwGL`UmqRfJ7Z z{{bS5ZY-g})QK_GxOgJRv%;WeuDff!S+Y+ZHVfq!ENY=Tz@s@u&TmVq2 zN#)Tr{gz1E+;OjH@C(aAkWE#dX5w#`;lM^`vN4hD%u{8T$0)-{mfg2srX`-!=J{Nx z^qCy%;gN4ihA5R>!{IKE&eyW`lx}7ovIoU>n5mFwI{i|q!#fk%RQx2`NYh+MABnhQ zS(soR0o`lCx}Nwq-ROpp@%m46!?cI-JfiYP?MdzfzRV3^+Hg|_uG3GLJ%DhUjOefJ zIokUSz_dyHqDYoly$jtHIQ}lUYUu7mY!RqJL-!=~TjnUNNHcLG+a4W4zm5R`Usd2? zR0`WPF4e zsGe-#Qvfq@Tc4i*z{bHrA8N`g-=V~CuMeF^4?_GH+llDQcusp@jwUgFW#itJYwi+j z-he86*blZNq+Wm!iLjgK&WSGK?cVwEmgu0>fNGJ9H024e$ZT3jJ(QMK^op1lDi?scoF z`kvqXL^P$Oi1jEBavz0xDEiZ)fX_LBLiXp+EA#gmu6I|#^a>J~iH0bIUo_|9zmOkYiUTlQW+$Y3MKG8ID=SwLI#{%a_0KpD>+5N(>Cp)c z8&nNrcaU7lvu*D-Udz9Nxe|o3Ro#5T2_(6H@+T{9^j=0PqdMV+tNVBS#(8@(QVG~Ie=*bY4(C|=5l+9 zZaX6F(J_`09oD5mmIM5_A6ml0q8%2AQZ6%?#$RF#RC_7~^Q8t$ibUAEpNnT@G0=~(1h~*Nd z>faPt=i1MN|2vexOqCu!Vht-p*c7U1Uu2EId6H;#8?5MT2h>U(7TVC9W(b z85;3bVGpZt;R&7GiL;coxIbMaWRKAd{-vSq_Rkj)KeQy@q=h&AVu zOQj=Z`9sXOA@;3n9SdzYXNN@xHDK3_Ngw8@5t(8L%3QWEc;AdS`Al53N2vo3gC?#yV7&BWX zi59O_LlY-X*NeH78mG{ZkeKaw?`HJdKeVl4`tJ8M6dTQaxwDe+E{Qr5>5(syA63RZ zbPd?%9&}X62_{C<>`5Hhl>n;|9-Kk|jdxDBY6UR>qG<#7Y@6>YNUni2{_?}OPKYBlE=y;xohOvWIdOzQ-+Vrrkx$mFb zKHraTx}S)4Ue3hyx^H2|FC)7j8hZR7@5^szpOTpEzi!ZzmF5$cl!Y{D=rM9@B?4Cxr{Pa z8;x;Wo!#j5K|zPhHg(V`n=xU3?`Wu)?jCbhWZBuF%zCS}zF|KPF*uWnUw@3-R(Umh z4HXUiBujw?jA`-B5%AF18e?M-B-*e~Vdx$g7A z$o#u*UEVp42Uu)m`#kU5KljdjTblbFvL;p?#LCNWB_n4^EI}6Msk9*%+Dd8bXa)TU z|8GL;@DijMXhm$#o?07Gt?UBlABLM#Y+skIi0%!E!!5X5TSHbd(o~5B`#o4|{pz5A zhOp33E`2;VLtfWj<~f|4zq2tO2Kwy4O4B%<23qN&mrouK2G8>bf_Ay)xP<*{)XOw> z=Fb-k_~dB1N2f+&ZVgMKL35Wx?ME9cf7l?xo6L;%25MXvn&0n-9$~C;I^}(G#~Q^& zJO(CB$t95)(BTZzV(QGoy-*zXm6>7)?C={-_(t@QL8)%QmZ`ZmQ&xVOPgB@l63a}F zrfdKfsqD5}Xoe@Kn&Qc7yB(oDI;ia4TWFRhsIt$CIp0MmQq)`t!rm%IrpLMx=`0JT zYj2}KJv$%)RS=}>2#{bw{{1PTrsJUjGHirrSR;Bi>ROr*Jg7nTGK8BGAN4a8SjXaI zw3(uU2qz^GppRs=r-ML!sA!^xM;F#`Llun~%n&bbyp3tA1 zMmKwTnXAQ?BBrbT8D~`YxGb2G8CO&RBv@morc8AF7hIq(g zXCmTHZ;~i?X7cZ0l686L26S@ZSH_?XB4Z6u)^MBIBBi~=x;ZZnoV|ga^|L?nPqv$A zcm?Hu9}WVZ0xEHH@rk}|f zhT3On=v(GjuQD!xgy+d4#j^+5CcNxJHIFUIDf$i1i#}ko4J&aBX4K4>aSnXaM#kXLAyF+23&kaCd#|(dca$s{~G#z=!P`$uZ2hDhcd`K~t`wCzMt$YuFHv;29a64+wT&<6A5_VGl7-YJo2kzBzKKj`a=q z*MPG~l#e~tk7rrpH*pizE^Yk11+bsZs}rs;Ua6jcK84mdcEm>wIqEWr)ML-6FhVVj_`uCTtf)viVIY}ez7pelwzRiZBtekKw(Hyqjmkp`n! zK@{Euz4K$NU-_{EZ!QL5J?> zXhIctP=1#+1$vUf&APK3JG$vHw>K4vrCg%;PWd7BtxhU0zGIc-ZNGck3oe55YfhL zEL1zTesAw;ZE+0mG$p5XNJxQ)jMCIZepI<3Cg5*};Z?A0=Eq|?l))JcLhxM?ua>IH ziV_nc8wkPBlpdJx(Gb4$hS0AF?GfJYy!2Rz_%C?^Xc1TKUgQ4!K-ho85kqTVm|~=< zFIu-flWOY3eb!WfQQCqdS|?Nk-&y3X37__%j(N`J7-ds`V{u{XLZk~qY4m||C)kEA zaiIRhqs87QWpq6GEJ9yZPl>PO1YTeQ`M&bCA6(rL4xRL9xF)DzI>nZL%Do>|6HM{_ ztS6~B3_V|=KbpQv)Vq0Pcx66C1zxu>aB0i{M=BJr`OkgJC z^z0TqiW@H)_!J@!UNG3fy0_gd*ak}Al@T{pZIrgo`tw?}t=MRwLw5!fC=d|cW;dc8 zDM6K^C)~M&T1_}o6U9`iT!)iQOFiw~OFn#8z&k0|UiBj7bLgBEDN3-D!{Kr}JA z6#a@NDug6I;W}qk2*Ow24<9e#78+K7*b;f`G2RQQGn5x|@LQPJ**}DY8K1PEU(irI z9EhBKkZ@%!i;Bwc2>yFj5BRfJ46jA?*D?0LJeOR-%*}V!Z&1(j`}oNF1{rMp?O(s- z_C;Z zttGd=`3BJ-7TBJs(I_t?Hy(2d9|u`eSB&-+^NGPN<>Uw!R2>94cS^qVTEU%i(0mGH z99u}xI9x~>QZg+ce;ky2CltWr;ut$TCF$101kuTh-!1ei@9Wu)Pxcx(p7+%mJS(2x z{{GVvS>Dxk@Ao*JEyKs|DDT~I(O@O5<(V}w zdQ0|Mk-h-Qw@7ac1`q0x3xEsb%*lRXqYV}SXVBExO?MrQy;AbkNS?b9WV^jFwaIZ- zZR4|1kl9kifUd$ZYNAP@)w-u0fzm75Xg`B?T4~gJ$JyGG8`wt&3_5&x zO*NJHOSDkJ)L&7!oX0i9Av8ezXOXQ2z;7r@!uxb&xl7cnsk!U}hx!HnELb3cWqupH zBkR98B96L?G+P97UbVSt)RGIV0pf$nHY~HXa+|IiKtn?CQnV5qyQuqvQ8L=M&ar=V z@N$W4qQx-ev-(gOxFR&or139APNZMZ)?@11| zZ(SP1Dn?(zZ#rTU#FrPL9sF~q#Z-|9)mS2_ED*Z7<@~n(mNWYOlU9NepZTZbH&w1h zXru{79~=_RP*+^>U4Y4R44@A9ou{-Rq)FZzF-c9at)2^>R(M`pxmmS*o-&xG(rGU8uqpn#?0BTFm3uG~ z;_}ct7(_`F-;9EES{WHdz*v3Co-QBiIxn#?h8Lvg{*w zKOv_{i8I>I)ML@JBIes{ppkP+;e zODg+9n|Y7f5Conl4`F3IoJt#RqzKTq#*=mJqfTb7Paia0L|ba~UplV%FF%8I#8Sk?=JM3B(6mh9T>Mi?*vhZo!#f-`|o1ENh_-D z>z1_BLoMyHs?o5Tny{RDw4_a0maYpmEY|0wJLGjXmHc zS1{IExJ5WLUvB70Z^(gEfJ)V$*tHyzA&}xL(y&GsmcumQfn{c&YglqJ<_gcCl4#p__K$^kpCWtly91-`0% zGhk@)n^re#3t$bBynV?5vurDjdCeQ#NofuAGe5vvFF0PkS<70JIE~Int}ISM7fUu6 z?**L&EqH2)Gs&e!s4eR!8xdzjI|frACg$j7?Jj~9*YzyN#z(>b*Cwz}Nx;2?5xe;8V#}$~vvR zXZ^&R_qj_yPc&H`EuC5O?Xj?=gz~$V)#m50p)i^DCR+G_Bu`PJ(9h5Pb`EDvr(tQmb+jk1W?iB*#@~oJb~ccK9eo zk6l;1C#*8O)JbmI2M2J#OYmPEg)!nH)SPc}|M0o;-}MKzBK%2-}I@ zi59Da`pC79i$A56ds8TUcyQ7gfb5db+2@z5%{*R|yxukb?9sK&gXpuW2EVH`!lW zs0cKFSf;aCStQFKy~~(pm_oBs$kD=F>KVGBW*>Z8LSu*ju{LjA&dtqhA^6V!@>$HB z|3|=81}wxtZz~ z`5J1>dd-twpbk75&7Kue%Vb?AH&ZV5dvZ-9-6=ltJ7pIIXhZDsmA{SqL_0o%7w_gx zvaKR!G`+Wq{<1RJ^W0%YrXi;BHo^ky%wN(FEA(2#=|Q3x1rDMCnp*eWIDks zUyz@xx62esdLn&t-IE(xa={q+wX1u7qVjaXPd_lQ@jkJ`2kr&3>a-dyDUf&wITMpp zL@4pxe)^F{H99hxw$Hy&UC))?QhN6uC3dqbu zPhX(0cYq0!InPVPI(yF6GjH$E!w66=1gMen8nL9KH?+9o;JKAe6&mU-XL*?y{S_`ZfF+yv2X zKKvd>zW!IRyjaNlTEsq{V!Li6-d;!lKA*5t!AyW(zC0A(wm@ogO!Ec^gAjO*{^P}t z=}zlioOeFW(MSN2aQF}^?uPXa;igZS66Ixbl<@K5ce-&`{xi7azBaSbhjMJM!ywu2p zalAM3B`aKv(jbd@8(^KYQqm<0DQ$fyS$GNi%f{do|ve8$uvE*V1VbIe{%<2 z1~e}|1ey^&>kIbS{X#4m3@+bU;AYI}w1NQ~ZnX0(JG(kUsqI$2Ox)X7N&-yo%u?OI z*>EMXaFF++mIUa|Tz6R{D~)*h^CRIP)Q}cdxhaGBnT4X@q;h>8DvT)R_Ui-w?vT|E zhN&&5`U{b9Y4?eevL#Tixuc13iTUG}KtzvMx(Q+AkL(3AquF(1u#n`&Ck81{p^AEz zI?+$(S*C>IjA$0)IXCyT*YP-?I_^rC*k^jiJHh@^u8-W&GyfNe=*sWnb*D3A;)9A+ zcd_4h7CzYXgYo@9AEU zp2-*mLbmdQvov`3iRlswKsTnl;LqL@t9yVLMX$l8*}oI8JFi{oI~cmv=LT8X(FUwO z=IB!aBFt&QH3mA@!lNRl4+pnrc*XsR)tt@iM8Q0*oxbvz z5osT%>Wz%A!Q}$_?6%BwM=3Z`GM+F>b$mE%S#k-dKEz~|!?qHDq^JvrrTs*K(U-B1 zvRoF-r-r0uBC8ynI3&TOr1+!P!UC+E(!ept#vHl0dIe17j{6Zcc}Dcg?0JEK@1ML# z7-e4Lj$x;j#Wz3>=i{nriN93gv{ZhDByo6dZEC6I6|iNMq5`8xyASaRe|`lr5;rvu zC6!zum0MqhdjYi-L8M5AEBP(F^1H*9p8B92VuNo=C`@AVPO=FE5~w zp)e)18lW%{sfUy!bhBYxIUth4q`}pf4m&V2#U{VF&7klP_wS%*622s$7=QJbONI^@ zxK*Y@c(|Bs$e#*3uUwwU@2SPO&o=Y&;Qw0(K8wE5G2$zOD!e@)Q4b75lej_n*#EEm z`y6k8nOl(Iz<@3293*MNt5Dm6+>|#9H1ofK!p_u zCJar=?dCc9^Hou1iZCQ#@Z60owgTzn~0d-;zhuTZG3^RMx^KO4KGm> zUu7M(ww^JJ6s`Q%)1SoikL@_1W?Qj?cv^Xt1&hae{dC3U*Li(?Wl5^ zZnXdu1*=x!YDoQSsSkbSxDJ$Ib9U#6CM-l!6SR4|xZeFHHvhk*i{QVLo+Oa*PLj0<8F#6?jwOE5ER zm6cU#Wg+dqk_e#4uAlS)(IXUOVZe%>k5@2(n}9u58?Vd6s=xgr+Kf+Y63{n2HY{}d zJquHwpiMdUph--|ZS_82d)@O!IgsBFnnV-W&YpvK^-k|(umhxH@s0J7W2;AyL|T!- zlbGP~qAV!%z7Bf`&U0{z0c~q9a39-Ge5?99zkbBBWeiT+wJ}!g^@vwQme2-Kl+`yR zR_sj$-de5QBDAAp^e|k$Z<@nL7F8Dxdu|i2S}X;73mA5ny-6xaDC3G+lH`^OmBHjC zs{zwgYV(c?QA>mPsx9$Lf~rG|>Vfql7Q^2^@Z7>cY4VjoaIy-yGJpQ0ck=)Fqdxxe zH$f>h*JOP(@w3A2b(zh3L#1ag>jw9{O#0?bnoXhO?bdw&KgUWsf!W_PJ(u;?DM7&j zLq;1u$GnV_^}`xG27nDPmZtb2VH^s~qUUTqc66$aMC-_LbM4_#A=b+4(J`IBF9iMU zRZx3YrgOH1_0;|DQqG}7=9?DJFGa4D`+#spMFhJkidzj+lJ4fp)1xl!#nU4X9qe~| z&hU(waZTW@te)*L2OT`&IGB=$pc$!A+^!w!ZSI!{b=O(1zDBJA zh5FKp4;Y`C}=H8_&ai_;m& zH>K)wI|B_@^J?IGSN7usS`OuYPLU8EF2uXgGPsF9iVGRSfI>;>7%q-dvHFBAl3tqY z;z$O^*oJ`Nj`Nv%7AinI5T}+Ig?tB~_ct9;fn%9?vs3L;4Hcn zqi_KfSIX|u-ji|@N_De8*xVS`Co54$(gBR7Ylet3J0Sk7+S$2-%ai3osXkXv*cIy# z5MW=afq;goeKxgOS{x7X-WeD2FinejH#`pebw)=LO+Umwy7BS_4`f<4wj?pkAik)R z-9D(Yo^Q{e*IxladY!sF)?ST(-((kciZ`4$da(HwWpob|uVHV9oj0KWxsGg2jCwmQ z#8L>01?pwi`@HM>`b~r%K>baO>h>p*_r(v$=W)sIdqMAgkxVt$`?Eoe&i7X8X6I-5 zzrUUit2|$~r`ywtI&F``E4#O+sU43Cwx1C^p7$8&8H7(lkhj(rT%+fjL7?EEO+e%Ts%OM|(OxP`(n%*e_ zz;YA2*Cm(Xr8nk>s-oYa4YArNYI1%j1sQ8nC2}}ll`Wa@R%&}^E7(s&<^Lu`RUtbuKj&BBi>fO=7pUOs~fOeLnLN*WzRPoBAla7W!RDdIDpnl z@r<~x7X+?|0hqOY$Yh@E!dmZy2#O_!ZFlu%oSmz^#^&Q#X4p-LX%q1QDi-!C+w&CE z#5#{KW3NT$#p%Ka*kCi4(e95k&DJ@L=R`8Yq8K(w0$)(r_%}JpexrAG149vK5bJ3dqoGbCiW+f9&?N_vKBaym=c&w zhusB8Iyp_=nM^oonrSR*7*>|Sj*;|kPs^HXF4u{ggtS8+hBY<(9g*B!DJDvs-$6F$ zkiPW^SsG-wqyLTk$;Z@_rjP$*WsgncZ&&nCj}XkE zgZX$smwYi~ZYlUnP3Hsc#6Fvqjn+!b^HFGf78{Bg z&4HK4XHA5)gYXMeu#`v85nwY|zq-5LHTwCPNx(BeEd6QTsA08i*|3SQ4aN2bwQ@8z zbV7+*!u`0AzIvrnySf%VZ^ey z)>#FyOF9G*!hRmUGO~8gN>)D1#Sp`7+>0FS!_6D)La^Om6YnjHfr%nvJjH%hkt1UN z3O81V@~jfg(~tXCSpWIB4vGLIy`Ym%VjgXK3hl8nHQa?9&H(`DEs4DcxfH6*NHf6w zIf`pAm-I^K1=`}bfW=OH(}#n-SpW85eS21~l=}YXpst(}9F@))6=VL{FbpNVJ{s6F zYqi3?dKNQ#?y=%1QiiXTZNO*}L)#x=XP(ED0hmOcg+fuFw3Y)x*%^;|6@@Xg47fO^ zT+#-WIG|4Rn8VQ_tBOgeIdZ0g!`CFct1@sdG^AMxm9? zE~u=R1;8hLhL^tHphRY9=by&RxwPkfSSVRn^xQclvxI;F52%*w7OhJGePU#xwS8r! z@T-H3z+p_jd=%!LW+Lg(Pjmbw;fF9-ML+X;P~pasj*+px5lr!QZA{bV213v^Nj935 zg%*M_$#nm6%r;)5ksL(e3v$DUg10n*Lw-oOa9t0CW|Z7kn%-e%Do@~;sfZ|XeznFn zT^#>*ad*smcR$!Ts=ug!^O#ozdw~Z1dMFLcVI(ul)(qDgK+bQi+C+(UGdedrsi3Gk zJ;^xRokV?rpV50API(5uA7)da_NRz|@kOjOz)7pN@r5?lKX(Hw9Z9J6Jw{Mu<{QCL zwLVBCOXxr?H~diOs8*Dq;)5#nA5cWP4Fq7`J^Y9F;WxsTDOi|Nk&wOW4Wv6-$iH$k z&6|Ib$4EP%JXU0adQ4{d7y9K;Q z+@F)euR$_fW$X|cvo0@({Hup-3rjvGS%S=9%1e^2kxRB-50FX}^RoME6%r*24k+fS zmF$`8ypL9luEh!Jr~@ znhJfkJV%z9R97_}m{P+{c=Y%pau;~E2*5p^ozPuNwObQpTW3>b6g7W*)eRzp&n)*r z!n0EkSqEmlEJgx-0>6HG<85XA!3ib>08oY9PM}m~Dq0~^Vm$1-M-FIA=GBBZHICe~ zGM1Fz3HFRr^`CA5EgLPkXbYniA$Rc@DgL-{XQ46K2e{I?oKg~yQ%;qFw9Ag1fg8d* zd7_}{3H^=GurplFN`E3X{TuP-Z*7QFqjF=g%tiPVEB==#(PHEmrK*(5258N>>B%!Sfh1jDWyfF^ zDYQbv7x}Zxnbp8U{hqQo=vi3n$vphjc3_J5uXTo8dWbof2pXYZn0$7qxt0tT;T~2^o$t!?qZG=$R8C@gioz{SrWUzQC_v1Y>9t zDDibQFu)^;blUJN&AoV)h(%(6{Deh0ehy(c039auj#gk&F&Y3H042x^0A!P-)02;F z?}5@I`DL50T{LPwFLn9Dt)B}Qo-V4IkqKI$(Fzt|+I-p9_@V`~*?YuQz za*mD4*LT%Pj_;uQzPIcI^*yv+Q({D&f+L!UMNc0u{S~%TuMjRe^&Rg5oqN#f8_{3Z z#Y_-7jn1Y|jgG)Xr%gq&i_;*Uq(+)kNrq`v7E?sBD%O~R0C0+hI&!Ucgwoy-hv+3S z=^*k}PvizoG#tM)EB8AH=BzHWtcw*G53(aAvv?yE92_`@&B4BBHs&^D2*`jo^q-nG zx-NC01i4s=s>h;V4%N-q&d>Rewy*W-Ym-rFna{|cMGwax#x&FtrW?EG}Z z@qWI?ef1YH_dX=O{5(D2`Q9AJX?ve-FFWO(d*3^~SLW@!nf(kE?eLe@dI5Wt7jb$x zv?w}4aSB;toC9yn>8m-y8w|LN22;g5SSU5d-%E$0tgq;-u(!^_ZuD54UR%3f5}Wh! zU1-VU?|o@;ie&kS3X#S7Hv63oJ&as$w^;QEvw=UKuBRiN=q^AwxVu9-te8>_!9AWh za722HiqGbXRI_7Qje0V@>i?$O;BS5gYkuGQ&hHIbu8H~F@BTZDZ*^$l((}bD_hWK* zT*oX=rU~yWB+vJ;E22zi4w5wa*33B3N7;hPfR@ec8nLhFJLvVo>RjZFb1K!rR!+PV z{vS&oXJn)ZYbRwzyk1;kvY>R*D|FoS|mVUgEC7f8ll@ ziiaicn8JTF3X&LY*2gRoEgM@H1 zugzV(?&fYGVgjc!`=l6y$^o&d=34c4B?OiXLmp`f2E0n<&CfaYcSQzkX5!1A3U{8J z@3}V=5^LgniZLqIGVT`m3Yv0rmNmq%AMQWhtHgEyUbGNZ?T=i?hi1J`IsA9k+9}qx zSzYj57Kjt`FH^D&!}n0SHLKxfQ-Pcky$;{Pq&B&m(ZeaJd@S!Tn@s8kF{PyBW!Ein z%hG17xMXG~E0d)mlypDDa53MOblMlQP2@t2Gp6i9DZ>$QWzN(~M)qQoYFd2n@{DpO z>mLijM9sbI7-B^lx+C+q8QHA;CF|{aL{0U!_18zBw`4 zQ`yTEeOfph_hB}B4JZN#(U~cwbHa}rp(c%Y`lPVsi5x%H4|^uXLPH5Ru+v&eFM(jx z?9+^R~gQe|b`_A^&uKQHDhS z8vDU+t&o;*>SN$L)BM-Ns%f{sVcXzT0V|@G`ZpNEYmB-AT(Qj2lV$@eS+UKl0(Rph zS}L&keJhpESgz2P-wQm@Q3s0$P`oc{YvU?BjV$)YXorRfyp{}7h}7Po-8(~FL!4w8 zT(L~zWt^sV;CKazpYfiD%W+{?jp{JGmHmR$ZIA*Sby4tTK+-j4xHi zFJP-Z<4CQ!#^pq#Vr>i&r6K4=@v;#ECM3(0^vL#}Q5=Q(QtpugLXdnl;w=$0tc>UI zPo#p3RlpMZ3 z(B3RRgOw{eA;~)OYQ-aq5=QggKo!pHdrtNqPoKomYZ@^z3>DDmLmCTSHV6gf_Hs;B zk>v7e1+I3p2CpDItpuUO0NcZzM$Qps9V=ER!5ObfLm>l93zs7=52AC@hOs3DJtIvF zL=uo=njRa5pTz9qcnY(zjZu?`M>A}|?glmd{Gh)f45a1$#rgBr_h2?D9YnKvBu1M; z))$!A4s2=6CjjLXk8s>UX%erSJd`fDS!?s4;9+7B10#SO4R0HrV!dRrTv@|`p9ye@ zIzmZJ4N!vA)i!v8iM3;AHTg=ug1 z$gRc1---3XTDX}4VcUffjg4@ltO<4KgEouAMtt(cYdYj#I}ySDRCoS_gIMz)f`8Zv z)2?f#NUh>uJ|rg3F2eh2K?mugv*t{$JE6cfNz-vND(ViS6eyAHO;zBKRg56&CD+HS z@Fybb#s8=rSK}9sIHXgCeWkEKFl`;9aB5A+l|>Si31eHVD-KOl$7vA6Y`Id9Mr}N7 zFUQc=rt*RI=k_ftM!z#mv^r~xk9EzFaCXR_Z4tyz8YvnP4&ALr-nG7~0>hoFuzyIibt z5fnz3SuJmbkhS~%Boaad>}b|oOH&_6Ax|W;8>AaRiRtfnK0R$FMqc29c$l#|@>mn$ zb$vsLEyDnRWM-B%<>LG~yM;2N$7?sRuKFW-r?FO@D(Pp${N$nRvLow4-FoRJT=E$q zxrq>hIpgwE9A_|Q3#7_ub8jaW%7!U7msBgOubFy%PF8Pwr^Z|5e4ymMkrd6ayccYP zN$YmZLAf|S+(Or@*PczLBTrPCEp9r*CgUF#Nm^A|k<}*VZVq)e*A0RLP!VQPhn&){ zxrHK4_03He&JgloQt(i!^xS<{czt0w>goe1829yqcLd2RfkcecnD*$@n4jE7HaC&x z{Xw@Ms3J%bHlkA>L?azH$s*tC0a-GEcvcu{IUO_Z*uEqy6n1*95`EUzye)aN&AjZ| z>U`C(h%0r~5Z*Z=*s}n-L0u)G8cj8*{sI@~X790r>BxfoC^?70>wkRBc*|-fO+d@T zzDK9ge1UTxwV9yOs4*3FUVez{+`zR=xzBzJLL@579BlMP$gZ53x|tRQx#JFqojG&o zI*b}(TYlkS5rDnF07N2yZL!3<`4hL1A|Ad$&T9?4T135Gm|iu+Z}=(RPr_Kkhv7&U z@8Efyc932>OC=y7P$BXlxbWVth55#CX$4$EovVBUMpLgELRDm1puU>2R?TMm#X{co zovE9e6X~h)+C$xDT(oSL(geOV_9Yqrf7p7*C`-a+UAL?*+qP}nwr#t*x@_CFZQHi3 zE_Kktwn{$ke482c$c)%FS!W|fhysjaBvjzM8*TjH308Fzg?ag})4u=Chy~dKDi<@X%f6FIV zk^P+OZ1bFJSaU*1H@8WWp>0(7;IV~GBKkY3Rl2fR&EnApgz;ESb>qB~(WdohlALmi zpYlXiE!62fGZk6da4x`L(7-R{n>tPO-7b_HgpmkwihpqwaWQJdM>K%1UZaJdRlM^= zShf#3=(8 z6@juDa8E*b;OzMuD^P?!DX=**(^_n~9w`;F!*e-7j#APD4yc-HHFZnN%UZXMqIvi< z+3VVW`odMkV$-;5n9QiCLEUUMaRDjw7;M6{D_X_c+oM%L{|b`+x&Vm~O(6@(3Kc73 zZ^QjXH$vxZcb?(SO~?Lslfg9X0FQhX@o1ZP}(R_lRqr7L@a=l3}bfC~UQ; zoI=HTFr#C|f{tPym-jely@J$|_}^Dhkb2RMC7X5_T!i{xcs#>0!2?<`msS|DsA(y` z9p^jisIP#U@ma|pntR0S$mO!urwThZc{{bM5lYUM8V-|-ne^W*;&0}#D3ht}y>Qd# zctl>0a3NVdUajuTq<7VAl*p}=`1!?)8%t6KLXh#`*ja$;M7W8;8i;3rAFLy!jw0L~ za0HD!9-=#60BI9B;H;u}MNwReW;!MONZFj(2HcrMo<^?QH>w~6dnh|2$N|yK@Ipc# zl*_P{ecp6iP~>BJk}IJMRRjgDUkt1UxV!}wRgC~YsRuRC2dc-IAn65IWGN4iHQ_T|PoX2k}qWeY@dkTpTRKyQ)4ruRz z(}KmnZ4e9oDUvC+uo!WBit^@~ME)W?Sp7y>q?n;SU#R_Xc5ZJ%35-}_-9{Lj~9d&Ri_u`Jxo$*f3=QuYKO)0P* znh1!p&@l>$I@hTWr7~;OrLt;VdUd9Iabq!Tke;M>qv^p%>HUggI=`SQb&p)&I}I~W z1pfe$X+}oxf?Zu|PPd}o?2#EYjK*(B?un?o=ne|2(!67dK1AR4 zkxe*jvUZFzE8)xG2~+}Z_e_c&wK-SNBnmaM6GU^K9cm6h!x|_V5$0PxXdycgw`&Rz48ucU)eh|Q@9a?}H;xyl z7*8^_Z6}kVIw$CQi%{P`_3_dO*{$A7$bN#2QmZ_n;x73%ckJ<;M?MK0&s&L@E9@sO ztQO@7E95b@W$QK_O9DIS8`u%F>!ZRNcKS}OebL_oj=Bz$XnACCjo=mvysqXvvs^~% z1is+4&v$uOEX|wR4%eWh!@s==Zi#N&(0_J%fY%6_JHqt@y}x?@dT8x`)*qm541mbw zN>9%$n|s9b_+1lGKCQ*Qa}i9)@Kd6HHCWdl@x$VI$8=|n!-NeAxfF#^7ZUXuQfChM zjkQtM*{30`kF`N>$b_piinRKj8ulKlLF&*4(gwSkUXa{C==rLUH<)y4HF}|*P;G9z zBT;3rAX)Uq_DHqljoqu51<%ItM^o&BBW|$BrX9+Ce{Tg3MLd5?v-C zWjHzoxw5b!qNbV+vG41%S}E@cndl;^CGx0+5MoCyFg_WoGKcov0RiFu9JI z`uVmMqkF19ZBn5~GWl+BwMcQLI-y@UYEHc}k+Q7+<#>6r0}aQHP`!iPdmC5j#bC_K z2)IZD?Q0k>L}WuLa)E;O6`r%7(QyXk)*pf>4b!A86yFSaaBqbcR)JvsWi?+zVdWIv z(6#NU!5mc6=Bn}D3#c?+?@Ba-!FAoRlw%m0g3`D|OJsw8G{djDLZq&{bP|ZrKBrAt zA67Gv+!4r}O<$OZ9GuuW@g}&QZ`8KlHAxShr@trwPkp2>0D;z~KtNsIdqrOIoWS1e z-=R9d*Ph*?jtj;OVDRImD~``;q4RsR!|VO~)iZ4;^CMx&CXQc$++OTvDj@JcAJ1Ey zysbfahPCOJ8U;>u65Q$pgqTm=fA5#3L2iu$X0q&*S$K>@8;0-OA#Tq7++2n$NuY@d zGbzN5T>}OhrSOv5d&oDqUY-`|(!Bwo@)}lCrWp5Mb$a`&lu3{C& zPs=g(qlzxG*n#d_EHJq~%-g{;)O?hV?te#SBACY;Cv!I7rKlzPaXRtZD<=Ngbs|+{ zBT0*v@{F5Q3lm;Tl&mrgtY2&#QRd-U*M$I<+X#*i(leS(TUX!d>w1&aJWgzQ*e2=a zrWI|+6)Ocxo?a!%Dg;a1I@F0ULsD! zMkdk!p++5)Sbz^GM81i7HO8qQiNtM&z#~WT7P3Nt#0fT*F)-_&L30wLokVa*djeRv z)s=?RfvoF=m=^RvYFGU`CIlxnJ)llE`Ss1lr$ge$AclA0DM0-QvXEj8Y&|$d8MHel zQnkY*TIUH>RInFV?{};%C{tQ=ure13jhRH+992%-WO}K#Yhb~irF8CY9HcjS?tC^R zdP|3G$a_ZdaW5kMEw-6E}em+rDQs;4>t3fD!7N;jP<@Zx!4_OE&Yu+ zjUf(B67%VE`;Dt|O!%Vs%W|y8flh^dJsRy@P|Jy{ILYmq!zk6Szf?VT()f&(FW46f z>vX%4l;-#Yf2sC-AHQl6OgfP>fxhb=o8obH&;}%V)f!U6WM74XQh0gnzH)Gofl#*< zad1?jDr5wuR=AKyn}LnV$trL)#Q$1=OO^=yhlT~MC2i|$BozO>h%_vOtWTXO@%EdQ z06fD@OCK>hU`|J0Nlg;s>34VVrIVWzY`^k2uqk3I*C}r?Sk}?WrNdofnLl@L+ZTwB zXKH9TkKnK3P&0}qU-6&>_qg2W1-i}**M>K4mrF7u2Yto)EDX2Kf;hlcLv6<=zbSk0 z@akALS@0JiFQI`Sxdvmu#Ms*!3%0VuOC61VwRKa(w2Ca6QUw`0Nr<;n^G?Gt+j0)( ztj<0X!!}ofp^LDD(9&J&+ElHLYi_iz^K}XLW_p4wSMNB`Bx*Q?3S)Nk9FAEyGkCr>SSZb$ zCj;mU37ezXIjknKCKmKQYPeY~W>Q*MgbYn_qgQqa58fxUu0mehFD<59th~93TMKC> zb94y)Bs}j>Z0QY*O~zMQ!dEq;&%l0*@zFliciCl~sykF`-jKpOR8kecW z8tiBz8d@5i0c~LdVz&T%g4%t+<^K%&O5C{s7<_O36IlE#zp#Qpe;b(d8T%^3{}DxP zVC3?DA3kp!!|gyu;eJgH{X(be3tXX0c&ZiesZzSJUhcwXlQ!+iQEa&m#wJrHk~T&D zEl8fvb@c{d`+VYX5mD4$cQx^6O!ciE_*x2_pREK!D+{c}hGFThQbR{AWynsb>;)PS zy?48AJnT{~+1$ZXuccA5w{dCe8j-elk*>tn$5+q3kbQ0gwiOLJfTBO`@c#zhS7z_` z?~q)BpwOJd$GM8-e6uh`peU8IPyHw``VZRRz#cXT8Wf6JIdz5AGzEwo4Ma{(I%teb z?2olHY&fkZ3hajp)RyPRIDuatbA)<9s?C}un;q7hmB-g=mzQ=PJH5)ZL+6ZxcIuW? zqG!(G^t#C$5aY67_oKJx-yMjHJQ4|l^|92uf2gGpbx5kxj?iPRJ>;DwOJv7ba5K&BjdPw0%Oi;}j!T!V-K5M&zOUDSa>eQEu(Lh|59adPd9NgJL3Fn7+ z^5SgVda@^z3C&=>_oaM+;F?40Q~owx?CLJuhP^+W_p?Q&nF^x*>oNGn!Zh=#MZBjZbI(Ha#3(>R&NUaoS)cU}I@FL*#AvjW5Z z2Od5uEu{%%2-PK$g#O31aT%C44!bZ28d92MZF*Mg45@9`{iVic!N|Se+kb-pkr7`s zc6RFbd+K(L0_TJy7mFeuJhLJk8AloJJ8B4O4;-@Tmy-~Wcx0mv<_bYfYWQH0B%#12 zoi6Wd0}Y?mvg~*mh3|LzGo3e#5EH6x!iJ?a^& zZSC-0jIP0GE?%Z3~@&elGuhty4hpmw>DP>9w>ELFdP8cop8-t zs*vVh4js6B!u_e$Xi{6O7!}U$#R68lx)JImH-pa`1)U90=Nv~q|72Dh{UO&>pwW{i zP^0ogKyKJkM>xX}fDGR&1;1tGWy44QO4$Rh6{ETU>Qj8fv-I$2bS=;+ppTc4*5V!* zJ4K>DdT?ZNnPkhUJQ*4Zmmjg%H!gi=-`(4d$3anxq922p_-&Yr&m=DYS2&(&AhI`8 zFElBC_c+347WN)B&g#AGiVkX(YmSRyh)QN_?3~@1sAuIzP1vwr?jwe?fR?`Y*3DNb+=T_RfOVO@@ji}r2yzX}jN3=nueqIm`U#8W@lByS(!?LS@AzSpTsTHEjM8(uHK`B+~2 z>&}`1<6gVZ>uy=z{m^L-1HY@$gY~LEcVKU9J_ApnzS;Kg=f7SnPM>aIK6pX~M`HhA3~M!SUB zhq9IA@YDSR3TKBdV^tF;XO}NgC5u4?$c78JPpw5D(#BvH;r$G;g;Y?7T5XQ=Kfk=W zh@AoPuqg*7(qmf&vY?R%VvC0~-OpbUyoMMAxZfes*U2g@NFcLT3l&iaU~mXGkSANs?4L+}sMYkFL)ZyHVKRO=c%3ldW3QjXJjT z&Xu2zf}!=Eq2(v8UZyh9C&m@458`wVORwr}SM_EWlMnWRXxK#)jqXDbsCf>}AojSp zD8;l&VQIPGzG?JSZ^X(?{_9uC_@vlh5Vp@R2DDVo5!G~oiUub#YzX@eyfaW>FydS~ z;{x^@%WrFIDfBRRB;AlAe+cP-;3f(msH2g@L+syoMM7LB~H+8$1#ol$Dx+``$Ohgf6jF+kDL$+KEz2Z)2gwH)-fo#r03q&8CbO~ z^yk&ByKZf>`~>*5{r{QJyVmpBPldP$q3;{;ZE|$PZ`(g69w*>*cMRv^@_EJMz~>QD z0P%Uc_gW;;r?lelF@G@;4vg#xT#*`#4WAzhN{It6=tk)4E(w&<55|To2nTt~ICfA4 zWC!(hK7L}}&zE3C)6L+6s9 zoWFVDN7i#f;_G{~(nU16_aRbYd`TX-tI-@_Z_L2Y#;d5G1y(sy^}Ygx2_F9K?Py6& zP(+s-uDN$dFe?nM`5KbvsLu{}l&O6>-NGpuj-j8wW8ESSO#$4Z?E{>3FH6lvdt?}m-cyBfF=S3k3++a z-E+^8NApy#gCwiqpx`IOd^PdR^uxNtjif3Ei=<{=5&L&9nNG;?hmP&c{zw63LLhsf zKMw68@kMfTvu$A#KL}wNuG^hQhHqtRQ9CnEH7tX+XoRS%&Bjz=Yh?TY{C97MDKn;q zc~Z(bDwSN0wtdP(jWAVjWZ$z=m!jI_-)X|>ghXXxJgNzltMtSlcMxK+^-X($PE~ff z^^`nrCyrWo!g1#T4g9;lOhQVvEx$KCDnA|3{=Cq?w*3`(WuK% z%e)_1UX8gww0ETgwejQ28Z`!0;_@*mOS526m!1QRZO1J1u*We6YVPijRTPmu2$h=E zjWVM@b~p48sc>Vs)}>6_#}wFcMgEpbcOI*T z|Ai`*ApR-j?SSVgb_GACg71qANE&&a15BXL$8_(=-3a=>oLLK2dbkLQ<05K_g9JS` zQ$<2GL=W(I;7TaCXl;D(ZXT9KIHZ?w4Dvo2 z0?=L>gs`9}Q^6sm%m*Wgk}~5Y4bc>)${!6RS|x%QY?>K3eWMcV&ViVdn|o=scGga2>1jPv>^-dt-L%Z{cYQ00Gn}P8aW}sez}W3l`7@P_ zaSAX}&8ms3Tf{ldqG;6+mvZ@;x}h0ot`u|5;T`COsjn5Rq`=jr&d}r{YU}B1-$jP0 z^KdWzv~uQnB@jn0Z=8Q{_?y6*IV{)P;8{&DU=6qSHOhBq$*61Yygsj1tXb>46^WPd zrojTMyjKwQ#W(SUqippaiWYtti)54Ev3vjkqTiPUonlW2wgzq#bC(*KwR}oN@~E+j z8A1uul%>uhy8`)ERBOr(+O(}rn$a3`OdG1WfpnMrzL8Y=pNQq*O($se3_~F041);J zj9pl{`e|gnfJby|W_?InPFL9r+mBJBs0_N{Fq+ZR8XY~r^WG11obbj|+ zORvmx1?y>95E*e2xx7A(`LTojW>k6SRB{~`qV|qWFQqnDNDjvN#Q3t)+cd&=*zilH z!{-I~vvJ;9%Jcl;k1N|R)Sj@}tMR4!gVkiq zqvr|NA@q5`-@Bd|oW?oY;ut3$|D4dLMDtE|R&Q(AyT;WuxsPhBIbeBzuDVMnlHZYL zN#o_B87#V?*3|Mu;4Wd2>8`Gmc<27;Z&stdHBMm|aRPW{!oh^(7-V7|{ytxUsZ{wY&MJD75m_f36kwewQXD?qFU#L_iw?hcSa zv}zGbN#uXmi*+e%pMoCMOSQ1xe48;3IUGsD{}c2lXf;#+(yowmLE{GxmiUDDMz_1K zPw0?4cTiz&hu%Ka4l$HQ%Z6rSFoK_^T`I}%hssU^!h_Hgvao@36^D1H^IMwCjDBD5 z3M*1Qlmot+%>Wp!*C`)K@4zrxBqypwZ)N*ykZefWMwUkw3^lg}iGsf@8Epk;nKh~iMWwV%?!7eRd(Zsj5Elm4VS)QAh>*5$~u~SX9 zJAUYFjRQ%=#VXzCqB)dtCyB_eeGYe75U|z)&qC6)bxPY-lSslb+O*oVW(Uzp7 z>BkC6Oi*bJL7W>w$$p@3a}E+`ViAT?YDUv;|x>tiF4e z<))hz>(!oJSXgkMa|kQ82Je!dNQ9)prdBgieztBg$TWZCL|_P&yu=K2Rr58@I{XdT zQ?kGH5Kd?1n&^e&q`ecQJx9lFkkmzbrr*!V_d#l=SSAY2M$8w9L}izf8biY|xLEc4 zxP#HiF5bn7y7W1x7Kp&`Kp*@SzZd9PFkMK$hCXi8?-=UXQJosxBmYIhrJVjlcZU-&kQDp!cIJwXyHY2iRJ4WA#z?or6&n3{cshGYlo+kU`cm@3-O171k^6g+;!b+7_ zu6|yz1#vIGl)-G$W46U#u1xrc_X1u-$a)Z^(LUP``aoF1L7I@B@LGeIg;_XZrm5D{ z6W(s&R3jr9=MnT<{RtXRQSsw6mmk$5#pH^~g*Sg5b5B{C&sHT&q#Y5BmNp9Ic_a{& z{VWTQ{^dlQO(iA$bdSPu)O}8a`GFEgg{d=JY8Weo(?6>tuu;H zackxc)ZF6Kc8jcQf^p1S8WjnVO`+=sVRD~@aZ$R0!e6h^Ty-&1?yAUe(N7;`f7<1F z<`&kung=W3I9y(_w8|aTWcoVt+^KW(*@cQ{*4f8}|0$@Xo<_u5Q@cra{Q&0cBwD>J zqWFS=7r}wYMc0fK3x5U24Ya<&Iy=0si6x4~FIB8`PZK}J8@0vfFOr49L~Pe7{tHKD zL^J8&5=~xZo|9JT(QYG3(@iNWUFIfij!A<5f}+X9!blv1k9(Mi?(B=I@9Fw*;`O%)Fp?i_gblJj4^P76qN7fHH+YO!eS)k!! zJQ|}}w9w1Ik;g8Xd_$0RQ=PjHFJo}33SE0~e2n{H19YyWGTzjXu!QGW2JOG;gzNwq`hcR?>co`3!FGy*D4ksyM4G z!72xu`&TGOuOau4+XQUKJB)Dwa{E3CH~H+ZB4ebaA1C-R2pe=&byWooLgh@I!Ggi(d=QFeStog;D(Fl&fL?`K*&4b%dCIDsLm6 zk<$4LG4s0Wc#v#OsrBybJ^I{sfY|QxKP5|Y&f(HRNrYCAAUrVNmS-M4G|w=qY&A#> zN*Qj{#y;5!(?giBw&LAI0Dnt)bmxATM3}RG?v1Nq1g|Q4KR- zK5x#}ah0R8DdAk0n~jKpj6w;z%3hzGg{DWr7>_V=m%lPU8#X`4PLDu1Qf@3N=lj~l z#9?}`KPy8%;~Ca#@%Cw_n|(+2$mnt4Qo%T?;O{i9t);Eec) zks`^=zQMn7qX}RKLN3$4nq*-M5G`z6=AE0?UrNc9E&GUV!7^Yyxg{}=_!LIJb8V#Z z!@cF}i`c zBzmTSQxUEqC_<9h<|U7=9T z`}S%Qr~kPGQ2p4y{?QJ5zKqt;>^(SpoLx1=b$`+I^}g{qe2s_sT`r{2^B(;^b9}k` zQ+;D_|LDE@U-j_wU^|z65knm7*?Rr6V$}xrUKZfR%GFe77yrU_e2?BwokPKG4%6~4 znopsDFLq7d`BztH8$4QFSFXw4%F8^3&aoHvQv-w3)jEnzJAb|0)&K!s!r9la{ZLjt zSzy+BUkwl1A?{s~RJ;GcboF5nb50Ylm|q!LUOf69KzO5Y(q0N;wdcoD1`aH{bRZxI~-C@P&a_(#Vej_jYc^_gS zI|K-_n$BT3;wZEX={0B41yKoKCE5hz^K;0L;~Rp12DhSM(bzG5YCXR2{!eD0!8)(Y zr|q1&fWuT`@w_p#bq-DT)ey0>97!X{O@GI~^~o)EQ^4|OOs5;|@kUmg9=iFH52!QO ziW0avMZ}6m0B??xv2&8Ko1F>iqIe>Sd?Sf_2i*rQAN_0gJ=$MUWh&*FRZA=QgSAtLIOlH?3f<&v;riG!V6jXZn zXZwpnLvna6_ecuYkXf+Mm-KPEwEeq`?$s7vghxn{NWRv3o7)D3i_DD@UihweoF&`5 z3f?F9rUj^<-A~6fN@qY}Ye0d=mrQ110sRZcH~pi~VH6Wwm=JCiz&3#Z9`QCg~$>NqiAi)110IO3d_gEVHPqv;)PJ2E+YK`#f z%KC?5d(?I=%z=f#h5)#O@?N>+(eK@_COF4_sCikeD_9IQtqH>r$de`r0;fFB{8~zF zq}TWDaI+&6DlFIdbj7t#zj7gaI<{ zw6z`ZC!s9+Pr|lxM%LDGS=2Olq3fwrJ=VirNSc)^*DEmua=iYL8kU&Q^Yz@kN&Zg) z;-p&dL@N4;j4k=||D`~#N=6P_Q?(|WD!Wqa{sv=WwR18TQO{*CB;H9u-N;c?_ZM7{ zPZ=$>pJgw;na*x*nu4zP$bhs zxEs9{q^G~%Cpd4Kq2FH9IW<Ozg5j;MqC*os(bvAySsk(7`aQP6j%1|c&qA$03 zjQNj#<$>kwQMR*3r2UwU$Ed6Qf*wAQepVi0fv1M%%9|YapJ$kVV5og=j2ixv5kRFq zGa*(tazFq-GjI@sMnwMe zdG~+lv*uD3x(`0N4?!&d_QGHPgRdP8e|9k4AYS}pR=jR>vym43TD0>&rmk)uy3h9| z)(Br>Q9{Bm&a00m#wR6yH>+f%Gv9zGn6JwL(jsS;91b@tOw{df9Ng#^dXO0iwKr!U zHhH5oeuRR6iBg^1T|=zlBi3vqsRM=Hw~(Pn_(M8O*HY#_@Ddvv|0;A zo8+lA6XtmZ5~P^|izhoUfs>-^`nV9Ts265}^6yXN-M9c(& zP_i;ZL~3_w+*rHUxaQ6(pg{Am=Aj~E3UIV*b$jM_?Xn%Khw)nhIYN@2Kvdv+Tqm*t zfU01q6|0;{LZ))N=^_G;6G5oCh=Nhc~HalAytOnV>dpykVLnI*ayf^$=B^=XIaPALz&7PpwC z>GKynYlV?v&@3&gS~3{eYOXwdjyT z6wxymj^)z;$=|1dctgZLVF~USi1>%ofKO2l%JyMaTNdma(m9XM57Xgb9M2o%jL{bC zp^^I6PLu)&1)86l>a^n))&=}U9;nk=xh`e8DYa&p#ovSgE4*`(EiMf))aOrt%jZ*^ zO<9e!e3o>b5mgRn$afSB)Xe9%jQgHk$w%Q}T2)Wlrgug3OX9A8sjp zUx}Z)>CxXew165R1~?jY6}dh^$OHOsHN2I8b2Vqbdg2-TiRY8oR%j2Ttr1?HqM($@ z3y?*kog!D}$tFoRE(j2r{qRZyF`+9bw-R`#SmdH|t-*BJT_etgKdDvSKKl#ef}LnU zVh%F%!JX_F9048{D5wp$<2{imdUKu_W5pR8;i4>x75+w)O+LRZ_CbltTBxc;q8>8g z4rYKg(OK;**yRQEhHU0JZkt4tuo^t``bq-AV#pT&U>pf&ezD4=PnEGe4S!7e-SSFC z!lsXjs4^-l;}3=sX0Q73#7PKmggW8Q8#V5b0YquPaY|bKVezX{{BPtX695}IPXIhn zA)3{}aWQCO>EUYkiZr8!oCF3ur?5ChoDr#{U0qhzJiN9-nwRTDU56m#d2nuhf`(6eSY$ z^#xJyL>FoAw1^#b)bdBs#2>&-nJhH&VBw@fU0flZrXP2jj@4UPn< zG=faHvH>?c5ccbst;BZV7Z4O}F-UViKZBXKq6UXW(#CnSM09~uBY-0Y*aHKuxo8#8 z3=!*+0{nMmJK$2T91*#A^suo-B}Gp(lh6=_H^I$#jMl@SIY!zGJbt`AuOL+4g865u z%2PZM^K-U%5BX#2FC-@|#7TOCP)OtkthQ75ISdexw&_RW)-|AH(j7^Yy`i&_U{G$vr3_d6Tra` zfWmG21N2Ss6Hr9(u|7H0hXzm)czU?ydjTx&#Q8o7ysinfTpnqCdOv^J{7m28nSK1N zx#8~Zb-FYDbNOgtxiTgi zks&a)PU2aucDpRrD;Ub8y4v8t z_^+LG42Vz`My}BPA1X26n8Tak>lW~C(f0D)!c0O9+f;CSY-SJ zBwu41zFm{S!33=nK&`JI(H6Gs{1f5(O6 z&q0Z>7zzf$QY5IqC;Bz^!<&$aYQ(@))v@zTL(3OQs7I8vnq&5#g4-Mh`=x>%?1q;M z;KKNPJge(E*AqkYS$U86h8Fkt=_Ob3$2zF2oK_ZNGV|}%L0h~m2+kuNj%j)`iafgl zI^QDCheAP74~>=;g;ijDbV`;rio3f&?7cecq#*dNFLGe^+Cj zzC@3gH8UY5;Hwu*6~=@ezl1tiOhaT--~R0rj61{@_Ay z8AUVGwK7q%{+RGC^ZNLT`tQW73w(fLn)P2b7r1*XUpnN*XH^P8Mr@oC`LI6Ey0~3( z>4Dm9|D9ddx_F(ciIU)RN!9DOqUkZQ|d$Jkg*(7Z3Wc2Gg zrvN5;5-lnc|1=>_g4}jV`$#Tsui+`;m31Cr zf#wvd@e;g2+2T)h9ErfsqmSS~Udx2gokB~3IdNEk#YpzzW4trK(x%hI-}eIg+2wMo zySx}a5rp{4O5{EJr2pB;KO^7cr0vrUz_~Yuxkg?8=?S8-1Z{C}#H6(9X}NZBc{cJR zKF*1-hR+e}c_DzIywW_?F!$iJa8T3n2&6Y}0AKjsN@cow;QPi|uHBRBera5svsC)# z1hIC!u0%!?2JV>U*3g0BIfuT&GDjzj(x_3^uVK#r3t&){xHqq(Jr-cEeXZhkuI;_c;%iO?o9aZfW-OTOWG)q#J7`>#mnwuh?=ODjDm zzFQv&no9gS32)SgJjle`_Cg|;c|zUnH1Q~p`a@7!BX&HSX4N@pXZ3d0PiiP97*GOt zqj)cRM0ZMzgw==NCN=w|>nmz4HIzqq#@~bFL&`{iOw3<_pQS0Al(OJ4FnQ`x3wx>5 z9EtY{xc1RYA{}#D#}f@LcOpE-;2Bh~g$>-++nv68oW7!{H_QeO=LH6%+)hCB0^|5? zZtae}wZ!-LUq?^KNIfCXvE#V!ZIW;@fmxp-e3-BQ(>|o15)qfF4ngW5JJkd`omT26 zk(O_Lj@C-vlHS)2OOJPpM_b$0l3L2o&p<8X+z3m{PqAef5(kpreWqz5UotXvs1sS9 z%&U9J?jm~(BWntO<wX~If^Q(VZK0h&#zb2==W)Qz9PdQsKrg1u@oum9gbo>T(Mz$EIGEH^rY;FyE3 zzKh%BPTL3@v%ZYV0)z9Ztbmjx(}aSQ%eakllKMESg2cxigY%&YJ;uI?Zyj}}R+q9i zQD6sr)oBoaXGcB(E12uQiw7ZsUETRCVaA}WF1e1XImjHItkb-~)vEWPcC4PM4KQ&}L;_*4%tf)a`Y+yreOcwOi_U9MNK2 zSIrp+lGG#R%-e_zUXO(qAc;9h(zchsXT7xxZ_fQTR8rY7Ux4-*357y;k*@9xv2e7!5}?#1-C;&P1q|@jNtu%i3_HtF=O&zU1U$ z2!ZVoLPv>RI4X)}tvQe`blN&pOl52ThMgu~hLl2dO?>0 zvk~R|Ow1|+>;$t{VoQ0wlp0?z5O#>UyGTEFqk}SH=un*cY|{IU@?QatbL(zBy)%v4 z3A=eZ1Uur2ebUNtjcOP8sFN>vW_>MvdnnvozD#d3boX)gU93yD->Uy%D>Ply?iGYD zRAptM!fTkGjpsfOf@i4bqLF%2)l{vVp0?q&+w_%S%k5^>B)J^J*W@nuEpHx>{^_^^ z&log+WpRN%rQsbuB(6Ko*}EF__96z}wj* zJHnS=Yc`LeR>J6yr&LPIg9?IdI1y|2gTf)*hA8M;Mxt$EvKJ5FzPWm0W4Jq6$u>l{ zn)C5JDR%dh(UZhrv^x(xjUAJ?oW0e*Br^Ket%Y zZvqtP@FovPsaOhj25PPR3|^38;nvo zw+4@{X)Wr&n97t`@RtFHkqeUfcd_KOjV22SV5!=Xm8Z)Eis-0HQ(PAC=R|rE@S8RwqYum}WD@*^cOYLq~PFrq?UN(PHQ zbd?6gsXPvWHLO}1Vgm>z^TZ7;Z~HBoOIN?W9jKm&hINnQ(iVhFB@Ck7xP?+Zs!O=q zZe;X!HhGcod+r8rmE>S-CC8!Vxba7Rs^g6&RrKabkerdJV0FEGm!uNmufpV!sX%ot z?4~~%^NT4=G}I{Cjhg4HmoD~l;I);mrqVgn-6bR(IutApWRuC<8D2KgrOOkrsI6dA zfUVmbjbO`8vBGol$b!ldM<=CZoWyhJ?*n1tN_EJA@!HW}2fBHFlOogvfv_N)al8s> z{cp!!)&)NxEM767vy?L`5Kix6+)MkRkjkbi91gbln^Lgqg1zZrG-LlAzt@W($Sqh- z)rS0Ba6zOUWxJ<|*2qJCL51=bY?kBxIh1_!C*u&ZhSAIcYY6$%$c`H8@j9&b=;QN5 z*=<3kYJ*Zqa=L=UA4Wj|BttK z{_>>vqJ=+g+qP{?Thq49>F#OUwryL}wr$(CZQXvp?|c7+o3*M|DnC_{N@ZuCbN1Pq z+USBGe*p98@5CFVn~REZ^Gvxg!=|Gww+;B966S1MFFA%W8SVVLrv}4YpU6ki(50BWQu-XP5yYN3aD*E}XB18vnILJ~ZgXI3tO4S(R|Vvuh09d3}6}KF6@p_zacMUra@Ww%%~(@9RZ|^ z0mXT|N1Hxtq0F6_LPX4!=#og6xLmuo0jX;oGy`c)^aXq!>NCmH2T&v*3!qBlZow6n zEs8EIrdsg3IEoj>Wac0E#n8SA&>1;(w1aiD!yhD#TD{FsYsA?L%7 ze``^r#HV)ox`KA|2{xwW!>(Wkao|`nCXRbERq_egIBm*@!xTIoBSvoGaUeyuTZpX- z6Tt;?pGOWGK{Di}-A9XKF-G2X7lY?GzzUnPWz5b4H$^LeGA@3SUc|q3Olk()9 z*CY6>Jh$<>*YmvgU}&S#_}a$dzke~xas9gMCup_d_dXtE(Br1k=(>H%>3)v^q0+dK z3anMbr&0g2uxIC+BvzEi{7hR?WPT_iy>HPPty>XUMi$K_R`URvTs@uROg`MXm?>e3 z%h*nzssmadp|6@2O#XD>|Esd4nV>3J>fe>WAHa;#mq@^-QSqPVaxxrZWc4jQ3kz{( zdz*K5dXWF9o@?}aNECE{Z&-ShHi)BtLm?-al%CJ^Bn%+z`1*Z&-oai2V}^iqVpU)u4KE zBx8+U96Q&<HDS;RBkh{#Q?62W+DV<*5lGE5FGyGO#H9_IGaPP zi^dqc3WOWzUW7g)O4Ng$19+Rg6$kF0KG2oyYRY-Vnx@^p=spB@O{FJyMNW%MlyuSC zeZFA&PgHZ%1oUo?5Kb&l;>Nqz6 zNd_nu)neK{(MK2ei8?VA?x$EFnzaGg;V=`}E%F!91N1y(lmu~$xjOD%OmMhMoDHN1 z8yjA}M7$e;oqKQ_)ks`6Ku>OSLBl{dEkS6Rg#zzdQ{W%^{sN+BZW%9SQGN%(*~4p4 z2zxeKgnKu9f4JcM8gMaf541rH@F&AC7{MXG^}({_G%^?62U3c>#W#u&MAeqsc+Nnr zlzS@cs(18Ea_l5=ILXNHeu+e6AL5R{hiQoJ@=pAfpi&Nd2hQ@`(h4@cm)JdROVg+g zKQ1U|rdcAtS0dXtO5my}mrha_RV}@1Eu)rzR^j7VoRotW1zKq-dBdTD`PpeYou(b1 z0+qEls!h@*)k(fE%gl@J$T}q@Xp%TiRor!lwI!+*=Ohnh+>?@3sa?{`)!k{cMWtkk zcIgT`$Lxs}&G#xGR0V{cCbNSJN$h)pqZ}isTDBhJbwH6*X^2oF{aYt_wDryZS+@DJ zHYy7*Rr{S5)`8!#(xxd}dGF&v)a(+%H>(&WrGS*LIjrfQQSjTKaKNBqw;n_7R-WXC zjTRgj7v4Hxa&6%!8$fnivya=el1X`bN>o*LWT<{d)_q@;^-hsBPbqYMqGr&Hy${=BL#|l5O+2D*9*2NuF zkesXSHJutr}ZvL^Z%uX85VvpN7GGqrEu5Nd?jycJ%`wBBGueCo%zPWP>@zot7x zwTy;LFy=IZz&#;F61}opMA#e3sta7&A>4`$DE5*huuz!H7 zz)7a7S}OR9mtQuHNtoKZD77@TcK-0fPOTpFl^FVrXi;1Hf>VjONOnL>B0mMVHMVYM zd`-DuG4H&QR#_n!1T9j!;DLzUc{iClFfnp7orvt9B(%_wG~^Cr#$ zILxf0@nST#vdlzH%*#|0<=;Rw8LHz*#!iKc)HbW+g{k3%l3esg&*NBgf9PmXp&{SZ(m3c*mYP!>W5X8waJzH|0 z(U^sIHl6E;oZ7BIBPyKp-j07E0YnO<5fuLwCyT+01F*`9_X?k>~G#k zb>isY!ByHSr+#W*q{b=O{M!pd2i2AN$1exs!SusEqlq1#Af3IRdyjM9Zg0;o`jGcF zBtkTkzwmkFG9-FkvhTzya>=4mM0wL)S*OAYCVyL#4!dZH8sDR7iPc6#qavS^i90T8 z*g@yBmBQzJDyzMF%0;98aM>!&*fvuMdvb;S?%S_HrO7_<;x z-sOFavC%w3EsKyVmY(FnX8ydPBuqE8#hE8Xk4&KR%dw1#TWUN3ggm_2x;di&YLKy8 zf~?*RwOp)qe$%hgHm`yuk-jUmr-JT^Ou)hrIi807!6~E`KKxLmG;JXSzl@+NuFc<= zFI)PSe@tR@qwW--VdY@^|sINP}qenvT z{iuZ=Pc9z^={(9ue^8;Pycb&DCmQIY8uEHhmfBqpX0K;V>xwPE3=j#&bVm>sxO|If z|HsdAj>17d>yGPagiQ?i%K~gH&KOHUBJkJW&l3ws5gGPi5;8VkpU|VHQ@ABchEZNa z$QP~z1Tw_d<4?+8iD!bYw;e%Y;Bh>;GEux2EonGJ3=cW_&{afMHpVYL530#OqB}|@ z10`%HxiQ0l1(w~9vA0iVF)xghm0P=(&4>F&_!m)U=WwUE^~)<*5)5qe`tf-z6M3^l z;;w=hbCX=_F#EH5JZjEAV?v8naz)PS{N}%?A@QGbVE|5T>O_XRkuL9eP~TT>_7o4c z5F8=x2|_ppZu&z<9y?4SmQY|P4g1Bo<2hF5z;Z((%wgbvcTw_nq?FbDgV)f-F5Gidi964$vh;FJeH(jQHvQ@~f99mazh=%K*XF+XE`SCbrd{oV>^hmlFUA9Q zPjbx?C4#?sR!*KhLLWWG9zK&y7ywzOpB^F42lXW8YR(;vil@9Jx?Ri5v znmjy~eMysNkUQ5oqy6Ez72w7Q+-|PdH8eS+`rqG2wJ}B}+=qH(hXLBirl*BR{a(_e zhPLw9N1A5olcXW36+C%?+N^m4?)`0vpNWpht>^Q<={f5SPHePw4t!mf?-|L+`UPtf zVIh)Ke#tQqjQyn3eIUL!Pyh?T>5k9L>cuN2$vgRT=4G-tgA!|$u`FuHaxWAMhtC%K zLhi4IA*_8Xq!9`=09<>MzT_#Rk9=W9efM=kr3e2 zHTOzj8$d_K;v`komB`yfsPcfu4FV;hGOsdfML$|JVg4Vb` zPja-?XKl@|_m9%OQhLKAacG_h&$ zot5ztoG-))`&az_^h9xPH_vvK8$K|Di$bt+VNXvv(`EtGe#IyEbz>qIEZ=*O%lrzP@8&jF%=}^t z{+WpI`EJmg%nfs%eh@14J@UwlPMgxU>PeML3P=Q9_x$kIxB`J5Gex^F)DWq=pd09C z@3mRH<*3ZlgXrZsf>dRopBHDjDVKRk=OJEi*k*})zz+(8 zaY9^PHpiMntK^q1n`2jZdF0|o*)^VJ^7~hWjBarwS2at}9+fo<%J2u+_4HZFS;@uf zY}U204Y#ykBUOfb1FYPZEcsbN*ZrakeZYh}<(z#>ElZO3{ar&fktJnA zfv*)u8LcX@h*06PzN{N4r>!o&pnm#(fh;_moYN$%F7nuH&qNBD3tAhGENm?2lr-@; z4D`M>Y-SspOH@e;+0JM?njQ5WJjIV9>)lOA?ep{;Q=@HY90TTY2(l1#Vj`v;+jy zQa;ePW|t{NfV=fra}r*fhEf$Fh+6wiGGBUvNTm?Z9F*;T?#w1DJw&wZRbHN@s2U=u z6xksO&3rgM1I;{abtb(A1$%t?XEF5kMy*4&Rs-L#YI_%r6dq7nBoP-_sE#W5s4Q^| zV=1t9OV$d-owk$6<&9W&K5|whRII0~%8^T9CPT!U&z$qbgeU{<@C%rFu~ zpt%M-tMg!k^)rlMbiA4)4^%hIQ8@6Jx2@`L~u^o=$bM4B9pX$u`2cSVn-E?qEK@y4h?t3!Sb= z3HWJV?{n?Mt>!!Dz3VCKy8Dao3xgYT>MSofuj zpUwM?;eRyvlTlW+_ghg4J^%Y_yWXpx4Z+7(%vZ-(#PsLCgW{1S5vZ~=De)vMr~Sjc zJt@$|0x>00Wt+J9k`zKfl!LW1vdlh*cu@#FIk)x2P?Fbb zVYg~Lw$(Kk0LX#1i-v+RZFWcZ)@{vac!lAw+>IYI*0$HfZ@up&j_%Ll@0$$+8lOuw za-ZYZ7_Yt8!5H4H?e0v^qehF6O3&k;Y!Am@@-=9^s-RkBp_K>CN%sy}+Eix_cX${J z^Z`{7&i3p>0cB79f4rRuNfIR6trq1WfK9=Z7VwLz!nh$ujvhv8#e+6U+=fbf;^IwP zc<57bKha&L4lqeaqY?^mws#W*qH=>jus`0P!?cMWWy#d~!?y|5<{E4Bu?pyMzji6aCMl&hr%e!G( z)EqM|L7~ZrIed{*0TK91D^+g7YbK+hW&_a>G*iaK={4Wx6!cKiCvDN@#poz+=i>WG z8P>t}F!~hicO9GQc`gf#T zrysMj;BCI5%>1sg+tI@e9t4{zqcGzqA!ND8qJi3}cj*3gfLZ(YXZgEPw?`d@JdJa^ zM-ae|%8izHrI$i4yIgJq9mT&7`r^RhEkR;t0sP{huc*bt)^Dx4B&JlkgHsKUon%PM zn5YtU;8^TkqrZ@jyR%YL(y9co7n=f75JKzf&TP!} z?ZwP0YB#`@x|lCBBFEwvB}V;CQKD}!vscv%WxV`J#u~+ZSw+lQivRF-Y_ee>eVA0) z*VwAmNR)%@Hl^m`Iw0^)yUGmB!oXaw{B`rEgxIhTksRRzk=314mct~}c$@_H(P5L& z3J(>>!Bofo=H08(t}rzPn%B_pK)lSyNRJlmI*9e-Q{_xw2BDMgRb_IYHYNekLJi|T zk7!HF15~Tl@PTINH(feZuAbq0IH7$A;=o{m#54VZSAwvpXx4j9Cm`avc`YEy*-C`4 ziIEXVLAP8U-Qbz89k%aXIeA1NXX*kJV?y!3AJwBr=jP#XWJJEoH*Y}E3uv_iD<6P7$mZBS; zo0E<9_!9c+$J+=uMAXNS;1`8l`cuCRpgcxjo8kV>9b{m8aMWLN$~An-a1m7!4o=); z+*R1>y$sjW5Y_sZji~zC9pk?DKzn#pMDjZsP=p(J#!F_~c%7k5SagcLCLf~;7hF)pitS!|@{X%j^Kg+p z(B_#!@meIfqwCsSb(O0Z`u#=kBUe~yDmFP3h>@=|B+Zd6q%U^8O)&eD?E7oe8bf{h zaCPR?(3HYMYx@}Os?S9V?b`Cd<%pBOB@h%9Szf#+g9_-{0>jtbqsuHC@F1|B{xKe% zX)6EW_xYX0x_et}Kzn=8tFAM#nioJ|ohya_f)t?!-C5-zF%qA}S%(ZaWLYVI>TGO) z-bvvK1K9@7WQa8<&2*|z6I{%gJ!41_7iR@xgMnmGw0bc#Q{@1_;q%^HZ^|1DH=aEO z>iJcQq@Hr7*oVuEb@<%wkdKn8SwkWc6tg@YUD%U&I!GM}bc=L|I{=Otbli(>Ff8oN z5Jc;_(n-TZre+BmMRuA=Qs46d8nZVkp(oeXzKj;+YP;~C zDwWM+I1I~^Z6?LA=2a@Ju!sL8I@&R9yaQ>Okq5_iLf}ODyuRf;%J=tG0rEFw=Dn3o z7RVF$otdrw2m1_ne)sE`<_=u~ZJ6QjZ73tm);$bmM^4EE(d9FLE{kb6UV?XB%r?R5 znQqe7Sfx0^*VuHCfIv^qdwr^%@zx3dz(LUOSvq!WK{-C==78hZ-Km zX6R6o@!o=ASo#(g)2TcN{GRy$qrh(<_6Q2+f<9-gzT>w!YNIn)A_I(|>7#f3A6cVq zgnad4Y?08`g8e>Gvq<%)Q&f_8Sh@(YK#Uf@MzAPiQ+{WIZ7L@@PmwB2k|caJvl2BRqH@phikv> ztf?U6rsy#h7J9E`zac2>t9|Tk7YN1zd=Z}!S6sFcb{Ip=SZZ#U;s_9-&HK2(aU5}e zA4&oiS<|TkcAg&$%0FdQi$&u=QQOgJHK4afvO^)<1yLhT<7?W zVbJ%y6qc~A%??Nw?7G0Rb^kGz=1DcUlF>*n3CbNa&EFI`r5oYPV>h0jP$q^8;VZxD zuf%QG5NcH)<;FSE9huBT(I{yQ?T_eeQKeDQz;S=pNNG%#v5qyhSXFruF(!qUk$SQP zoOwaQT-YG|vTvz4F~%~S=Td@=9VJ0{)k=vhae|WHg&;>zf_cq+n=^Zo`qw<{Tq6w3JkaG>|iXgLePAI0`+q#NpdU3u<1j9?}r%YHbol;9B^jTLzWwEr2eO>(A)!7tG3 z#bdyCCH~24T2`IOhK;j^8l0Q;Ygb_I%~hb2YuLX#ARvI3=M-ugu&Q(I(V@tuCF3GI zFl|z)EY8?kCNU;q+uL_vZ(H;2^Sj0_B?`laX#R=wRJg{)I{_j8( z1jEXAw-(}`arPienBHn>PSZ3?pa^BuKFew{gDSlL1of=029oE6N7U^cHaG|mYVFM!1bTZFq z>-L+UgZOhG0GVABjeW4lMq-Bat}hXBqeEfiLp?5ThD$Y_ekxL9wvvp@bYGn(TFZJPYe7V#X$sMz~ne&|&bH)S5Lle1mvGW4XW3+Co}g1i1G+LNH4FJqWb9IC`P zOXuchnoXzYZ$i?(m0PdAcz%P%&&R8CJq5_FT*0zm0d*Tj^5w_ny49<_anb2js#H(N zvfggr)V2AER!=~*=J?$bW~)oI(RA{5L|~*!vi&z6$<(w1XuN%;{nwtWGGv$-6ckb{ zO(3Y8xTU!Kj+o*j8qyR|FLuTNwy1BZew4=JF|zKHM^gLhp*ttlk2kJ{tx~T{gq;#n z233ps;|A+#msquH^FN+HuaG0Y-}#`d4SZ!V6WJ~P6zc9Q>Ti`XuBf!1O}|n})?R`; zCe$la&&{R?3>ZE}g~(Zj$wOO4wP;X!zP@$o_zr_B-qEt(p*D4HPXFAPBVo8%ygnqm zHMEG)r=HdiN<$FCD=^Lvk-?Nvp4fpn$n$Kr7fbMTkv)$O;)xS=HB)r`j6OrNYG8z| zCQ+Zw#O=eJ`dy|9hf*C>_X=DBShFg8XsA9BM3eDY+JJ7_2ITr(`m?NC?|oOEy@i*W zartJt{*HQD@&|9qz`aIR`*Db1FuBrJIINMYr+@?(BcvR+=4s|8Q1Ipaj(Q$~8AA!O zK&k99A9V5z?nsT$R5Q;6Z%?;Z_Q|cE0r4~@=`GD$Uc_|V1tC3@$+^8>oE*4Fl}z&O z&dto6a$|D3kO|n~mC|dcRoGJ5=@yWkYd*Yb4qY%jmmnm2B9(0rel? zztKFz=hzC}96wSOczAbkr{FNc9F*n*XTOOg7@q&P(|)$bnEBYflgC}O8m_&fUVLvK zN1O?Um*U&H%8Pkghm;#OD*#@gW?9<=jvq>rrV`zlgFXQZX2|l0mL}3v_y4kVbypV4 zE?Uc3j3wPsKOxTTlTCf6_u5u4SCGT$0JKObsTprdf_`kh<3Yd;mT&5 z*Is#Mvk^(rPwDE2f8mXK%*0&}l-Hu=q`A&@$`FMT0nwr&0%kUXZkT~beN6`LfEIdKo^7ykK;H527yjdUg? zD0DL*#Ka(oQ4D((ixZk5`#;@%dBz4cN((fv5L>JQilDdZrnKtB`-|)aQxy<2W5E7G zUHDUgm#=BXAvS08&Z%&PWaS%gnd+l(tw(Bdf08iTXWDqIlf9Sfg$Qv9?fg7=;;}@? zF-MTZTc}B#T%44TTXGPtqSKa}KOgb$P%)yiv`%(d*Dtucg60o%|q~mL{#4 zkB7Jj+ZHi)$AEx~9g>hK)SSiaSG55@ zAriE!2oHwJo}X05IOUM2*BN#16%s_erR!gNDp!&We;~N0Z-RdyU4-F{%t~Hewe1TU zEkF2VTCV}|KH^T+JwE?$xz{DH#~-;`W;)vp3w=mwGDB)8y~*4+=8^-8tGU8a=7C~| z5=_E`-}raU^f)X9Rg=masRfX4JgOrza=7I<7SL}CC>upY=4f|@4_--@myCV;1HTn) zcuc2mjAWN34bU0rB|@c;$^1hG@SyB|6Wjha6oz=pc|$!O;Cr78piyUKR%(QntXmzZ zn_iU(4rh4QiMS!}y}}u|8cgE+>5tEk!9@*!yt?2gRFjrk{KnArkwfanc%6~eIy{v- zzm1Zgn%~1rQ2IUdht*&$X_kGxGE-2J5~m_6!X_;eX@nsvGK`ED#VC9Dm-XXy_`c6i zrk<{E4rged&mN#}?q39z>>Qzcz=jSrGD|D!t(asyxTBx8v^`DOfsSY%Rh<3KPkc=o+Bj*=2H1Tpg>G3 zgUX6XYsLH858j|eILGZce}DtM@d3B_pkAlHGC~yj`+GOGXY!Ab0-AQGg0@Z~X=!jp zz~}e2HHs-i%GxBf)+dujRsD10l_28j4CuKf=sBtiaG;Pj-FjrZkTP-7O2Ijq8(vwB z|GJC?Zs&j#Q`HVdap}{EE%LBtGhh#^QdOUegj3D+u z4(E3PM}k8qui@hv0p|k zpm4L0u_uSWX?q0q^rPVi`{2wp;}`m)YW+a|mU#W#ehryYw^JgsUy{$By;IVcMY}O7 zGzTf6R9%R~6eKif9LH)hKg}ShQL=jcv!CYJU37MEf7`O9A;Q!QNNBW?q*m*9MHzMx zBP%fK#eL}BG^`KIi*#h{nB!E9D=Qtn2j#!DCW_*__v&Z#>WkiF=+~GZirOy}LTI&y zAKcrKT^Pby{eY8{oeX~)>r9*Q7LHyHiy!|O*>1&f8YsCU{p?BoY~n5%M!DTGRGv%r zTUI0qGS~DS^Nb^NmmR5_M()@2&_BqYR{a!=)XxC&b~;b*DjFDj@2T@qKvT(zSk(yC zDMN*hpv|Pn}; zHAPb411s$hH31n`%n;JRJ?Q^hg9xx$-l4bQVfQ;+M19Lu*HHP|0tMm1UTr+)NHDiA zl@;j-S@i|c(lB0>O1Of%`5G_eO55Bg4`%lHzMx(1{S z?;OrOX*RkHr#@AW+N-zLKnl z^QS>-LH?jKz{))|R=JR=U(karR$wpgKYZ!~lz*KRy3{+jKpB3N;zda4Jjnn&8P&>Y zP38gckShovgkq&zc4ddWR*gJ>X$9gO0HjQgva-Vo-4RtS6UN50NH&FEh;ZZS`}SBe zW>|(K!Dq!glR~3Ufl$O!c1iJO=Av&fU8qp;_%v3`(9oGW&fWy!KZ`arBGhhLrS8i} z%9_J?Ndw|sZw_*+Sl5?x8c_f+c;v|irxJh@7HAy>yBB8T@OvESV}nKR5Z>eyoL}Cd zTa1on;cG&5IwO9g>nAUv107tYtFht9r7SuNmig_QlmLjsT8MwwodbnJD?CH7h z2K;il`l3QZNPm+&Km1Gr-)37x`<2eE&Z17+`Z?C_ure}(F^F&U4Q^r)FPL&upFNS6 zRmIwTD;aP#_uh@bYLyNx>RU-4Y61eMF=wO&F2?1`u;IZTs~4o_7t?d?D;0v(6HcOu zn}jl?i3}ov_UDO+DXOmiC0q@as*Ui>GJ`swD{a)JS(m6 z@#clM#4Go}d8jytgd1}g8%@jZxL2n)E*A8Gn)cgl8=vHyK72q^g3vvvq(UaL-U3A1#YI@*9& z`B6fklqm>Q8f>Ayxb*Zo_vyfF@CEgesW>;P+DiwpKn1g4tQ_jWC1wg3o@BhTVzf*H z5Br<`8pvIrdR4iiABX(jka^(BXrzehB&72f1-EF@)fyr=o$>y+Jp>e6NKwu|>3M_Y z+Ug$2Tp$bdQ{UPh$7H#-nnB<+e!pV}Fw`YLW~pJ*sM4adKyfhfVg;%)!bcKxb=e=x zlz3)}G39Xa_6^B?kYdNP1FF(>Y+;PQZ+7|ZLzrNv}9U6LhpQ%!SDcI7NP`vk2ibc{e+${&ZtRKB1y zovdUx?XyqLFn?Foauw zmAL4;iFSIjMK|s-c=u?v<)cVy&1|ma!US~nj>*YZa;YA#we6?Oaf^&<>=SY@ud}v7 z%~!~SrLu>oswymu_P4F<>Quj;i84Ul{ZzBdVJE!9hde^VJO*`D`BqT~?C4%QwlLUq z9P&g|=8#%}E`PXNRveybT^x7_swm4mWSSk43flDYek0Qo(61u7i#?|kVj5-p7e(E) z=9v93|F5ASjNs55x`!uhb-NC+_KsDY{pZnvBewNlm2gZ3x`{RZtS{UKudXMY{KFO4 zTTVIR{jQ0b4SKbq?E7qIpmdCSepjH5$J(4yd()yj=WaU^rhKLApd-JL1rEOJWMYAx zr!?B6Uc_SCRC)7oT+8(snCdm^>-T=bdAiW{^iJnze}rck$f9L#XK3nDuxY5eFqub# z{*_9p&Y-Nqx79b%pi<)YoS%Ga9d^8-YE(@*K&S{({|v9j<$(|%;s<;7SiCEpZjkI@ z1Jowt3mR!oxgUjgi8cJpIoZWp7&e2Ciu969v8kEW>Yt){lY#aWaf#5?ljMI!STO01~Wuarz+a<)RxVm6=Zb3Ev(-ehEu|y+#H{caxf>=Oj}i=T*2y$Nhjx- zIor6%mbANnY*R@h6+u8RA$PI^XTxI-?W{xPa)9t&b|V^tYJFCXlf&;K)*Z*er~4^y zmh>jN*|2%SS%O~W)K%=|nmja4ir`|w?)}52y>JE8iuN8^C8p6{H*rBA*gb1&af9PC zzG`ibBwa0?27u!f(J(U$IQph3>Sf})DU%~Z8b~TzGcNYuFv4mo_`2S@<310?s%|?A z7IkkjFjF0csg}%Dwi*76(iB#Wo?Kc%X-kzviW|Cp+Ss+?E7(Q)%ZFNoyP#aW*AA8| zNZn$gk*38Za@)Q|B{q!HfDu3*jls3PD>E27pVgiLdU>_~{Dcj}#QV*afWMrty%Xi~ z<=0cwwu|BIO}i~ zr8@y@tOLuLAa&j`5xpP@=)?XKu!&=$epGzIERM`R?TuxXBJ|7VF(%C1_S^O(@ znA1}!DRPevwA;pnU|Uy`YB|{s#HULKS;nbISJbe)9+j^V%f%#-B?34p#_gcB`y*m7E&H}w{GorEwT3=ss z%P!JlS(Y!uy(^mr-*`kb(gMerC3Z8*Nw=p_awF_u0%Lq#{6)qO#_OAEY#Jr9;uNv> zK?5JoUun0ww)}@ez05`JryYkRxPe0K)UX;@p179o%2yu<&7I7s;RSGe`;gx=3NcdC zo!k7y`!~6qQ-DDUF!_gfmMiF%9CBfzXQ==tRq#83BfnkmbU<+N6RT53W;k#SSad8U z^lC^z3j#=nt!rUq3@0IB>=cf*i|~?;BfKZ1n1#&J?Nygc?49l$X2@v0^oF%#@&#OT zdqKn1TiNC#$R=Ki%kXBfuFIEu4yg+T%?m}@uczSOe4htjKRJ!W!DBjiA2Fn2u$q5e z{#Q*A>Z@@aE{pJptf-xHr-93zSBBXJBvnLEFPE{ZREFK(5*`_y2P7FDsnF}OIRbzu zaN7Oc0N}q1=%Dojz;JyXGd=BzfB+Q}6SKV?tf$Atz7uRpD%ga8f>i$Wlv zm$R**p+`=xV?#?TM@SeF5<D=TYcWR#hi>Fw=}iHV7Wg98o@4h03((9keCI$B#>>*wdk z&dwei8=IDv78Mm`Zf?%V$oTa1^!obxC+++D&!3-B`TqW?{a5}Qw*QBP=o>~>R9Q(_ zP+pXoo)P4~2_kw01w#dB0%-qxjsK7SLGr;(OlR2-XAQXW@WQI%3wU0hk$SlZB()zVs3+mYSf*wtH82MdG_3IqjK z-9M081q&Gr4%^v2ldzf#8xB=5y%xWbwV(zMh=qpt2cIn2+b=B#T;0Ti9q;@IFMp*2 zL(U0E>?8ro4<vyi^3D3F%cI4dLV+3eSQax_VNC4 zYOsiefO%iwB>aL(N#B#afuGO?Np3DP@(CLBnEJNb1XT0jhKwuj65 zzJ{6;TyE$4v)LlZr^O4>>rw?M33QB}$~q0)2zjpWM`d%kV1sB%wrt4&TBIXFm5_{X zw}5({WONu9z+g46BpldZ9dV5iJW*N8+>gpo$?skvI3B!i5Dg_HJIN&+oRn%1MdBID zH5_=@F)2t8{9$E};W3L$?^|{y3uW5@23&MPSI!;nkez>uRmDk-)T}6^IPwvm>=$}# z;qSk_tm&k0#wMf*^u&V#KsAe`2Tp35MbHW%|6a(`tHf>LiQr~jkA&(d5w!rYm~RyM zN1MQZ3eoFO%L*wK(~k{PRMW?c+kD+KiWAb0%^g#siOWm3E7Q-)`Z~`3mej|mpHdH# z7WjciFRg5c1R%RpWEUmhDH6lOo=a2~XYK=j!$i8y6v$*L{!t@IKdp!l=m1sSr%7riQU>GU@hRx?5;vlq;PRm3CEe$_GdOrc` z*kL)A?z<8?z>zQ!<{f^sK|R&d+m`%Kq~Ms@Neo2HVlIbnQhUCW@RG$&ImD=Os8}*e zd#Rh(i_KOafr5P*RB8qQ1b1^5xtzCS7CM4bpaY!~TocJyxCDBZ9g-M{()XAc8$cj3 z4<(q|7xd2-8BF}=sMtpdg+7J)j|CJM{e%}p)zBb#$G|~ToM!-_&j5L%{p!gX=GALU z@Q@Kr1X_dBBbd`dr`V(`$aHoT=mk1F1=>JjReM>=spH-4~K zurm&gVWLlT#x4pI{5(1}U7zAc3=)JhEK=h^?i?}Kx8=pg%no*x$+#aWRsU8<99iTR zlVU_>0_Gba^oFp>?A$LuP~;XOLP{<})Xazs=2qUYKou|I%LOE$={sNq6OsrCj9!zF zIbuMRfx#1U_*0w7HmK(7dNSTUpv9w3SlErlkfa|D%je zG>b#e#Q>ApNMtje1NI`}5rY&~_#<9l%6I!8^a;g!rvVe`+K)t^Do!X$a8wnzHF3cW z#~JlU5*#G+lUZ3oLP^*2__~Q8eSD}O%g+d7y3^S3S5tD&hxC3Pp z<=S^T^|quzt)^ix5Hm17#VHiD5V}yA7@F>&e-N@;{9P%(rZVVetCHE$Vmdq9E?1C( ztld^ph7Xvj{v?-r_Yqo95ZSS=736Z+qe^X<3IUMWn`+}&KAyqC*|9NED+=C|ZwXPU z6kZ!jIx2C!k<~v+U&^&&)k!n}CZd9ThN_W$Q=>U8iL_mX#^L3Xb2ga;uWN?pcwcf$ zW6_zF-+NmneM(bO<0TFE`o^RzM(eD~l~dZUwXik+woRuqn>;P0UD=opQZ@$6fQq$U z`{hQ>Um5Ofm>OO5$yGUnf4s0Pw4t|BIzI>3jAps?5Uwz~!O(YoD$cd8J{D*?%nrWK z0JJ)47e%F=Dh0IJ{{M%)_wZ_J?b^LV3y=T;N~HJRq)8EUBPs$aBAtj-MFa#y1hGN_ zq4$n}p{3J%l_E$7L3*#!#SZA!vw~&g^PKH|&l%@C14m`iB_yyqFSwJ`BJJBh3_n(7&Ge+9`wjrkPf~j*_3pj}_nP?6c+vaQUzZh!4 zmUZ5mZc^8vZL~`DkYy6%_2Yq=V=<0H*(OffqlD`5xMSs_1Gy56m2p#E9?;#}o}TTJGe(7`4D zQ&qMBc>-Ii_fP!M6MmBYxMNJmcGrbNz?R3;TcoO#p)%#>^5QSi)+giE7zhE~>0NGp zgR>Kki7_%>aT#&X?y|kP>62?~wUA)NIjD>Zp8WV_#>{YyF8BJ!JSoHA=>j(`FKVwE zQDbhOI&Vr*>mEhS!U9w4$~gb6mu@r1j~n+W9%UU+tex*r2x@q8?_Kl<+^ZLACu0)c3O|BBgLYehtUueB1Bk`v{))rGhv zg;QeXB;Z(_h+I~b94;RxNs7S0mADWxN?4K1;t;NyI)rd((UV|qtOlwb&fVIE?G5OD z@f?*`h7!UOhf%^JScN79ti*JX%p6=qNN8O|W+h0n05|mB|0Qm79qz?2_Hu2c0MYru zgheHngWxxfs8oMy0L{Cvq93mL;#>z~z^EhtXwJgLqrq@Xb`QUh^hPiBiVP!h96uwQ zj3tgAzrfzbvt`wW)(xW4G?qBa%2P4}rZtdF)JxMVB@7TD|9*5G;OJj{P5<`jhXvL6 z1m#6jVhr=sdvDMaf4O#Hs>E7uIWY!pP#=z^+sW$+*Vz)kKf+%t34Y}xM;-?2*hsmkZy>zOk zzFzq7{OkKnUd0K^FKYXutg4!WSTyQ0Q_?uRf)|>6`eidJr07?<9mjxJKyWal%CNKA zS4(Y|HzgA`6H*FW376Pp#_QP`z(CA6l381P#@~^K@?Rh3>${3t{H>zjyMdHg20B1d zFhf=p1E459Sy3hs0>Xp^1B!wGMQO;2QeZg&MJe_wN=;Uj28?BFV#Lx-2SMiGU~1}h zFk~f&{x3zPe=2(AUle84XCW&}9WcuMLs1wz+?V_?!*&%#BdH-UVX~slr||6b9r-xXy76kWGTTniy9y1uKZvVI|R!>*#IQjY+N1_FxK^(v4R zRV$gjG{dn*->YVoVPm_i=mi6>>wuyMRRKi<07bb0MZ^64kDhMbVlZ6UqQYI^!dJ0f<1W;6ZDagU-Vwle|j z4!9WwSgfU2V#kH>Hk-1X>ntk`p4A_n*j2JARG1Bxq|7C{N67B!%7 zBp*z`HK!Q|)b+Y+woopYsD*&b=|$KyE>~SDG%vy@B-TGdmr7DYm>@u#e|C8(Kt}qG z9^|I}ohB)b&&jxqvnguJ0a59FOSpTPnUn-}PDv^vJFIPTR@D%2}R@i!klWy-rD-7JgZohwMfo?Y~7KF~o1f_>T;W#`2o)Zcp7UYJo;utB) zE6R(4DR7YLC~91N6dj!DX(SGYKttPtARRCQn31N}KfeGEellWFi=*Q#Z^VInMmS4{ zk@R@RX$S+whW{*hIs}W${uC5;ZJV|?it1tS0cL(Q#RY=f(=v{Vk4!wORHYC$W_D1j zstk<%0EnL=D6D-UMFxky@wQ@Y&tpvi3l6;!-197*SjkK6 zCHQ`JT|q8ZRAd*KP(fcjWFWqf#+lwq2=njeiDBIUE(A<*123(9jof@SEu$Z@Ts3Bomz30Ol8dgrDT16+$93%)l^tZL0wNSV(mSHPSE!zj?bTY-|C zM!MMsIfxr3tPk)6gTsvqCJhAH{5KkGo-YYR%cuy*PKcw)L$m5uja!Oq0~c7VGYVA+ zmq#mX84^0Y6x_!Zb`s=xxxC}Kyex7Y<+b9SUe?*V56hP8d9GPpeFdJfYeG#Ly5VLi z>UQEc43m*B8+@GJNJ-S2u{^v&&A`f~>hgiWNOr=X^$U`UOj2gC6MI#5mo1)-3Tn->BiFhLL?COjQYX)p}W z6hK3Vr*8-%-aA_DkBvUB1oN`g7v0cM@|;nNym3c;9mkKP$U&rNAb5J;0(fF7@8Zi%}hDBuS> zd{S!uJd`rjLe5E`9u4xfU9)N2Nn}_=8x=7my{nY6^YuIV(D_yYe!qWy5tdvA zUAJK8F4qp5MkD;k^5qOaOHs_pnz3n^;5Q$|pA0q@g6mwD7c`--_RpQvbTP?KXow~% z5B8Ny1{!lKn3>#qU!2gJiS;nQZ=y}$!dJQ)EC_xWfN`m=F}r+PaWKK%b^aoDVrzg= z^1)lVh=E&@t(|yI{Zaw1H!MlHu1x71+JxCU>u0XflFcTp1j4g2hIGPP)><(k)bmD~ zu&IK(>ifoz)h(Dyc)~70RY+b)yXPX`?jN+d-=4{-45;$IqGf7YJOf!wdYbe|pa>$8 zzzo*QE@qHkO{tf3&P-8A9ILaW;1l0OosGu`S@lGc@sr}%2 zZZ#SrGq+^DqyP}XNl&i?>KT>=Ltt`IDopA1clT51=_o@fN)Qm?+p)bwH1I7!LU<(c zA;0;SkhQ#HB990hCMlJKsouTSL@0GJ#a2xl%wD&|U0&@*+%1=&O}h6Fe325oroc+&*&i?Fn4=4o7?xUv{KNdNHMWDqVYy)_rvB(v|nuh6qN1sIIEA z^|6YZt4utW`3L{Ze z2GSG3703oS_$dnbWl>NPq}!jG2nr;P4IxJ0lK~S`2nYz&hI~O>T@37C+6?I>*1hXz zB5`h`C~{HTT-i2LlGJlxJ;$7xOULfkkQlkC-A$eZubGR~>$F-?Mff|u7St@t)(svy z@B(hJ!39c?-j7JtEyi>`Ql)MD{@Dh8>fe91|LwIQcMsoc&Af?fa`(_toz zwxVFBDQLfxRcA%v+_Na5GwEk5ixxVP6rCqKD~n(DAaBAs&Q+Bx_v4I+R$W!4t3w6$ z9qH$)zx~vcUDf6B?0oK9bw^Kh1kRMIla54ae1NcsC#ZNcYTS`p>^BRw2|jvvg-Nh6 z)J9<9lk-AL*W*L^>4$eV)}Itd9{S_x{ef4n<{sxi@%-`y`zI~>;b%tR+nvP|ghqg2 zbo@vRpLX=324G|I^_3e?GDHX@1PQug#QS=i0 zc;R3LQX?|*dhp5u&O+ngxqmbX-Iqi?S$k7GdFgdwOz3nf@54D6@cE zUkEidJqFEWW=26ur^qiTN(Ggcl_%5CRTb7GG3ImTH#EjGXAk39NU}F$+EOq4>j#Bwm!=7AtNj8BxaZ~y*i-dy?DM>Di> zTF#D|mRMlDjj zirzJVCCmX*sc0LvE){!HCPmgMnx8?@H8`boQ6YkuAUYrwLvNHP0aKhye00sLdtBvR z=kv$Jr#TniDqO3QVd^KnKU_MSjIz#o;KM;U+i=a>Z05Do=}9vc|7YOwq_R!Pv@`CG z&kG&paJb{`smZj%FIWe!&BA)v%Fjx-1Rp%rbMcPYTzE$;F`+Y5<>T2;K5x!F*U&|Z zhTXwB^_y;XFD?7LpWGkeGpPVof79JUu$g)nW^&c8qA_>cAfBo~lB?|oAErjVlC4kt zWU8vb?7H=;sD^jE%R~r$UyXUxp@%n5F|Y{QZ91HPQupU4p!ko3=lrhtCs$QSF7hqH zRpp-HM}1?{)=wZpho2VcSTh+2alMFO#y)gASWcnUPf~CYX9FcC>Xxd=92gdHNZ<1S2;M=<<> zxF_zB{WL~YD)Vgc4Uvv;S?fDBVs8&VOL^ly8iw!Z=}C`qrIZo&Tta7Rm+~L3@^myp zpTDa;pvHD}AjRy8!~M$mD?@SC=TJOqu*)M6R}W)qD-!I+g519Hv|Lg>Xgh)Mrdj;6 zJkfgU-ow{48y^*~Wi`BGrjmBK%{JGv!+Qdj&2F{e7b9lvP(EVg(3q2W?0PALMs{uDNk${V^;T#Wp1vAXet6GT|PiMm+%q=&&gIKSd9%fi?Z$u*Y_T88v ztk1*DgSb1$A~W3Gz{W(}z0kfP-2KSegZKxrZ_Mxy6MM+> zthjZ>Tqd&HRU-H3h<;^ma3>{4UQ|;=Xm+Ju!q|Gu6l>*1)Vjj9Yw&ABwO5Gu9zOa0 zLzvN5ge5JGg18mf+4XvJK~he`=$E^zb+9Acxlj9*NpfG4819~tFXt$(makxT-YTr- zX;Lq$5$W$>7YtF|wiP$C$qY^&D$oj%aAC>{l`d(;zm=;U-d2=u=APr~%+%Q5g^;^i z+!Zs!&a?8u*zEfI0rRW5?*}ct*WV8vx+3sl_;TX)4U{0$334- z=ug~!DdRnP?@6uqlp@+~PCnCva$mPwWZS%MJj(LMpy*GTQ z5bS8s!qNe&GAabfMr|H*mBp7oDb`AV)uJ;DG@U}FuenlDtbdyf*Kcq59Q5_&kN9}( z_DsI>bI{Dwp-a9LttVdHovK&2zWvUVFF#;q@X$V0QxIaj?$7r=$h-hB=H@ zbzAp(Tr^iVswZkT*R!lYZoq>sZAsFqZuz-Apq{@-0gl$7PKfYgO68%Z-KwXTJmd$n zbHaHxkfn#u5ZK zGi#kgEYt^oBHkcCfo?vC#vjkzeb$GcUY_TuKV00Sgl!c>O-FnCuu$*<#R+qI`8W48-?GtU zXwaOicb^7O8kx)MUnu^zRt+7(p-8GeOU-o95 zd8}Vs^?r4@*m84P=^K~`bZOd!9$xN{zN%||X6bexhR1~aH5y8e_b6<)Tr6_wCV-i9$kxCLI8cfX$!oa}d zzKg!}VXCunH@^(k2VKv@2hi9ra`*;OgsI^qbQY7F6IZ539!D=zkBUSJL$nZ6|Q@W!n z;hQT#(@IgR4=Fk!P)bYLPkbO&>H=F*p968dY}{*gy?pus*PdSQ+t*WZ&)3r;$N~iz zlE?*hbZ}fu0szTSaj8KmkI|3Pva+)?u*o?DVmdA_l2n+QUlI`kXwgj0>q=|Ugh`83#RI;WKvq6Og+ zCB5j;SNN=Ogx99ga%T3BHs?kEiQ_nb23EeWJqmqE#TV!?>bVxg(L8$ByiTWM6=Vh{q>b-O0iH;c!PHhec~g0Jj~~E`ZNKLqZ6gcS6L8b}1pyCZO}99%>LTo2tSKyaj;txeddJd#60s1VZgR|#E`_=y^6xH z?Y>o=*v1Ouv^Kj1`jWhQ)o@X!}mF&+sa$oWV-msQ1 zkSiK~i)7eSbt@O{eSdwZGN#sf*iqf(L%uUDv21v?aUyU0RUc3O#M{|R`I8^tq~%Y2 zqMXd1{>sW*FheC|@5vu4^@%)a}@WeJDO zyYn*2ZT!x&Oy`R`s{;MUJl0-xM^u+oVY_!WdOy@1S;SmjRexRf*wRs?_Sn{$^6r<0 zMbbr2&mMh$(z*vyNZg#Hf75oAbh!_>tbf-5#gW7H7oQqMxf+SG93(xTm{+&rv!?GUmW zb4azc9wXj0X{uU0h?K`lFPj2?L$>ZeE&e7X2b1|JD2R-0dKn&0>fhV)rFxEc?Jmlw zc-ft*aiAEI%q*_Ot16@Bl(b|+MaDMfj*7Gw(i#15=4dI^9_?IC#cMyBY%!DHVw=NK z3{2|Un-w#e#1}A$k~V(Z!E2$cQjbb|ZN|K=!7JrZRl_$zPF%{7JR6Tb6PKUB`Y^U# z?$#VG)JTSvkH^-Ds?R7v)pGWPCwc<`Pk_4`BnG@9)Dm+dH@tyzT zvjn?g#FkhiaFpCk5(kTw_&fNZ;Zfp_(y;{@~qcTEES@iK^{$1*Nk0GJKX3%pk^0JBZ* zVzynqICk}-Dkft#O+c?bm~B@tYCjw&px1dV8SoyxevjD>J2SGV9h)vF1oYZ}sH9NN zc5s>gfYjI0&To1hM&-5M&^vi4NB(RESufauhp|O+SLT*NjKo;^{Yy4Neh)e%~*_i*r zY?7@=Wev~UXoedAX7iIw{f5~xB)C$=Z6KKO-YF&wfY~0(B`F|wG26pk%ti-bwuDd} zPRBi%?cpwF%iFA=dH&uI!v@vgih)*+Z|3z{5}ca?nfdMinynIhx82&kmE1tR`h z88c2f2F`gU4Ij3NBkn}EQ_yDi?P9i-)%TJyokWjS)@}>QDlz< zK-%fS1;pVwi!S)!D<(q6+WOmB=+lPx8*l)%mAyXGTyLOe3>kiZ!e9SP{tc+1K+MYm ztEOB2hDW)vZtx5^Zsj{_8w?xxj@n2Z%x340H+lAvQ5*BWqc(}^{}t4B@;ho%+(m8K z-%uNF7q!vuqBhMRs7-Shwc%v3-%uN#jN0I#j>daXoAckIHWL7~QT`2TLj$O-D&Yre z^N=B!vG7L;Y<=XBB$1`)y*(JWj``UgaLedGw?-ukjPZC&UFlFq~gp(5u7H-^iDIY^@ zTFp7EXPo0tNHQfA5*k;wAxph!YN=(CLIaF1tW|Ix6c?RC8M(cv^Kwm!2?I6xJEkH+ z6@!eD_uk6YmEIye>&%Dgm->0XVMz>lxX37E^13t6r(7qVQi|SWz{l*1)qoHET+F^E z#~Di0tD%-ODu;bfJV*ypPhp1{2@_FCzCTbu`qJKsnhdDwzx0xR2W_d*0BFM#zzjJN z^Z+)c&kLmi<99(D9-Lho#9E2M!Alwg>)^Qo6nKjEKtfjWbl@9h3FatP#yx(`#2?G^`c6!&i1~8vy@-{}XB>;@BI6 zNT!@{?WE=^f%wr*J$9YeOhSfL`Q?<+q^hpW)`q7Y zi(t5=aHTEikYXvxN8)9-?jOiwl6O4g`?4+|O(&8|9flHZAN}M%%D*~f^njoGA5mKp z+1Leuw$x|&tPqnNNV;-RR_sj6lC~@+HS5q<@oVa z|6fCG=o1Iaw0?`)+@$1>O>c$YMJ4Qedfi(mK%;VvP0&Ej`^%AvcP3r~_3|J0c&Sc6)Gt_1)wI((Ta0x}6>3%v{g;hfl8k`6UBv?6)Q0*CwQbKZ;dfCR3P5dK0BQ^TiP{2%vWqAJJvJ-?qTrjow&QSE7gE0tmy z64T<>BH<@f=2HDBO%`L5zm(w7&PO0Cfk6?YO9>c+xf3BHv;8}218iN+!N1-F)BtYN z`$vXDhHR+-!+}#|#!=*8a4{evDL0aat^ivU!I;CDQ(h6ukORvvd;&0dIGE;XT}vP= zhryz&Cy2EgF)$baZKIBCmJh-CmTb<8AUGkikoCEy-47yxFe&RNqCVjtrj;MZI}yVa(vXEfC%lA znW&I>3_quqdBCVfLN`ua#GVvpC;`fMkvBgR%p$~@=$LUk2ZHmKdaO|c@vxUsZ*lVa zYzjgu=fL^-4*K7AO z0SprV?Omeg#df)kf8+ZN-*t)Rjgfu71FMa$Bk1F5TOv^Q$I}gVYT{O%<}G+u8Rp`g z8&1giQf_^oy0Efl{DIVg2pl(OZitb(-eZyIS!3&gD~it16xGtfta;9{M~_}q85x92 ze`2bhdFx&nQM#$h3oB1(?X0~sXehX2HiFOf=E=QM?Dev-G*bs)32Yu2_R-ZIcVR(~HZbp_TRYwT;&?n{VESzx%L_+}hdx{O9{G z6k%fSHq{7lzra~@SAiy1CO*SZ@e3TyA@#C$H90rt2obzf8PH`$;kI+4=exP^9v1Bh zomvg4tsbIziOPQrbZ*}eJ0Gp^(BqxN0g>mKQb<9XsuFA;M*FeWxg&>%o)eDb*!_vQ zwOW~H1)Y&TV(K{Z_^J=4t8&m`EZOa=ZOe5vvnvy^-nxs7l|%MZ;Sc4(d8+1iGl78| zqWTrXHgkTF6xtgq7S>lDnl(99xN$HUxh=qN?^$4#M zBPQ1qpB^<)IXP)?-)z7Bev-rSr0heU@ZEUW{8b)?wx`lEMPH^hOh8|O+5zxt zi19tzfhDis=IX%*%C?Ri5rekZxY9q^Itk^$?3_jF66{>Wy36dYADSGrbCusvws$-H z#mnAZjcLgK#+ zF$Xa|_L*jwJ6DSmF?XFChcNft`VM06-<&nWKJb21E+7~(T`@xjI`r`m>+B$smaN4c z(xpLoarwHWZAa-A{wxkOT2rVi%GassTr?ucUILe$;Ix9rVF)V((ob&%aN)1!ez!!D zdo{4s+IJtXC~>W*Q+ai*xK~?hz2t?d^Lpv9mS*yTdcR+Pr`04_8w-y%!qLj1+4`dt5o%0GUFcSBxT`n7$ORvmq98-;m6NK%*s1tSYlwBBY;dZkr#UT@i1w1s zuFCziEM{iU^ZU{H^}{10V|f$lCA9F_*?Ih8VhJVfTiOG29j~xelpjkhzHT$A84c;1 zXr82sol!S$9bpUTV=sHte11$TxLEwd94b2ML@0~8!+3dNYHM_*B#iqO>lK;!gYpk7 zB?vhQ$?_tz3=#?g=gej9FJ5-+OzO(4ixBL}@s#L^&=2Gd+c6e3iBhLk9tIGhPdi&fQ$0P_U)sKaBFg#hgAM==XVAO~|2-(FIO2$z zxGLWRkZMvm|A@{O-vidm{hAy_);WM9(~35CvzHSVY`I zLYq}AxW>Xk*@G2scOQJ4-HPHKSng%cI}4ux8ioTPUurOJ|1y?mVaPWYaZ=ZgxUE}{ zRfQ`}ixAl-1Ehr5OCSb~{Ln~skY8q;>v2~5lGM~0~B)4?- zJZbOkFYJ3U)Rs3q7C1QmqI+s+X10HRq37jN$I9xn^^Jzj*VS*|m43jR{4nbrNZL10 z514hp((HPGS%28^m3O)W5lxBuCE}bkTZo&!sYG@i@97d(-s()vdcoCY43FSee0Zg$sn+K zStHC$K&o{a?jUigm;GteRlEDbYRoAs93BPHGRjcBYkt%erRqy#EpKd=p5K{ux z&2l`RX7uJkTwwu>EhbQ%Q%VOlFWJs@g}dF;A0V5zPK0&i?4Y{2d>+=uH>kEoThk-C z-E{7E^?)a;%VfU3UfsEO?rmdaoy^zu%?TvrE+tIM#&2RNkpCEk?C@D6la4@W4Y6oga{FK)F) zEWTT^Kdj2MZ4<4nCw}>B3|O}9GU7bb(&ac!Eu$+(jE?z;C|)dz6^XqhxPMXIT2u`q zceULJ6MgMXpTkjSrfrPOO@*M#aeuTd+ou(5ZihU^gCrNyZnIh}i6K^SS0jQEed3v+ zqgy!9SY99ToM>%x3DHNAx=T6d9zZ1HvK9rM@M$S<0)Ys-xq`z_4ikvQTC7sJrAIu7 zxxha!JE!L(c8GbkhUTPzXLd;>RENhfDX<5@o*UF3W1bs4l$n$pGFCL48#>j!|-#+hHe-hS~=`bSprHZ=_S;fN$DK(N{WuQD!oa~Sle~OYODx;)GV%`3H zM4r74Suq}-9}Zc??+#f-lIk`}knE84w3{LvoCA+K2F(5HkZlGWvb<2TLw4tzLsp;c zkiAH|*CA^<@@=u@?;NrwSXtLrc7ZumC{fp^V4h+S$B+cS9LGaIu+g%bV03~mmD*nK zJwSBb8ZXq#z4TblG<>V__@?#atED*Adk6Ai6=9)ydKW}igF3kDv=>LT3PM<=d^960 z%|q?`mEeq%mhb|HgZJJRIh>k%8Pry6*043=V*>3XArOOxiG`ulnnwGlH12txH*My4 zX0=wlPUtcrC2n=a`rAKw6rWh}yfFasOhn1eRX$bZ)>^#V5GM=I7yY-~s!`?N7h5WR z-3j>5vd4dxJ^r)o@tsr7zFmu!p5TX*c^8Gvoq*|f2^ zCIajpKEj+W$q6E(1UY}Hv`O6JU`TSAO^v-NJ%_NMPykmswN01F=RCazs#nt{BK*#a zwkmHgu#qnNx>6vK&e@q~e7&l?*hVML?sMK|w3J|+ioO`e-hh-K_5`RUR{Y6%c!||3 zHio&IqQp@~-6m?-ovJ!vx%!GHifIS6X`x_zFu!jWY{mZO<@d!wrzQZWA1kSg(%5fIF9SSapnE7xRa1d&$M>UW~%E}7&(LW zAmWvcsVreVM~UMIV29+CHyUxFS$;lCWs9lwUNR2aETQA_Va#WV&X_C6W24g)8ktEH zDA#1;R9^1LH42)zZyLH$70R@!&9NL4q4TcQayPRJA4hu0)^5+Od~?X%{iwOu zhX2>b#BUWzCJ%AbSfs;g=!)YtJFr+1Cr}?=wDO%;++*qAFug z6|gD6si3SHj@_JfDAfFM%a87Y&GxW1+s>ahe2f3`2p6(D!et1^C6935L3T&Ds{cB| zO$A1{>ulr^Zu*`P?(DY_t}1ziYqUGU-O~G)5$-s|V@~SV5pLP{5$=8R2p0>CaPNK} z;no5p+%+j+gzNZkBitu{jc_afGs69K(f_~W2$vRQ4zdJs{x!n=W#J%t&%!~#UkeB5 z-GzhQeS^}}jC}IKK}p$@h>Geav)zq@_9u-AyA$00vI_FL!DtyUy&apLnVp+oSbVv( zyt2BszVT}F$70L&&W~RCe|NFvClvYrp4piu=KNTXOTJmz4Y+_OKl=ddaoKGdzQLsTru4Ks-0Nd0S001wtIOS{pl~9LR|0h7M!pz_b?%s_FW>@JVZN-{@Y=mXUY(hm ze|vMyf|3sYfQNfaI;Dg^Y2uzT#m3r`DSiG{%JmNZ*_fJe~i zU3iq4fJbq(&P=dsooPM)pRa=^e6&(d&xfDjUAAV9)VmXC7u(rJx8NwFUup4g9ij*@ zP-*i0TdO#EQ7xW>R)C71cYk^$^FeZlcwiqkl$j;Blx?>|WaB{W7a-AA2C@@b$Q>ff zQ`7+>FKR!Dx!a$LNV&H|WTvG2(IK*Z?-1EY{M#s6a)(H5J4Jxbf$uod%>LRT+LAj& z8ghro`K?3bCwGXCe%YbiyMXuY;aC4_Pz2vID2n>-7Y_KvFP!wvFC4M!7tSM=R^YNK zYpQ~4>l+%Io;J5UYi(=q=3!bU|6*WpXn16_)?{LGYWLxvZ(aZ%{<)W{8?V-$ zZoYlD{_f+-hpkVqn?8S?eMdnp9+e)-rJ&tfF@vx^ep>_fdiY~+T9~%Nrin4ujv44r zbxs~AnVD?yl%Spfm7g$Zx$|bJfmum#9GoI*-(8J6)m-l;){*mq^3Bz9^kPXi&cof` zh>1~R<=zki6T*tReIQ`jZSq_W(y*7`Sn;aaZR=~=x1Jm3SlGV1*Vjj_sZ`l zEAz~Hh-p^8JZR*8_8X%8{U}08S(;B87)3;>!U;sUDmGl2R3x1rB2N?)H5J7ZcuIo~ z5tIYe$l(Qt34ACXa(Dq;9tbahlfw%RnWFh-1cC4Zu}*pM-S7hV@TTIo@B%4vc!45{ z4+t+XX#&Cv#K_?V`W=UyrT!=31r`*+f;0U4$>9Y-jV-}!@&y_EW*aBJg%_|G3xIOgJz_^dC~71TudjClajL?r&ci@iiF+!V6vr45#c3 zFYsdf#VHpH==zU_s?@Z&@972UQS@LlB8d)!4Wj_|-SZ2AK&0LD0wNtvc_55PPA?$R zH?@|c=!w)_{*}Z)8qy2oV1FP9I%Y9wHrz7?ed+(4ST^>=jHwaXw(^3Z3WIL{@Pe^~ z#ISi&Odmn80J00r+4Q-O0@(#A&)4Ma0vjN^fCyw40EVgzAiF?E>fK&LmEF3H zLo|(hvi@d+tADPq4jRF&mkES9TK1^ScYG{NOIw%H(aO7LW!4!Jx&R#AOV6c|&7$)g=0n+0hy&q|1Vkg7t#5rDB*# z%fqIG2|E)A@W&t)iTBm+4ZIY&XQa=N6#!+gxBv7&cDVL)M{)LavL}$hW5|JzN@kUEiX|IE{R(#OQFf+BhyoDoP&4P{U4jJt&AKQ3Lb5&n$oup+^ zGgZ5~?^1&I&igBc^{*;Uge}3R>!sTu*7^6 zuq|Z4rXz}P~pzX0sfFE1q8pd=QPEL?3ClO0_(F>$gw zr4Dgi72AukDmXzZ+S`l4FH_HZp!J~l;A#j-KX@_%rgQ+FBdN}R4Fe51GD4Z9USNn| z5I#w`oN5pc!eLCd@Olu9X*Harh$cZ|pO-^QHiXy;a)QdaPZMG?6F`!2*9n=6dHTGB z=KOjEU1WZAnOq~ChH7OyHoybkn46yoaiaH<)kPF=`$`gE>h+q9U=1_J*X2>fRt@T;fmKLdfkPyYMwI1m7X z?gJ)}_j`JxzwvbaCxL*RK)^0!_~N!55Ui)9cIBRrG2X~DkUsX*8RAlZZB6>1$r}5- zR+`Mw&<`ok7hJ}i)FCh7<^Lzvg8yOvhMaV)cmb+)(J=lSc*V_eY`rW=3?sAFG+xtX;VimoQ9uYC+7AJ zU3LmwX>;ze9L%O39l97immW;nwiJqRy5NwF+Mx60_kc@EgkN;ZIG&W4w}pdQg^VtT zr*QZ>5F`Xg#RyrAj#!wB2V^0F=(@6!uL6^!%V0is#gM$ZIk z>f_cb@xW&$`g%Ejt{~qa@d80qR8*b`WiJc2%`1z;o*WJo8r19Ds&vJ&+;{3F1*@)b z(M!Bwotc!k;^05q3R(|2?Y-diTyNQfBNy7oskO=9jm$^=2yN6m@?**UZ#Fr0vt$3Z zRt3_Z7_VCVP5RUKCHHGRP+=whUd={CfAZm_noNqa1+Wpb1CA6v##%-3JBNwYO@U#{~0IBC8`=&kNI&GN_qxT2S%Xg zHfVlGz`cG2a_hrZ?uq(4P$ew4@2cu1I|VV$d+G>-1_mSr(HK5=8BDUX^oEl5b3BB* z3|~T-4G)sIb zN0u^2WyUOuFl&O2Y5k~GtM@iG->yF6Jy+kJanS#j^>MVxk69?iLCUVNwGiEMhUdi2Cr7*xJsUTWhX)c8*45=UiIa=7qk5JYE$88W z`K_S7&OsH1%;$nrU`~W#)F!?Ba5E3WaZ@N-D++=c5Y!Wv$GtqphS0XTq z&;>$r8GeZTYDwx^G0wD*ajfnZk$N6r^}vS4=la&$=clsaj?x3C-{&_qM#wg@kG{EV z^2~o^qfs1rA-Dsn=rq{;y5+2v1U!sc*i3L4@o41eHKNg0{D7%=~ zyh8Ew)|Kj6=H`4j*4wtKH~-qp`l^hCT%Chwba?`coQ{jHr1_^M_z|v4htHSGXeS5g zsYY(3&!I-Rwfd(>)n7lH`g7ZxSB7Hc!(#!O5KkKc`YNSqDMqgnegT%rcQ!}gS_Wkt z<#fH>?#~OMv5jC$OA=!1?*p5i8 zc0p*gKRf>N{wQ(THb_hgiBd%T$=U6h@KWkua=ZRYV*JgNDp6EkkdIIBaB7UaII!n( zh$jd5!;0M`MjnEq7=lL;e@L`6SPicjAbJQd-w-HTjFQLm?M-5o`jNya^&^QYk8B8!gq<@c`#8|^AC>T@pJ&6%m1U=3xz=e1Jk;Hg|oW$4x4K z+BRr98Mr5j(SV%9m>KlHl*D*YrSWoD)QzTqiv=3D-&Bm5fP0J;wwz2dLa2fzh2LOC z`PrRAj8zC5pz&eJFk>N-YrSP9!CFT0$ld#-g#yPsyrHq5dgfu;YAm*_o|O6Y4_?f9 zw?VBGm2mT9`&TUtW6hUa8%>@U91*GrKzPy*7nVlxoL{Ldhun{T$%leO-$9uQ_{u>5tlKv@RbA(O#tNA?}TYErO)!VZbWAQXS#?o6(;Td}V z^ira4Ff07*tD(lqghfDBr*LCFlGis`<$ua*W$yPWqm&MiHNBpjs`(G@Z?b+2`zGt% zU0J&db_4)f4+FB^`XOuW|3ua@wh!Wj^M`k36?aKBXF)1Kh6~ev%6e~CRxt$K9$8H? zew#|YyS`L03szmk&4HC;mKv=I?^I{Oni#?CAeC%Esyy1W%5C$}0v=I%P=icWM6^kI z5-^~+>|-aX?iiE>wL%P2CaFIq4=Cc=1fn$)G;_x%tXG?y-Kg=rGv=f z?!AWnKArG6X!8?#;ylsimLUn}$K+axyznLN!#2Aoc?L0NpMs3sqo_!IW~H#{(iXJ3QL zC~`hDiKmhrV~Z(FN2em$ATUt{qvl5kY3!a~U?kr0quR7H5tqvNv_1Y$jF`GmIw`hI114RWV_ zqw`5SpW*Ku2M^V+$Pz`ohr38-qq*Fc71sw!X@$%ncYWTpbf1ulbS+W)D_$zw1c*!j zyLhQrP16PT)U<5E^J07q5*3D&43u@W$QzQv!&ZLq-h$(|WqNJ&=qZjQO@a?Pu=CP0 zE8_(2a3?jM@DXupXb2KcDH8ONsB5+Jfohvi(qZ^j?R|vfY(f}#+imn9>ghv>NO+uF zV*s5_La$czOMF30m_dZtyu=wnd@~cTX>w)=?*kvZc$0Taf+1MXN})jAl&cunep4T$ zgivZ=*8gDdJ%gg$)~(T|$vNlLc6}} zj-|hUng8|`{^%3=zt>lo2WU`3e<5Q3)}a2^Q!sh0l)^Pd2t3de*w=dGo0zTDPb*EU z?JRsRUQs?iZ3?H~zhO3BWA_GI!AxuhO;uImjQY2#g};HZ-}^x>Z(??Na>gt0Wgs|u zV>w0+jI9m4FxjufC^8hS%*LzMcLFa=?AWV`*VWZllWtT8tR~BtG_e8?R9uCrxJsM( zX>n+NHd4A;xoZh#+|+Fu?h}_XRVBOBov%ols@Buy1yZJZ{xn(9j$)n3#X7@~1hkPC z;G71?ROZ-RiAb3$Ez#j^L5_)Na{Re5k=@y-&)d}`Qq(*T|lOSfBf2H*A$*r zQr5H;jN{zm&yOpR@qX|rYwaQ8cbN)FJ$^i?{xg}12o;qoBbnU95HmLs)1(9jj{_T4 znUs}B6H-=H)z(FlRY96xMp0KWRTg$c5E2^_6TWGA8_ZNiZ8SU@%JABMaw>>)fRb>o z{UdVVCZMurJpj|t1QG}?7#St1TK=}S{>6~ro5slEBTQ!sv}VHjX0;N>w615EO)`@IcEP z!VCr#3t)zFR*-E^CZMdiW+ZG2M-yqJ-=kkBMQi4|~@vw#*+gx~&~W-H0y z@^i1{?FssIlZDKOhdeWRDR*DWU9;M%%C8t&S~zy;?wcDL>X1tg7|&zUQTIOMnrKtP zwYO+Kw_ub+9UOu!Si};gS%<~;X~>Bs z%Kze4;!B}ie#~Ui*4^q!a0znIsWj6h%Cfu6WR$&7p40I|hd!Zr&nK|M*qeS{<_QJV zh0w;-Bzp!lRP(#?d|0;0ys8j$J{6v3>w6>J5NM=D3B~;{-^_gjELAdERr6M>E_~|e zpK0cOB`G?KQ{~aGH#4PmyY%XW*Rt)IgnZYxiue!_D9FBF$lHRlep`a zxho>t@tyY)l9iewlh!rW*!ZcO-$o{zO@nPxBh5V{)3zej*o7T){cQvv)aThe59lgd z%y4J$=g552QDgi3*#yakv~RiO22aS=q;=M-V8m0I3W1TW!pfInR|+k@T1JR~ zNse(Ln#qr`t?^Lo{2yam@Ej1wo>HgVFc5@=GW`BOjBORY`sYDd@pDMpiSGb}Z?}a3 zv=iT5d+NPcXCfZKCGAWKD53~#j}}a#assRlG`?5rP`PhY##hy6zeLpstPV)p*?@+m zozDTQ!=9xsP~BJFfux<>T5nTL3()Zh0NTlqq@B8d(oWIXJZB+GG{O=XKs!w@X{W|< zrnFKK5AKs!Iv4%~bT(9R?LHo@O$=Wnxj$VGI%JW>8uJ4pFsq$fR* z2Dykdd2#sga{Q8LY#1`jte_B76^T^{WSJos5w8%)GP_(v?4OH>Wt@RFhXO1jPA@jb zpNmNFV-azGEFutQHg#aV_@)h5M4X?Ci2I)`qRo%uAmk$cC=P;CgE(~Le=H(snEXE! z2NlWu>)HWw5ebls=+H^_r*=?;)DD0}{GlCO`K2A8{;nN7ztj$Ne`yC=|DYZGTEsuK z1LPw9m)gP2|A}@0iO&XOvP8C_;IVqa6|mRyE9E0-Tw_xxL`LlvF<`^P-~d{H=i#t! z7%dVM4(~x4vAI%n{o_dEUDTrqip~45bkuwExQNx?S|<}Zhgk>08bNCTw;e`S6K$n7 zz|RRy>~WIr8!+VqBRo@(1eXb8bi8ZTc9sdxPPtG`*@)C(FT~q`zAU~-^YAnuDOE5Y z-Mv0b@@NBx#ph4FOq78xw`^E|cxp!gJl>(IC?Gt%uQQ2|p;#Fw*8ruMf(1FLqdJ)8 zNG>+|ZKYEG<%T+T1@rB0j5OCuc+Iac2ZrB;uzw-zC;5+I!}LU;DhUS10_DY`01bmB zrI8@`<**?P3%w?S0)`wmgkb}5KUKllFtoR!b+C|rpe}Q4I0TM688l|{e&AyO?n>Yw zta{bI#f0EhK=;NDUVUyx4^Wm=3CB3Ba^z6;FERuM4kcEjAUOcbRwYYX%~rg=zF<0# z3tSL916eahD<9@iga@KIkMEVn%JN}}*}R1ZfdarF|FVR%?)R^)q|uZ7twGkqydYK# zswcn7+HNJ4{QbLMz2Kz8_K$%>JB$2dLtx;rTF@u=)|bcIW_+SzckatC>xH!hy>@oN zJK+xN3}kt_>Jt9OV9-RuZr)qH)?m0$@wsak{Bge7#~DMfV%gk=8b2hW=jNJK zVKShdZId^>xc{qRF!cAzvwsu%|Jwm_Q&XF(ww}{r077v$R5G@g|a)mp5Iav2nvElGhBt@oe(gpg|cgFX%>w# z_a{@|?O5S-TYHl(ro1#@{1s>DHW@M3spk*}+s#x_;(`q)VDdvOL@h}fw{W~9*{i6l zxnxo(H#XhGpB;burp9z*+8pcdr|JJ`3DZB#nx`G;X!&EPL`IqcnKh3qUJhiQlnB2H{6(KcGH^7E3t0?OF5Z z*OCw{&|VRyIRy$9^9$cKy40XsSavEe9--lJ=0NA!Aff0pHzZ6^u(14rx~pd?*%bIs z))(E-sUW%GRAw-y@Q-C1cS22wu&lzSQ6Q>;S9&BeeN5~?*-?E>aAi4P`LA}Fw=n2J zSzS3?pXA35>BGDIufBq1#!HE97I>iJ3FoF>NQT-3I6O+TdCnS*Z41>Jlw?eP#4T=} z)$vyf#0UCy>--zi918~Kkin_(q=rC!lHrRua6vIFmLZo6v#PqPGzK%5s4f{bw>g>8 zko;9rE|Y&cX=gN1H&Z?_8DU>|QE?t|(^yDDE+wSuWiIjC=^#kOXaH3n*=i6GHRfjE zEb(r1PHxswWSimHcQXCqH-sI2C=jBkzO{0bK+ml%^e&P`{TxgLf#%91Hl<)pu(aOG zuP)3KI1^n$85`}4TsSM51pW(52EnPqe|GYq+Whlm=TBg4d@Rw}%Ir^i_!FY3LDP`c zBn-#u=j>GE*>cHXJ7tv67BVO&_I18c|9(m?)SGD0V7WU~_A@-#G%DYoc9*Zfa*6Jp zsAs$RRi{L8%Vo6rapNZ|+8TQYxjJIzla!dz(;k4^eQj%AEiG_D2bxucB&19d!S7oV z(qC1JANb!?vk-aFD(x|H^PS>yw3B-|e9HJGt%O!B$hxDZfHpSUTprwqAva`DIZN|+ z@40UZb1eOZ&JhY)TbA$7Le6Z5zsj)~AmrH~{g<=h@0n~6GOZ;%2c80?wG<&k`!g$Q zqr&1#>Y5`P5^7r7B3fgU+Iya70ipe!{ez8fhej($#wL1UAEpD|&3u@fA6r-)T3+d2 zTYs~;)xN#c@_DcB>wd*Ixc*NE8b%kTIxuyCg+hXs1XL?Q-}_(7P<^<_5l%&GGM-qK z*B+H79sV7YBmtPL`0@7rCO$>_t5H`Ti>3y>wP<|Ym%{u=Zj6VJn?YTg^0b1dk%>Y< z*|g`6u$GL4m<1yR(W3SmM>aAs(SLOE{PS$& zdus3>{bzRl-Nl@7{N2HgW|9k<9+VXm_(gWLe$7yfVlvk6HgnMM9z7B+ogy9YB(%H5 z+&WR&PUM2c#mTa6gI<@}YNgaI0oHz;Ym8nJxwBQmk zWDV(IvY-#G_&BZmWj3c1EUnLWK>V@~eYe#_q**PsgJF`X#oUn-vep~3Rs7i{F6 z>)#jb_=LnHWUtDEi{F>%^;-btlJE^Y1(=De%Lf z+TCT{d#=lOMQHtauQ@o4jigzfmj<~$d}&og`Z^ln2$e6JVtK{;QTTj+|8t9T#Ydq- zgm)O`g5Df{quy`g8xk&&4(sWE=CDpBWgtT(V<63lhalk+QV>H{TuD|INmYbNXhaBu zgKNSm;S51cNVueiqXvU7;gaenTv8fkp?<^!;F741iWmu(Mo73M{{fezmvBiQj0(VI zQab>bL_gt@^gn@13lwUm#cWI@TvD~824Yi{DiawJDqO;4IuX%?AJX_L?eeR&o2y0c zSB?h;2>H4Gjq&q6e0-;$@%_qvY%zy34ilP6#P+=G=rE|lY zjFq2#$cuGx=OveOEN-?y5WyA~sr{fQf4YAOm)%-R#FmUh4%6s!^`gDX!hoC+NL!M5 z1x6-Ez)kF01HZX2{l@`JIb?j-v81FPluMBaN??f@b@OXkV0J3cc}YyR0-1g8=Z_SL zVtU$kH{UB9)7}|q-ThLDU0#+wNYc1r3!tVT0M8^s|su z#o2`>5}u<%ucq1QK@Zue!3s$a_lb(<0p+GUySsb87sCC5^uJZ{lm8fMO-}@dT48Vj z`n*`?ADS6G{Bo!jCcs`3AqhhcwZep6hp|@$3&9wF)695&YGyotYGxvrn%R{fni+2; zoD->;^#&Ii0%NU6&5Vikijpds^&gs9t!u6#re~m2Mn}~jnpxKuq-G`{X7U!ks)$cY z4`^ogKQ*%;u(b{kJ!|;CHM8F}Gvs3b7i$0iKLpbFe=3k(xlmd;hA;<*HP|`6JPyeP z1kx)$x#5=rsfg5_YeLIp;ebH;2oOk%F9lNahd4-qROxqtGz2M-UV$!=MfCA%wZ_hU zQ@YQ`DdT+{wS@M$THUn;;cs;mfIvF*Qy?v!Y`zpoSAPhkb9h1- zl?r&50;%MsKpF-Jqyk8RlztMT@DBnhId45+^AYU%F=DYCg&cPMW5nWziFb1u12FOC z$Hwzh8RrF7x-76p>AYfIO%!&`228wtF6!8P=5JZ>VNQ^>Xu1s7)ijBE7j+z`EeVCR zhfDAduB+poEZAHbqxo4^3X6j9{3d{Oe;*3<<;(zXb*mR9shf6@v@a!#BF!AyI}3!E zi*jV2C4y6|~&wV(1m?V-Y_H zKHZK3wxFAHL$m*}^Egi~JHm92I*Uo~UpKNYgkM9riS}5xw1!^136TNR0!SHk-&Rw%COv$O;q0y;8 zm1`6ni#Z~)5sNG`xVM)yDrhn_G34#WB$MyB5fjpBZod+z%AUr05go{hbec22U4h&L zoaXV;U^n0+)#AcRF*n#Ops+~ZTuHSl^khxH^Jro1xl2crLW(g0yvE8rjJ+cISUr9{ z*~yu!BRi0y&mY2>k!>sGKp`0bq4%S<%VWm+x}Lis%DsZ^|59g7DU|bTSfPP8XcOk) zK-@>3YF8arD1;RskVaZ2rOpMD8--&FU+cWyJ|@$QE=MPxTGaH2$j6rPPH$lNXeaXNjuNN-D7!WkuKO6vZ$mi z0<*rMk<7%9n5Z_990q=!fLU1D6-Sg$`8FOkzjH8#DxYaAhLAEpXELhCkZeARka%`E zk_xu85kc9p6Am8u5>A#6`4+Zj`06yc|H=7PwB6}8g<%_dRx*@%1$jbE`_043pKawr zR;7Q}R{pPsI_cEd0c@b;_ZXEw5*lWJ<^o`;kG1%qSDUrZ^sk?_sZ#O%}XLws=a_ zq8_!%)qWUlc6{LBm-cny-K!v5i?-AXQ?%koADnfz^te!mD^x4^+WnMn0WW?K<=ZC@ zF;GVL-!0KMcviJm+!Lz+Ui_Btu*Ht<`Fv0McE`Gb-hn1{g51LpvS&u|#ajlM=O(T20{(C`(u92@vlnwk8@J@FX!anOQ`?f^q&6rjBESt zf6N_A_|%RQU&m2oGdI7(6~=<6!p%P#+4ZG>f`;~MT{aV3Nj>Z;lf|sgft?JAj2m+h z6&?$wy!5H*`wl3xzPSOq$7+K|X=i?n;QaTg2Z4|@-HQ_Cm#Vpez*vkmmU_$>evHY7?Y=jR#23vhA)wXi`)#=@D zCg-2Kheh=O*U?fVMbS8$)$F2bu;n)c0YddS>EqdGT4xst$!BY>CQRtP+K!P}cIM=0 z{I1!4*fiBZ@eajTb5&vzVG9*<0@ob%i}$)3!3+d|aV^AMm6@9*I5jtfXJRZ@THqBa z%T)fXy_E;JFL_va9Sapu;9 z)4zP>&t6ykI*b`65!`3O-1`+roD6IZ;M_3f!j|A9keV(~KSV(5Z@)735=-128)TF>OhZ$#h+P1``G z3s!Gm3o|jGGJ%F?bv%-6%+ck9FGNyt7<563R;k|69h+5UtEr;N=?Q3((2utVW9jgx zsAv9BaQ64rDx*KDRql}d;DN!W_1x+U8dZw|CIs>~$;C7up>oskH9BWM_YKJHOHFzdpPE?}hvM8()A>aggDD@&T&^+;@5_%{zjLSfp4vfIj39 z3K8Khc-3;(Lk2~XmDH*~4(o-ud);Ulcub(|5$i5cjGbA2DsdYG69<9f0>P4pWBNrl{-m-9wE`$;-n-HU-?hq4gg;M?6n+kvKO^binn+#g`$i0afG|l~U zZ?aRR`yuxx)8*bYN5>;#H^1DQM8{?~v(+|0a(a}&-ehg-MEPxR;-mdl7!n6={C^RK z{QrAM_OA&;GqY(1KN9&?*VcO0fjG+5&7Hk3yWL;E9ezGM**-o!-|s?zUE$xZ2kE0BJ(IEH3jrr zugLFouadcw&Kp*`v@iEXMX>gg=0_gHjSDZcHm2Aww`OWI?&&c-!QCwR5(k7OK<8+( z@Qy+WA+j{ztlF{ASL%28@v}^ndgHll&Za_DH@d?JwOX!w@dSmElp-|8iQ1p%kWWZ6 z$~R?9J(qiEcY=Xg_*p50z^8MHDA3AN;8|teF~QN4l)y6y_weP?Rb`ooop^yVi`-82 z`gO^*&R$iU?}?`n3Wch@#) zSwz(yLRb?_RfPIi!!+qn!!+qn!!-G&VVe4fVVbPEm>4ijhjgPB8&N{=E)COon9T7V ze;B3}h#+zdOoT;pKMm9JmxgKd2qQrU>exl7-(L*V%t*uZ67;8GT36$bU-%x#1jLI0 zx`P4%VFSM~jo;tQQbs~SS9LT{(a3dG1t=>kYfxL;EhwTd-v{LMMYghLrcWQ?qFg0Ktl44 zgk*q*27-Zck(>L})AQ5T)&LpVQ*v^7dHEp@4h#&8?*al_N=m{)LMu2pHa0e2zka<~ zSur;^pPQT0P*ZDfYPvW(LtI>(UjTofArOfA`g&yrg^TlZNdbZHCnqe7jA#1#ySuxS zBO{+TH_wia4%O6(i;FERESQ;?cDA=K5D0%izu@3tYiny&W##?-{lmkg%rv3T|y|NJ>ibu(Qj`%1(`ro_zhfx3%@<^XL7Y zor~4g?=CJ7I=b`4#k14XGeg6j&CMegmb3Z!Lpixa4UKQ#zPZ}kem^~ph=^!sXTLBs ze8t8#*V7|SL^M!eKRq!Ks;|GMub&ncrl_QJ!N#`0%DO;H+fr1t>*xq>C1@~gA#>bTc50^pB%B^ z*PEgtz84FS1tl~L5@IM~C-<&0#c>VeU5bGtZU2KbiJQLQs->7BnzW#02V=2TaPh)K!_%$_o%nVk%8H&)6AkHKqJ~qWX zRwE0AQdU*N@*?M_D6#CpA_WTkIB-QwOf9s9AYvw*k*EHMeZK&bGUE|`M5;O~SLEHp zpnx3xTWB1i&&BR-DV7k_M#vT^j6-vBzlK^vKVC^sLAOB!WGQPZ&BP|4qCd5(z`X`q zsqB{OVpHBQzJ0UIduOygmOQ?1IL`zjVxhDF4&_yPeM@pPtdw~prsm)zVNH4CYXNHZ zt{CgO>{qA8*6<*M7k_ zy!!*9ADs5zNvxOd4@w_x?+?jg@*NB-QQkioQROH*7}XTtIT+JX;rlkOXL$eHdn4Pj zZxeSO?|l1U9>RAxc`xPu;glV$>~PwtVdrqhV4|1cUWp{VJ|cLeVDD} zb_FDB8nRFkIoDzrf^{yqB4A$Td0w@#Fho{u63e2IbqCkD?m6#i{k88Np|d0g6~?q{ zML_&E{Mr{C)`=Hcwo149`LC7EmQ6fpewP(x-L2~j@$Q<>r0F5?eR_TadV}s;rH!8R zqc6|`Ne6~c-`g#CcU~L;m1_v!yG-KycvKVGge4?{jCQ`x_rhKk+c5kToNd4EYh>BT^j3^Eo^Bh^Kti~Kmgj1WPKvU#~b{KEr z#!xs%Gge-t=rm|0M1s9xIqaH3qjj)?vkGg3icR54n5Go=YS^t?Cpk$n-A|SxQKKa- zQoNs?Sft8U8?6P|xi+qa+<$en7V@xK6(;-0C)74dd~L%v*v~(kBOoBlK0il8tOyLVNg#^Hc z3oDAZ6bE>-L-BwC{?Na%toV^ zK*2WAf#&%wpbH#_Ix?YL;FjSF(s-uIVc`pc!Dga-~Tmd`T0tW@< z5-f3$U?~c^4(b8X{R_vyaD|l$uBfmHt03od)ldvZ_WZ8 zT-up=J~-qw?#TI#$2j|wUra_}I-b>>q~F%_ie<3FzKG}a$5xnkhM#jg`M=I44U96S zt=AaNj*+Xh=HY#ZnaLRZB=af-nm(=O8S$Rsb-ue*W)W+1PpL}p`tnL0iv*P>ulA>k zd{8jB!GYW1N*kd6w4rOlaU$5$9aLzrcGyXlj~JNU--5^99U2k8mKNdSwn$-(}{SViNRCr0GwvJidj z^`Tl>@#@h}DgxOb;Xb0<91!;>#~%nmsJki(#Pn0yn*OH+!$WTC#KYQxM1{7vB=pd1 zt>BGkLS~rf!{8;)a_?b6+;ce>OZR#B&5s5+D@RMR-~ofqDBx)5a)R#lY7k3J#p1+h zON7jum+T#@Kj@+N%tK*0wOWeeD&H8wkUos>k3@|3T1Ct6JjsiZ!%@q3yY(uSHE2X= zlQj`i>&cw7V*J)B`Nc!eycF|rPX->zlYKdJhPcg`G=W@dz1WRw*`x$gkm;dV8IT0ElEpEE})C--B5)cd!I1Tr463|WZy5q~=fM6+4L%iGz$NJU~Gjv@OHn}!~X z;-C`;D}=e2GjEuBRJQ;sxkZ>iVhjTFoC~7(7o=7fF$yBw!#7~$!QmTp-G>34sGB)7 ztL5hs8u#5RO{i0Ax_3u;%DE4*E6@?g;S80c(^#44OD2w+)f*3S9;l2Co)K2jR|)gw z&e4R;^J^}&1vWzCXkkqyr>J}#JELhhFWm%l?zUD72ldf0NIGJ9H91$7)IhMj8QLvq zz7vubviI(Jipb7tcY!T=zQ|HL7Ne4$OD`1rFl$h9dnIkT?7d%ay1nkxUH#QH$#B53Bg2UH93M)6aR2o;ScAGW6ovzYR@$?7Gx8)mF?k}`G8s0SVI<=A2 zgQ$CN%2Rs{QgY2@+CIA>74to}CgKQR+xm191pZ=MakI`z7GZ_6U>U_sDkEToGjw{&q8-Jy;gMD?DKKMukV+%Ts7I=uFh-nm>G zHT?Dgn61{-eZl6!C}pd!mdiI4Un*LW5N+8&$dcSSG$G;zeolaVIr^~40qxB8_mK|6 zn{iywJD`q zN=zWq)sJ=}tk@=d4hCr28Q4=FRTB4d&ofNx>gyYIS89u{} z<#d6i9^4wHAQO!43=Tv`rxSBq9yeZ9mN*GNf+>VMiV3?cv{(XTUaSHH zU5`sXr?NUBH%(->6b zfwnCO&q5uuIk2sbwkK_;5M{Z;wVgNS^C6iz6H>PkQmn*i zfv^7K${0$oQD16gp%TA^!q3iM z(dQm^hA@SZN1&H}c)@kAPS|mNpoyZ|LE4zEkmqDOwuJ4aS&i60!lw_FR!^65dOzx1 z8w%~6-|;ePx?O+Xy4mwwJeeGN4&BRXX>_8}K)ZOZ)!Mws)s*>$VIs2cL#s?B@aev@ zGPSmUKUM;JwN$)8E!fk^V8E8~dK@v^Yclkr+0Q5MKvB^bZ3)F(7DUv`h^_2?FF+Wl zJVilPj7SxzI9Gya@u?n&v|~cxc@|!G5BB9{Ya@*^c{r+v3DLN3tw+%uc^0(!2q9SH zT&}Df$MD@iqYRCCN!Ff#UvzvN=zdOJA0ATyI%2|7TeI^EL*%EjM4X4#Lt|Q3Zso~} zpEfS*TwBA+k5!`KGD;HSH?~MXNT*pPe!RK4oMdQE&5~l~&&BRzZSbZ~3n49MBx`$` zU=!&`aOIvC;a*?>K_Iy&YsMz}XnU@!iP5@l$k)I=uP0h*eJWC*_fRi{x6O$j1Y*=2 z;pKN))1FJeC>Y{p5pB%r0WlgTgB~~+4gFuN|6`ICfu?`!74rIM8isJ4qNeR zJKpEA$Qfjd!a@=nm1eL{v1^!+9zwj`tnC+DXEf-6`%vaom*f1B1_|YJCi{3V85;8Q`F0>giVWL z)D9!Vqw=gzYy&t0MyE|#?hnZ&0V<_dWS)rse{L{}!;7?{u5ocuF5_s2++&u`-v@z*5Pal4iF8qL! z>jqMsT-Ze4jzRxKKlpKNODHRA`TeziUCf3;@kgr{XiGyMQf;T)x6U6S>Q%Dlxy363 zFih#OfM3^D#plB9%c_(+(T+-ONyzr<`^{GjpEpmOO*1!+L)Ui@rZun&XhRV~O_XlIS;|aT`*<9+5tgrgjY}iuY#jCVR&R_{TmMHyVNJvt^#kjB zZgZ$8s#FGzk+K+QYC7R6&D&FDQV;XAWIGHpu?s`65#A+Qr~DLHF&jfJ^9u8_QmUDR zS{#8I2gcZ6IK;>dqVInXOVgDm@7h2wmi@qzihrXaf?#%=aodtUy*Te>%?(V~&8F;G zUISiMXNSkq2ap8S>#vJ|6ou~rlo;poibe(ZBM?}3Q0A^@3nH8%70yBkh`L`LXbKwB zbKPZvxdCY5&jEQtGaeuMrhDh1saDB@C$cNn90%h$BI-kWCNzm`lT zc|+B}E$YKP-RRoIvU{AHiBTE_;bzc4=_Zj3Lqu3`OriWB)x*x%26DaJbs_KmFqSL_ryPn@`)G3W zK|y9OikN?=A6?r)A)h`Mo&Q+4F`zMgR`f%Tl>TLDi{&ts`J zygZq@g6>$<ar?)qzehMdd+)(NrJ%BeIyfQftPC_+Yji zy!1LJOcOzKmpjebs)sKSA(cH7LcWPcAyMrB%!2ShN%bR0NR}Tcq%hfy6`MvFP`%F~ z4Xny6sqY`oiIE#FKvE1}vG32QTptPCCb9;}Mya*krEUuTkQH=h;j-9kPmD{TQ5$5{ znmX2Ze%pWhiesmA5zy~d$H5T(9sm7)Way`v;*g{kwwaI5of{uo8OKQoVlmbSv;WarJXhlREZgbR6<#ak!EO8EVjC$}vht2LGN^OiDji}T*v+aK z6vZUH7~#29)3w|2>6Q86Rb7Bt(9yR`A(=&q!K8~jd;q1Y3)2h<8;@y!z((msT!uxK z?oop9#7JE~42o4lB9b!7hHqLEtoo)R^@3|4aA+9iS>dpobQ>B%TM!j1-(7<28DtD& ze*5nD3&i6(sEJ{(hx@t=<#AuREAMu*E$TF6#(5p@@w9}w#*y12OeAa=;Z%QgRR4gI z=LTXST^vOLVB>47T@}9;pqonZTbzK%t<2zWvHwamFk0lR+XcA-<)0M=86yu zdU`B>R2E@X71_r^Yf|oePO7zMw!CX2o6hD3qgUn^mWR4=@IFIyc6WV)ZJiy*iaM!t z+$l&5&|K{D&Qvx7gHdPS4>=;R8838*6An&PtVbWdSZbqq{qa8ElMzojL{<&1@2r3m z?~ExX-Nx(NYPOWKXF~_MRxK~6V%(=%Xz|YuV|-kQq!1(rgrrZfG7n67i6F#_i;YPV z7i|JkU#}9KBffxMB<%$b%V(`htV`DxeYVC(&w;K5Uo*$PPf2e#BRk&Rtg3A5Y4cQT zCEu#O$=JzA<6wLjeyq5mG$Nu!vZBJXoxs3f+Dr>bzPBal79k2 z=7A>H^?jwW@lj^e{CA`eYgFV9hJ|_Z@!8n}G9DwCKO}k`;IKAsWo?aTWcy69IIjrO z-r88xIh19`7Iu<2^iDq6Akl{E5P%IQp7>#X-zV2AEr%7UP1^kXOb)A4yPYum1j7daX7RkW z&sN28iXb%?FaO34OZj3hYqnTj-j0GCF8dzVr2k?0lWX#rPG-;oD0KGn%RM20 zbbS^3A%XDYs_fSj>lH^-P?^peUz5 z0Yxw5H$a(0^Ht#RGuJS@Pnn~@_^wRD&?ub{;dFd9yvlpG9@2;Avy})Cl3>Caddq{( z*xvXN_QN*{4<}#nF?@bE`v_}+*)v0JM&T2L6+?h z?-d3H+Mf8fi(HKt01sK_YE0`y@{mD(Bo7IBXO$@{LH|8X!Jwq|>}b!CsiZz3=ruq{ zULy&K>^P#4Ny7u}7KfZHl9531u7_aypM!?Yp{!&hG5D0+B1}-OJB>^MXa}SNA>)9a ztpb57F1g>$NXZiAQE%wLJg)>$Md&(!Di?7DZGMtCI=As95mp+cb?CQP1(eY!jJTC$ zP)y@SB8s%$q*XlN{k=BRtd;g zZC&{Zo)lE7m_lDR*JTj~(R!O&a3YC~U%pKr#}&$2=**rZZ*kui2$; zK_HRM8Xlce+zIFiIt|lm88CGw`lrKvKF#r44O_Z1yJy-pj8z>ws<5RZTlY&wLL4#9 zpY++k*0G*wriAU4(7Sdt==mt~Xx-qhC2zY84c=HCgj1Tn^S%@SEp=K(UjS%<3dO$= zrYi5b2=v}{ml7NqLTx2!bd58(Wyn2Xq5b%C@0esi&Y(G%=hisH)Dg=AUDhNM(WObn zM?czsDP3*5i$JwCLKMgFYz7vF-rHw#L(M5A9z|R0Qa*5x=-1?&8JNHo&uu5M6Q7s= zgnxx#>6D&+K^;)8;2bP6tRD16fN~`$X-40HFp|q&$yPCJn$)evv-9|{JKo+lUWTs= zL2T%l58^Y(n$Ev3Yu5PGTV7N;3K1mG?1Qi&V&3F_8kVnTI?;mE=4z{WL#K5g1EFIY z4LpK9sL*v6Wk_!nfsw&;`eB+nl(qMty^{>apwFnCWHtNM=dyLjf}@FPMVb<7CX(Iweg%;E?5l{(|{sf5q; zuV;p>glQ+DObhDSt9Y)m3PVo{5J*=dg&q~P0S9?s z%HA*EAXkMX%5ADi3D1eB%pTtp%ik-UM&nj_0>6P4t{230OH_q#E1~7PP#7~Ys@m;Q zBGrDXDCQe1c9NWUx>;WCV5g%0l5L<8-|8 zoc^1BA})R7SrjZe%=+_?U=#hUB{G^pD-8XX;xWtKoXBxV5AQ_ZM18XdMHeR(O0v2^ zCZJ`7b3qGa#z^z_k~n7SB`u9W; zV&nA%lPuwZSgm?Q>x=&>z@+|b0E1=q2f#q#w|@eR;L0BW(?goLKEKO&#Q#467_V<{bX`P3u9Z?JWtw^;%%=#Rzmf8F`SIQY0L(J*-g2J<*H^?m z*7sVex`Z$MWI333f}ArGbRIcMh>sx_s`5L)Vmh0Z${0z|%I6=aEek7L9gK&r`Lv_4L``&TyXkN}g*@&jO`WKc4r(PiqxqkjNQ9{@0$ z-xMRF%R!GmVn`jvN5mjNMa!`Y^q+>oCcQ6a9GyJ<*0=! z@2!?ybxZ-(78}|xttM-jHetrk}R?dnlaN+F4D3sgAKLvum9ff;T?}QfPqUR9_4La~fAyolM6^7x!wRG_+Li9(Q04nUm?$W#TaSjHX+#AyLRG{i#^~Ka+ z2Bm$x>AoovWflhUxn=1;0w{yK4WJC~7(|*NvJWDJ7J}EU-PG_`5xrSJJ$|!Bv7b#KsN$vzG4=Yj`iVZx@hyv&hDcrD=M1COM&;_ z;^!r35?Sb|YHTOIZ?xvnqpy~47{AFcu6Lc{;e(r{2n}74w8H`SWOzF1Xb*V;ae))q z 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 _r3(%fPMaI7yJR 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

  • 3O9I|Ve(IyA*wdPvM8NhtpirZ zeKjjln_M;ttQ}kF2uU8aCnnLtvz8|0gw*A@f vQ*?qXGhycZi4QaRB4oy9x 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%0DF%htv^ejRUaacgz3MG5S>l)y#2rPmS4vsE)- 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%0A=u3a9aQ^cgcn1Adz^Ltv{+O=*%ZH_u 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 z zj;aDpE}q?sjj8#d0$mD6U3GA8!5O7@>-=2s<%q{J@F#B8Z90Qz?%t|HL#{lwlJYZ! zszk{*YgWYxH=bWF`E+Oy`E%)Y6NcED0Z0n(E5nHB5Tu!9nHu*kwsr{6Vs%&Ak7|(i z5*0yMo|E60!BZE{mIf&6eRb<(Lk9U4s_`{1QNSQ_2o^k&B))u?B<^;jd$^yge$j8H zs+3}Afmwz!#c6?&Y=n=ODZ>_Z&GVt}TZ<@BDF3Jl!M&ygonD_%05|xn`23-(^N5>e zf-sPi?C@E???Ix39?06t#OB2=65_k)m#34Rgm$XO&av(TLml zm7B#Z(p>Du@1s)7jL&D;D!mtSyA;<<(@Np76n?g4%ijzl3OaM=>l`om?68fC*uUsL zHKYcs`Ep5Yl_3q3f5UACH}-W%b*TuONxagps)zrtss}Nip2;32`;_w4|(T$y!Aln{9wVqF{Nq~`PzPT1ISLL)=z`(eFI^zd}eg` z=yt9@SR{MT;7-fK(WhzaRbTIX~v5=;`zUPU6 z(0eeo&ra$UsXM|DUTQHzo^FxmbUw7lD%WP<{#6oo2yC`tm>PjV+9YZ~K0OQyBCo4L z!6XoGHok_h#|y}!6wcFDlHyQQ2w3Rjv=z?qS`+R?F=s;PP0zy+GuvsX_7Ew8K*_~2 zlpNf4Saro_j%(Q;>5gw>2F^@yMe{RHw2@guK_@hTK`|-Ppv};`4U}_fj`)gmI+VL= zt!NMX`rzEzAVsO=cb zEp#8m?qrMV)};6UoI#I^x(UicT{n3BS{lWw_ErgyN0i zfUnVAX<{SKDPO~`p+MtQ_SrViV*B<=9pOB-H-pDD>de|TKN+qjy7cpK8b6x4!h}J; zNLVY?pdTi!AFmNF?Kpl(UOwx?I~VSJ3asTd-tUpIuILWx?P3%R)|wBf>*8OK2rhrL>A57~LWo%uL+r z|L2{k(n!QMF-y}=0JjEy_fwLkZlu~&W4Wkv^Y1A zA{!6AJN#8BG5NdVAPn}lY)rB{bDgrsK!PVV+8ZzRpHe*Q$hb%#882>3e#MDROG-9U z+bGR|iAh|Pdj|8J%Tt2NEIMuXpN=`I&6ute192stXUJ!c^qon`Fv1{D;CIAx5l>E>w^7Q_v@H3+k3Sx%*$=6rflY%*)_COcf zuQ-%SyaZoD?)#i^SK9K!m*|IIXsJ@i_R?W8%F;dgzC2KnMgRQ11o8a7T)ISvDjfhJ zzSUPoHtP3$E6^~F>iwo5`%SPN0U;U!vX2zx(=fO?)oYJS}Q3{pM`_d;9nQ1_;r}6J}I( zeH`@(CIxC2o#jV7izMm2Q&~w;Sln|-%6Q^g25v-Jm&R^RT{JhmmupxxB}+?`Ips|m zDF4>C^r6pNKRPr_THF5O6Zn%*AyKl2;u*8R^H6Wn4Njn!G>wh~Q@#eH(TB94p)1cb zITPB!)d&dnC=>BX?yeyb6xSx1ohWw=WWf+z!De(dFHGdAqwLQpR+|R}4ch?OXJ2v< zs^?+R85vdUZ9;dZHZoB9K#P;dX{+pXe=5;d5J@i-k&2xL*H4d*%9(Q`-cs|30?{`$ zWo2~iE5b9(OJsBMVn)SY=2tN&<`$%{kLM;gqMeN^Y9-RiD^8|=v{b^M<3v?xCr;GO zXa`kWW}ILTV=WVwW#FBM_@^&hX^;V5uD>V^)lC#+#vQ4x26n^$}SvHcq@5LZHNB-qg`be&W z1{toKr&P@U-M#A@ROqCFOpEG!P@wV0d)JxgS$9vs{(x%O-)HHE2K~7PmUhJx&W7*r zOF1FM@6o&;v$Ql7P5ck<%X8y?Zvgl4-ywd; z?r+zpAG72C26zEzQ-N>0fpAcJzJ9A87xpc{6?GP@fGY~e^qp|lox|h;T^?86?@p0zE<) z+mhUuLdmHaT~U^eS)l@E2((WD;h@NIWy?r%@V7n83|HR>JtOv7iLY7Q)e1J2*y}9{ zBXKwSFJ~rbOvRc%y0d{5`S?K`Wmb|3p$=<;&rUUKQlM=5T&f{0RklXB20#mySI+Sf z4)CPxWR8UQSY8O{)=u)LY5bvfxiK{z?28(n zT0~fkIqXDc#a^`joUu;0{9O#8qK`Q81{EJ^X0#F&`CyE}5dg7NI|7VRZ*y1G34j9+$lp(7%MXDEV*^1k)NM8G+HgzLz z%jI9*%5CC$g8A|f9;R2~o@r>?{NuwkKM`tscq{jiz4;wT7IqRy#sb{R<(m7xJxsx3 zk|8q%hAMx6$pE#|btQkA9V<%RJU9V~_7+PD9vO{s}Ih~2dbe_HX;I<>Ik(`4l; z^z-H8mKJgpQ9wcl{nKZ9q#ykY!r32=#8>`DM`9e>{~m?nSVxw~cu<4Ke^C;5cA6X) z;2GalpHjqF&3eo)85UioO~Z zn;v5vm;FA%Fy%5uthM`mhM`@Duj!*3*jkwhT5n@n4DM_wq(6S}i8Lz-4Ao)1>aBx? zNn^@hlcn#?pMsYLcocmwoSfL^)11(;4>T`DW?FkE?1igt+-P#y7h3j{<(UGqYOJo_ z;n(2E15YBXGH<_L(4^sAcJ?iZ7B^x?wX-nDEsD@T(^b~Vo$FKDEJd9kT_l-eNzMceF7STb#Vv0kH9a zRfLy#iQcmP3>X0!KAPJnCFD#RDm(Vpc84dt|EiecU+PDIgsfMOdz|@n_k-i_pXv9% z_%o^wsj$Pj;`SO0`jIJNfvBNCd#w zLVm$uoCwqIs-Yhy{r=!B8yjHwBOnUB2KPS=U;Ck3bj%wa-g4+ptA%VTKTUk&H7cyL zDThs>M8}nXSkb*)5~Tcyadxa6^sc|Jmfpgx(G(;2ya9ARz7cC_MZ)gPuYjn70)(GA zd!VA5AkL-!PW|$mC*Bl~4ZeQdMSuPDY2)PH-o#fqI#=+xHjwnw@uIqejC@aXjo`HC zRc4xH`Y9E4J;-Ixa&@P8IkLB%^h$y+Oyj*SKV*&dV@+u@^5@;VXoTR|5n-_DY5!B0 zmMzRx@cC$ z#aq0}&PcQx@rg-_UXM)?zJLGy#S~Ys?j|srX;V{LurT{&JS-=`G$G&^*F+j6C5!-5 zSSSL@i6=HC293DE0vso2e>{eOz~UV{0!6RMH7_6#dp;lZ5sF$E@DWg42=(lmi9uMX zXvOs^yohW(h-VXeh}27pqH!lzw~9%xM`>L0-`Y|OxZb`c^>6@O#H)4d9Rn*@R)M%+ z!_I46?S{`7xYrx^`q7J;P>J>I^e`EFS6eX7au&5fYe$OOHZ=73bjgjy*E;NOF7csK zUB6~=k>QS>gFlO`aD?=!*5p1$Fe_1K-#Dw(Y7cA0-Ei+S^)ZIBJR-yP%%KCgJTz3| zb*c7}tB(9?G6Ih4umJItG6$Wrfe}fbv}D^##+#Z9srVP`)mrV^5b)<~Rw&-jVXA>8 zb)lLqT@3zn`${OZ{!348Xc(r7_`ITBF7kj|L=P7+c;IR%FyH5K5Pc;U)fY34?Tafy z6U}s2Gmf4NpI?^ESbTThB%OPGA1*+_qM`O0}+5r`~_>tWGnIr1(1H z>fJAwr*jtTHkY`6q3Qx)?MZK*y+|0E2SO)>+=BvyKX}VBccV}=ikX%}>4VE&5R&K*GMb0ek`KDKMm4qjUu}a>wF`9g zmhsV>DxGi<{}6K1V3-PBQdAou0@Y8cm#gF>sucLGmo$7B#)zq8O+xLJf=MlV@C z-|}IVph!uU@sg<_NfV+LRmPJHlNC-jy{HdXJ{z^E@G>An?DML3R%`)S6RVvYX@>frd9&5XkED2Gj(HN!~bR_IZIL3KxH z7BNndEn_%xOMk-h&SzSEXq7pug84p4!RI%4Ly#=OhydLFwv^{U#kjrgx-5WjJ<$20$>N`Gy0qj z>ph43cn5dYe2A>d2?OHBx)`J+cSxs$rSQgJzR3Paqp&;ctdGN$0Mx=)Dlw%0WByoLz_l5(DcEP@0Pn(1*nXeRTrYhu}8;wCATu}+X;8}-9be2X+>K!^rK*S6mgT)i+>Ti)nL#rtG3gnj?9$P+`&Bt9;8TXyIh<2*+SiL4L{LFZJ-kp5 z&74}MF2$E=1hzo$nk!uQE+BSuE7@JMpcMyZB1{h_4wGWo;2HuCIpALT*zG{mX5tt? zBh-VV)}+)!5d>)UY7g_+?IEap)tQ_JYGKr z>66$VpfT@@61k_ggHey1zvug%-kC`ppfRroow*!$gS%$taWv_txzs!QE|)S>Uw>RM zE?;q($!T4QTq;%7uU*!gO1v{&yQvbn+DHtoThr*4l&9(CkV2vD<#{W<-1SD~<49O5 z>KR7=YxnPNt9JLxGkI9>e1t_h5yaYs>tl%^OWpzDAF4^zj~mfSsvkdi$0nrR#HXjF ztsy@%m(9b2@Q;J%r3yYw%2q^UGMMJJZ|d3&h7km)<(Y__`!p6z7;J!~p;d40V6ssu z@o0thV4fkxmwNCSbamBFTZ&Tlp;P0#$9VqktrPQh-qQ0^OZ)dBZAp;9n@G;LszE+A zOzg!hN=mPYA2WD~T{=>gi!J=we(CQza{kscufeU26)$2NbGSwHUAG@T4+<}nd!%O< zpwpcyzXu_gvU(SYyI@T1o6;4NG{@s^0%0H+makQE2>nZc*H15?1GTVSg7(WKa^V9#LDAg+eGd+eQ}_ z{U|5DXG~h_LFcGVRB*{0uA|`g!Z{dHgVr_?lhver z=npSL2&;%TRoh3&1v(yXAVLtDI>^(QhOor7-k=3d$SWFucI~C{z#1a+dd?GJ6>E~E#YTPN^&e1NQxqeB8|#);%$w2xd?vZPvb;a&Qg`#fqc#u zrPN(=So-)~KL3LO+d+>lgsolfTmpN7;_{Wg$(V_@Bod|;O&DX38m?=V4hjaFx|+pmn}B>ZHk z6g)}TS?*-_NuDOMNjkR{9?XlaPb6WEus+mrdvQ-)Nle}**Lf60Y(ZBcO+ z%MZ8kb-~cJpYZ2UJ+32cAL!t`AuGsXz1c@TqqNj7u035^2*;Ed9H=I8FFo7idR3$Gu| zgqXh;{q=2Kj^Oo2%i(BhTFfha{b?yr8U^86*xm4H5w~sS>}pZYoNB%#X6ZAKjH#+&scUQ zmm3uTek}m!FnF-?XEGGXM#n8=-jXbVFa~!~%F#sb`Yz7(5~7;%*8LDX#b6xr?^4J|0vz5dw(i^;9DN72WMQ?wuck)cvHTjk#yW=N?Da zfBLZY?k{w=!+$S%11s#64g-&B35cN$k&5V}J7hIlhB6cl!-ifkFV;U)7JvJ#>NUI) z9RkC|QQhG}*I-PLSM&12-n{gaXy`&}zz>@i>FkCti?IWM66`~yBHgYGfY4RDZZStc z5DIC+q;a)Fm=o){qQpM9G6^6m4A`I$siu$7I1&(~Tf#V!H#1SzrfUd;q7DUtjST@e zqw_#>v_hfIP`4zI8%kE%w{r8}8##Z39Tx$*8@n-64baG0*o49Hoy&SU7ofXU_<^AV zvK-50!?OO!(zEUGrlZQ@i4NC%m2r3Lg6g+H{uioeE2s-<7Hc?oQ0H58iEB4gtP7QQ z-+#8L|J*ZN*!T*KhF1whT$kUBSsPIV@TYn;TKsTRx+xWq+c@0u&$d#dt_MxzHO;LZtfr0#}&8lNEQkG7-f>;aRUDjVR~_U~kP zDTyYC>Y?dS(5VzIXh$PkNP|hMZT@PjQniy$h(^tu>y{z`alrOCtbBR(rQHpt+YOG- z^9NsTzMsw)k-ht5@xvQ3^p%xK&kH6_wLUD|gKw>P7G2*(Xxo1L34Z)nMw-7W!+&xX z9tcv?(bxaMT>SI+dx;!EvF3_7?1f6&)r3wJqLF5AfJP{fz?LeKP+6D9V9A1mv8Uas zrVP-TMWvVs*F3e^lCe3u;Mb0S`1rtwABoC(Vr@O4M1QvwYx)=%bOyqt|GBE**=c+l ziG$Lct+yVSfYxy_8-od`;$^-cu&~DVJyif;9jP7T;h;+D-(GGHYCr;s*yf*QMN4H_j3>PqE~2bFEqbQ^8$dduxdKUS+-`u@CTZAJmt`{a)S znLRFkIa%?z;%yjyfOY(%*5O)Tl^AQLA8F=26-pUNKz*&a83PAW)$;7&Btq*C#>1rb@sLRL$e?_w`^q1eL2$;G2e}~~rz`yF?)6st?tvuT z*CMZE1vVgyk<|ISi}>}Yycx>8gC^$*&}*Fs{o44p7^t#}YnM49C25LVRx?D#D&O@F zY+cp9UN7~+JpbzsKC$&u-3)q#%}G7B5vOP}}duhh?f#+bV;81uf5eO;f?^XPG}hqn?=rF)+Rs(9c0MTtIe&=Fd}w|wynyJYHlRrLAO zddSfu+7lwwS;wRFZhRwZBre@H zg@yJC2Eh;J+&3jUpW;bWSr|NX+CWeQke{_~T)owup;bm%>@HX;{YI%vX!Tq5(cxlF zghB;e-%eH^{g`><_kVY~8Qnd7gt9%zN}DSB{y^EDHhlXnE6t(Be5xN*A01Gx^?5B2ceKvCwagtY=2WS30K?}1WownzTxh-6;dJCy`G+(3S;74J z&ju^6_r>-N7E#3!n^VsSI169%xK_-)JR3|9TK}bfb!@0{)meOhXD`xg`2K@aO|fpE zQ?5z3>$l4$0rO|XORakr9zJmQFV6=(sg_|-yl4puLze*9%#`U_O`l}1v)NT3pYixE zqucDaqTa$O`$fZH_c(nF0)qpy0z+`Eo+YW0?oh;f!{0k42jOd&CHWwweinUwU(8LS zu*ltx^j#^>4Anh1u+$@ydWFygoGFFbqxw8o4EDJsPcn82Z-I-f#_ocVmqt@msdAtJ zyd70@=Csb2($mnk?cb)ycZ_=bJsNsw5tmsrJ(U8NipbF_l-eh;%+fova51Qi!4dA2 zd!H4W6=CTTn}3H*OpVS*bA0a7S-78OeD#l+XNJJxs)+CFUcU5_A@WUS{Jg?S_1vG5 zRAd)(%0KIq<|>vN`R5kKZa=Ziji!$et5$1(m z$RmrMcE?F37P_XS_O7d|;91A{x=<85H6#-SeOKg!4=mUd=QTT8wW{T8fJYV>Z@tJz z6kHMtu!9QU-eC*vqaejoWymv7OZ?%tG)zRbPyXRRM9p^3vP092?3US~n6Q z+2$mxzWb^kb2ogyEU_T=vfEs-HDUND?o#AE172bg$vom&NS2x?JM|L`A3CI}@Y$3i z2Mk#UvJxepi~K|3$ff8wtn@UvS$Jeg8Dn2Yz{oh zY0hQ$v%fN?`;fJaPY@s5f-1H`0nN4$e+c!Z3;Ctmp-?R+aB{QpqE&cz53)!NHSZRc zwWb*`%j`EgqOIgWnzWMIRkW*#{uQ&ChT2=9R&-lCeg(53!T9>g&3#;E1;w{MrNIap3Am3m`mynPKJn# zoJz0n_DOu5Ab^-YsZyhre{cOe#UAi|-T!VpVQ6Fg`PX++r5=y}Q^zHnbfU@uzV1K$ z*N3}_I{Wls-A^FRd8(N9JWi#rM(E=3zY5F%>%K~T_Us>9|L6-;o;GZJ+F5&d{XWlC z>aQ6evfmee>$r4!j!r{J5$1+=-wWt=_@#fvEbF&*A85~ExcXTg^3sp@-)d5cLZ}#; z8fV;uFRB4ETwB(16i3W+-VFy$W)eY4+dG-AL*SHzhX&DaYKqg)cWruP{|TOXnc9F? z4NPLP)`kvSx~v73z!h|(q`0<@is=Ok6m%CY5u@MQr9Sx==f0HErK*La#JN%M*tA;ylYEjV-&MKVx1 z@nRVk&Ag&Ea_Oz24$zH>Vre>$-oh?M0nL(b+{`ZnJ>XV#-X3l`umGd|Gz}nm0n9xb zKTkb@Zt%Gl2x=%zepmn3pt{Bqbf%4UdivMxEgTDrG z5rp<5K2bL;+M-@aN~4t!@M1ras6=Q8%x=gxoZr{07`yXQZ+%jk+*b67Cx?pK`02Ct zBGa(*k|Hxyx(!0_Nv$OXXQ6lW1?I@k>O-MN4zOHX|M_o??m(UMQHVG<(&pZC=FfLl z+kU9jNBt`&-}!w6bdNqvc(2mzR)!PQencjpi}>`en5V&brSTu}brgPA@49=#qxpDm z|2;6%P1;Nd;On5sWb9=Psf4l+x#N2D!G-GAZ~vb!R0jZoiid!$!DQEPMp2wzz2-=d zMpG?o>n>&JO*}wXDweVKKb8g1l|5ya8fhoKeQb>kc%fdS*T26|$)Li1n9jbLv@a&w z)d-Z%mhs^7?<&Ui#JDoj_myd2HdgxbItvJ)4 z`s5*Zy^HR`XlZJDK|SntVsGuwD)-!< zR9S;)F5B-XPnNrc+r6GVkIz;07V z6XAz5u}A&39fx7LALTAPK!d}<+AAyeT5~SR>XhG{{MmeGvT#2ta&N~VMWK0k=;<;G z9m9->`HUnlb}PHcpqz`D8TYlURC6;Hel$3YS{-P7Ig3Y)pMb{4=}fE!*qX#3(;iSd zfOJ-5S8f#oC4t$sqeCoO-S8p1u3Tm3*?1#xx~+&2)gYNQ&MHqgSQ{y=W0V^KNpF*_ z?hsiUqLfaY4~p5LVaH=gKD^^baKJ%pQb@miT84#**9}hLss_c-<)wLnOEHJif=+26 zjbM-vU(fG&kWkbPGOLqa1tE9cH+ltYx7d7z{c;pKSHkxBcx&4+I$ZIOM9o!|$H_ev3N*4kBYj}Q5~Zy%oO|1CYt z&_4O`K;sjelZRF|%^!aI+1Iz{LFUQPrcLZ~%ppNdlV)1wT3_fr=`NoIqdxjIg867K1w02R10d z=R0j!A1yKA{a1ns7mOc6rsSkCy$bKhOiU0Llgsoe+q+!soght!mg%)2biPtIK^{H} zcMDK=GAxS+*EJoXD;+c1d198xlGT*auQwX z;HgN7n$B`8JzUDz9gLDCkRQ;;L0qADu0un#QqOeCVJx0Nyd-LE3g&)48&5wh2N5WU z%O?`1Wbhp?Z%OH`et?%hW^2e4l`&Fk5WEpTr<5sXi57K>%rutJKJ)VQnamq1+{a8_ zP{8CB{tp&haSNbd-S?O1FjK4ck;&_D6Oux*9f5rRA9>*&T5zL|M1Z4BNIA~Lb12Ir zk=ysoA>I)X0X`Ehzx1~OK)$udr(st($5mit8u9fXZbAoT8|TjP-8=K`;?eoS1-RD( z7Tl{38os~*;q%=vS7XDEM#cw^D+Ymv+!ObDm`<+5B|T4K8y3UsYggtd9*Ws#0ga5G zrVV`pO=CvLm@;;ge1EIDI%&b(6EX^wKb0LCCd=nL9gZ%B#ca;1f<@{tY-pKosA2n4 z1o*YjCi7x`{;282+pjBpES%ZPXKm2>*(~IAPLs|h`BaRUr(7cKnUnLkowkq)l?2i% z->R+_j(4;^N|KqYLG30;{YHu3$Hf* zZMWQ4Qn|`_6yk_B&>(wcOWwE>3NIobBc24E&Tdi~Tf(BWi3|wz6aw)Zz`j5O@pPpm zQS$${c*io+DSVr)LHXm;upEw}^h;!=@4}!_^s#BU^R54Qkr{z|6n9(P4{RGTg2Hwu zJ%J@?3XXCgh`SMoRE@ZaaDSW(Rz3A`I!v2;dnQJpz&yQ@R-ap}g`mjps_77E~VfLk=9on5dDH^i@=ytK6bA z{+d$asKLC2KEf5$L3f{JdJ3Wp1dnUnVVrzZ| zZJU#>m@=G3(9iEw_k~Mq#zs``1v??X8empGS08X7BYFIVQ&ch|f92*Wy>ZOTc+)zu)*W3!9|g&ig65AnOK0kZ!A2H~)q3ux6m*zCR;R)2fd zDStEs;&-wv5i;M7I6a4Gs6@vV;D{qc^rq4EuYJ?s1tfs6J>y(6#o^{PlS>RPC zcP|-lM~k6EL?kPrR^-BX#XTIJ1y|r2>!1odm}NII?ms?w)z#cAb}qEmj=fTpv}{=G z0Tc%%rtSjQ+hd!ZYU7^L=*}i%o2q&_P#nbF`Q{!fnMINXh!%+MmK_~&PIPlg;WbTp z(~sHYgxF_0WFx3v%0nN@Ye z$S^-Yc<<(%9W!ltE54H~dv=hAidD9&wEkhc?1@>Hm2rrwUC7amYyi5s!izuMdaLTY zG5yAhYW>n;UK7##nMht?syt7vmYa*QA+*%*OU#9=!+ko$zISu|JmNUGgt+5KEp;@Z zFX@M%Rm;tXpjoM^sSS`(G+7Um5dillbaa^kj|Q2Mq9_6^lG)W-F!2l=;Eb;$&??l| zkgYQHYSMW7_~0gVN`t$a^&m=4esuvgSQ}HFyjf;0I~%>Lws*JsR2#);oFr-NG6M`(E1n<4>Lx)?kcZxWa^yd@h!$K=<7<*N@zUa5TLX zr)V+Q#UyWuFV(>)9zJ03o!OezDf$OF{?ee_+h?D*)8mI0(b2HGiSMeb-ux_0y`@jl zTG|)lA|&_{FEzTgcsaybg5)JZLNIw-5nia1%+@&JgXp#bzGkv-7z9?u$s3n?Q^+d2 z+5rNLzX5J4g{cSKM3o6V@8d-SCQ+y$88XnAgqr4zK&MBMeZWm7rAht`RDlk@bLV?f z&8(R7Q9iD7d4EY|An)!vz#zP%B>Q!<+hYpdVN1IMo+nhd?TAP`FsxFNsI{0l(THDksDl%Xyg~9W6RT>y85Hziot=MJ?2k?`z{C=?Y|Z%ZpI~}FFe+ep z5_5Fyreztr36PyntXtzlJc$tC*m1pFYTlD@&fk_=$7p^qYd;atp@8j+O#=H$!Us9) zm!JoeUZ-wHL&Sblx7b(M@mB6@|>?#_WryVz*z3-_lMjLB(0d+NG6U&(_cRlP&u+*X2MLs8PKTh=znut123Y{C?bCoa ziuF^)M9_IZFQy1q<=d|uB{XC`0!57u!7azOFa3wM9kJ>@4nK=VzrRI+6O0wmhke>_ zDAJFiE$gbGaLt=kUu-rihR_n^tR|t-Xu6WW-FCKGf)VM0-bMT}Ik!hr!&XX#L;K7= zFifHB*t=mWX|(Asm$6CWtM6lQsuW|?4s)X6Sufpd#~)rQ`G>Rf=Vztp61>vIbF^DY z)t2Qrz}ea85|12aVp^UoU3YGbEf}?TxpuFnTsI>uFfKn|m5dYC`q*;bS^@uTuev$e zBlKc(p8>0Ur^2y{^^!8Q&RqTW<*1E%&dQ~YUz3Ozo!5XED1cqSWvhag2}}0cHIs>l z-;_a@J-g^8ES3ff!KV@Dz4^>UCSMt31%s)9Y2^EEs;&A>3!#zYG9mHI9^+|RJayrc zeu-w7lnmE-vdESE_VHrFR6Ria|uBi6AHpI;dbE2_Zm$gx-4#gx-7aMT(#lrB@L_ zK#D@{0h|%%o0}{V}DlT6*1&>N&H);ue+cBwE#+S zc_@dM#$w!*9+_*yhew>UO(<4=R@=$0h8$ptxW|iBVxE0uM7ny;ieMJ51e<>%g1mQ& z+U;-22L6y(XiR%GHw&juhh-huLf!CII+tzp&Cgd2S^bh!QNdn*y5H;k-A~Ck#%Kz3 zL|B~tfxb{$$$-RCk7;rTVbIC@5=(tB!PhK3nfKCZ5-o=>K|xF0Oyb~neIF;^WWky9 zH*igc8Tvu^&P0K`8f~}C9YYI)lZA3n9j1y-VK?~@=i1% zx0Hp*j3_SY@P@cBBP2LZldI6gN0HXTIfX^LBA2>kQ>4>W^m){)OX;Xb43$~g+bC3( z1EUijX*A3B6C(7zNduldpOc!{?zB{-iz{D$Eh{jE4-W6WNbH$>ZQ~AKrUAi*OHhqk_CBEdipTyrIzy7YCyEE_TxG7;@^~$XIdW;^v{mrpDE!6 zZe%ErTmZN-n^ifL)E@}ZkNB~}il6lg+8t%3p$6K{1`@IVdw!B9T#1^q(YK%kT7e;KSJC8-f787d*HOR$Oo}qk2UQfn&}ASvN-HWeNwW;@I?8Ko|wB~ zd;sO)Zn^)TCF^$_=N|$6cK#(+ZwD|-d{gV-C~^5q!*~D+Pbs%YRe%4swf6B`gd@=R zU0u4)du4Y4z2X9J;jD^MINDPl<`|ht&4-lP;UFvVFM~ z>38Lr^oPes8^-@kqKz=*I%qrlqf&N(?V)+-pNhh|=zi3bzjIpZ|E?H+V?d;VjGbm( zexFZ_hJq>i2@x5BKLt*u$jy%-XSRXYlKEhz6pphRX3!st1RRaH;d|_C3g^}d_iXj4 zlV1S5hcXH{DN0?D1`;5_Uf>}c=p&Art<>EvS3cp2?r};AAiPbcw?bObm3k#hUU(jG zH@-3N2%-iE#fXlTT4P{2F~vLXIkZ~r!Z#>PUo6BAYmLK8h_wO39^Y-pgq6l|0wHxy z*C(dOE-r~UgXQqrjdaF;&n)qy7VD+PlMa!!jk}!=U93|b5_9O&-B&i3)?Uk#ALJ+b zoeE{*u>a|_Jb<=Ej`Rf~8v{B+qnxizz5GT8Pk0Cwdt1l(ogSj$NdLo3-*;MGluFzL zSWf{~nhzbjLR;gSQ}+9{SchF0N}W!Uhe^E+^1ZV?6`tAlZ1&Q2>w(kqpd`L|~_A||Kw+Su^|fOWr9Kt)cl z4L5U;!myU$5YMr-=ZKhBDF&M+pxgcKpxbn1a1LD$uw~57J0^0;TI!e7L!;3rP=S>B z%e3ex<2&Eeh$!-@TouGQh>p>M2Q_-0I^LR~RGLrga8q{0s^2BC1=EnL8YX(Upb)f7 zqIJQBPq+vX=XOoKy!c<``eZdUu**`^k3W3gQ! z{{KP_KUW0f}D1pg2}_RrKFu;At{!j1CMm6!KcCw_Pd|GiLOPhrh4;Go^t&-6$*bv9voX=<130+TzjqS`$Vo>i}L znC;nCcC~Ua{RF&|Y;If=eXj@(^UPEG7x4phy*`p286ba*&+;Wx{jF(?Cy@*Hz3bhr z9D%y#Z!A^WRR1nj=uh!eyf6!hpN9+&0i5~(s5imQ{hrGZHW%xT+_c=ssl#!uWS7zt z4oR0)`46gk_PvC|<{_6C*p-jd)2u5W=W}7lkTWH}@UV)cDyn6m&n0ANQ`1I2fl;#3 zuPdC+Vw~~7Glyq%<&cU#sRLKhso>?sx7ab%iZt|g`R(mHK#Avu2#EqytFn+*zHlL4 zO7Rf~4F&UwX(iGzD#_(z3$}2NGGcI^LWR7Ly9XuTJ@-#w+G{Jd{mn-Qw|DOz#JR41SgV$K_uflx?`^YqzB+el z0}u>`kM6x4Z-o5LoBs;&rsGQ5LGJE)e-`U#x02gEA7RXKVG0}3m>!#$}6x+ZA;`{qZJ z9ezh|bqZZK^euLx5XMzGMmipZxmYacN^K*9ISsWAK4A;#R5oJMdD)#mZqun%Qph^M z_x2j+Ms2#JsA>QjBF?qegOZdK4)8PnjqA(!1xWx#9klz}M5(d&EFY#v9v12Y;s=r2 z%8g(bRSs~bgzh9Ksn_#773Re7cow%6I|nL6Uq8XSjil)WBW;2Ud(ZdNX|^Aw;L-#V zx$pi@iQL{q!Yqhdy`(=XI4s-Pj>I$m%W9(y>xhj1ZTh~-#8T0F&HjL-IuTAh(;+2( z?+_@(N9>fM#0no4%Uq5=s5VNLxT*F$>$k79@4_7W_a5HfTpp_xP+dJ+>7p@0f<3eE z1N^I`+O=9(KA;piw|#Yc?IcIi-HZL#fX0Nh0sc3B#xn?RlY0*>qMV2rw-`K*IUwUb zK)mz!Zc;A4jgmhl8-81XmQ7U&uJn*M(2K<5G8RQ_H6H7!gY%m75q$B?+*h@VBFRFG z&S(no0sb1#0DOR0b>0JRp97$D+6YL445#yEf_T%1=-WC^>5)1?!o#;5-@SLeqZ2_# zBKt66HcBAl*qj#4g{IkPms#AeNsR{<^+EmN4o#@E8w4Bee={;XnJ!_QE5hUcVtQ&Q zox~ZZXyoy?F{#Ctv%0bS(OqUUf_hIz?q8$3nF7i6<+m_VO_8Lw`KFL&L7fiSZ`gu9 zF?#!=YensJDeu0%ffp;C4ATeD^RwKX4gQrIY8DGW}?%xy9h*B*xCU@<3&Z2nnA}P#qbwo$0DFlU;)XDajp7 z9r0O8XAdf_>xTK%;wR`msLq?lo16qwu!<}4Y_8PF^X0bHk<(s#vi6!LKWASJ!po7SqWY$v$SaI9ys<)>o}q`UiUW zYjF+s@7{e(Y%c%4{wD3k%Z;o%H;fT}o9|`59#D!LR&b1c1Q9JAw5zJ1MtoR(`&f=> zZAz$a_j|_;C}Fo_?0qAs7BuVUPF5KQ`h=_{9enIzZ5K~6L9URg@dUQlFhVBUT76tB z%i{^Ek@Hw@wL;^4TAzDyV7)C_>b(;P7ZfSiJ94kfwF>w(zZUZCRiW@qdgJ>PV zPhXo9Xq6>6*~Bi(vmX~lEoG9jtzeWcr+Xki{dQlea5IBa5eq`QkbYy#Q&w$EptH2( zB#CL2y(S2AV{wOndz9WxQGSum5v4>6rtwlUG!GC%!)Xe(N%c78Cf?n>#GkX&?%;C*kz z`p}Pf#v!E$n`3~;bBkPXVg#=f0UcUFz^97H@qdDB|6lg)EBG@Q z0#vm8mt~g+f-p$hWvLhI@zj4XN9hL_VGI{xg@xU9b#=C1Trh|2@;4yG?cYk-(gBNc z_^TQG_vh>wXD&U832R2P`6w!u#O7KR>Sv|5QJkSRoYwO(>bH@kdTEpU!H_BRe?qot zjPkx96wwGb@KKlAKIeT(bYc3iqD3zcWu;GV`;eBL$ek|`Fctm++5Sv(Vf;EFud7)& zClA%3PnUaiYdrmpu4?bY)5m}9lR=|)Kys!vCdl>P z)=`lKt~~iN+KhCinafX0Op(N*@N?UR0#<`|;qwEIoyrlc0=^<8*Kp3}*)0m)l%BYg zJraNP?fWy$rAY78dajC((PM0#Jf+J@Bjb6k_ZS*Q)0Gc^ARKRyXLHddIYx{6q)RH; zom;HH^hjt^QTPqPtwjE!mM)y~{#AQ%MZPEMnD?a5Y3j%%QEoYuKGcma3XI9Q%O8k) z@AS;X5xSl$P>29+0A7afd62){ajzwnP3LHsCkMLGeiBEVBwiLrZ@N?RQP7Hg`j-^9 zxYH#}jyXTLmHw@i4O*wa{`sH~n_$WX^vURX`R#f}VO7*EOO*-EFlRw)*$h38^rtLxMf3yL7U zV7Z?vZmHwsuc^%Q<_${QUe+(_xAoNiTCkd^mX#XJsz9*@sp?X>R z{u!bVpk~PaAX-{lPEJm7adD6hNLg9g$jHdr+WPn3e+RjPv{SWh3T<4gTtV*cAa}yW zFoJd}!KRSlP)u;GBDl+gQeS8jKp=v)Ho?Y*;OYvDD)hIhig87uP#GB+6%`feW^`m% zWLke({!o6^hblxJ0o_apZX-l?5fXX`Y5jz(K|;w0q3Q#n8HesFi0rRQBcRcQv@}9~ z{!ml?hpwvk@88#s5qbs*J##%nOG7PFgtlqm--O9mVuJa&xDDUiG}rri50@^7sA3i;nP>b%KXIo@&aLEg0Qf#0(>@CHrIi#gtct~ zVP%Ez|Kh_zpwv+@G*D8|RS^>w0XREB`vg@WOAz5u#qA#xh7!({58WtWA85JE98yBCDn3SB7nwE~s$i!!5=j7()7Zes1mz0*3S5#J2*VNY4 zH#9aix3spkcXW1jzwYVn>whya*vmKs8ybH5VGK1oIXONv!w4T;m|c2151AVA1+B09 zZG74;nEH;|xC|niG_br8S7*F9&LgONFTbjMK4pnuv1@+HLh%*Llli?nX1vb8344bsE}N zw5JV?wMneInAg&^Gx}EC?29(0`-Ajk=1+hI=UOB8E)nX^I?JDTCs7;bKPwSx1W6>G zTeNzxI?$7^LPl*Cy|8k>`T>dEQb(ziVTCpSaqYn%F zBZIY>CYdhwT0k-_;KNsfty?GXQ{@kRGQpPr~gdwbqCs=g3?YtA~ ztaM^L1IJ?LbH_-&lmv%6=IM#_dXZY#XWgd*Q<@966H;3?)~(&6^~oOIibWgQ-p<^C z%^T9e+KnGAKZVC$$U1=Px2FPA_s(nr+v*&VLiLR+5^xS9j_!f?<_+e#B zozW-$Vl(+GWkTn!EZ2&Ko}g?awcZpqkosLv1S4-1U4Xo_=}PM(AMl0}Kh1C_x8vka zTS2C_ly+#Z+Ic=nhlxGL9DWvqlT(ADzwge3WFmx~4Lr#2dMc7pE>ob^ak}Zk;FtQd zRwJ%*2ERL8%@-``pAhrjY=n+=Y4pKPRX-cQaVg9Ne{0D4`QRDqs*>a&gY3;)X3)@LC2ND{yzKe#}qqQgyux^y&uxRMy^n zf!QVK`oP{^`SM%JGMldtKKc;$$YrmO<~$XuKi9(lo4f`?kH?8?m`}>56u_Jqm@Vwo z8>wFTpob#Ptat;j$&$-A?^wLbA-+z28!pYLI)5gblWm!IU6FaR-L>^w%O-Iu|e6) zl|vzI*Gt!{Q`?xwR^Mk)CZW zDV*M0UGe5J+{^IJ(3V8Fkx>8^)IOdQyr-(6{bJRgbe)?4&ZwzZj15X2HS`maZZSyg zdyXeb1R9O24L->cnYnMpp|qtsvnzs;2~2o;to3?Z)qEHdcW%u&qSKtX&#l$f@^$!7 zXR~2}=Au9nZJt$^UB12RyGN6P6${#H9tF0x*Uw3(@D(@9K9aFCvztG?x+>$2v| zdM&_3fyhH0`zU!<;LL#RZiF(sB|A=@nbhxF6BC=@*!6MXX0w-VcfAHWsvdVW4)bg-HIX`);$P z!n0T8=@9wFJ#?)hS1tPaImyKvo{@Wo8QmGUTTP34>}4%U`bId8wv1av?fnC8qAHqX z0-)yhn`xnvvE7!sCb;s!;^K`gnG`ED%yytWe;^0aByj3=26r`bBl{TxUdDhSuY&j{ z-luCoabVpzjhE3V@X^w(k2|+o870w?S@=6KJ3|7Cg#{*Og&qPP$bR0NMX;h^bCs{d zz0Ji%S7er~?XM1bG)tD0pA>v#6d~VCY%HEwg?CNoOCLgBFN&(77EqfRom0G&TS+qi zIKX%Gqu7R36& zj6dbR8yU!*3$OBE_kGX8_adsX25@OLJh$%Ud&Bm%jcT#0U$X>+K)C<`Mxw&DS(RHB$GcNUj7%v0pACw&)m zYKgs)La$`x%SFXECu`Mv!Opw9xo28*y*v_q*It(Wp%6f^Uf9@f2ok@%Faob z+()nCZ+N)O%0FeWdn!90^Eh(u@`KB5z8h-_gO6@1$kF!PkXnx~(n^&T7;eJ6a^~g| zTF|H{?Bj0?OA@UD+7z2e&3r3NSJPh9kJ@>Yu2Ym@+-0jke$e-k$Pxqv2?~IULE4~m zfU-|@hyGOdNEUw<^uDr(`G;ac0A(K*9T)Etn~)p?NrR=OC#7WhWaMTffwPN{5O@Y0 zUszmS3e5}mtg6n=YxQbsPARYJZtv`CXz%FH#v=#cjzUM?_m7Qtf+we{XXlD$7DpF8 zr7W%VeOYUs`TR9*W9Q@d$nD{YDa6I`G8ic>9slJgtPN0KHvXma>WNL>^qZ$UnXwkv zbUhAm%3r4*^2^jA{*eEeI+7|WIUzL-mXw|uml2B34b0BTD?$|%7MDXy;Sm)@Rn@h1 zR5hNljhSs;E$xlnuPb`{iaQ6&-@MCxJCZm2A^rVC$M|$Yz+BPP;`~zJ?8HJ;!0O0G z%obu}?e+K1U3(jL=X2w$pT&bXqb(obPeytZ6YV1^qSpOQ76qw+lt5O%CXXJODGcHV zJ&8F9@dXf-m!BUdGB7MQ4i$v6zKN`Gv)gli-ibpBKR^^K0ufUpGIkL6>(v zd_A@u^!DP}O6Vuj*b1+e+e%KMA({MEO8?HDg2X^d>JkZlX~kVO9y~7{3X0^ z%xHuOK@<_v2D7qJbV`eYWoz7;4ssrStW1VyYGN-sJ`;@eduZ_N=+TIRltJnMt_P8? zA6c+3l%I-<9}V29K>zDkF(FaW!2vKT+L)wdC?pL6jmw})$qojt*99uM>zO%afqA@C zytG;6_1<}i>V{U&rb6uqxwf7rsCF&w)t+A5yAed+=>CF-fE6DscXGBQbyv#PIzNO_RcQmucbHj+04Z%S)1ti~!qQW>pXQM3T{+YTM9@lyU> z4R6vLNUb*gq>eDw`!62hbgh*lMeP81 zeq&~oc_W6iQZvBrEncgt{nXXsdj}D|!#m0XVgg<+KVTzP_csF0f@DDeSQ>$Z`2%+p ziU|t!M1@6$L~CpxCOrl#BGC!q_iW6LcoOSA1OtM!{RpSH4hzJ4OEK1H8xCJXyUN~P>V zHCphhnVs#CI`&Qzf>GRjlw!qP&F`Fa#>@GhDfJ*qg;@HCz?8~B{@cjtvOX*2a0#oy z&TYCG?KnA$uZ7ZjHoCEDSVp5o>hlH$bWy}1w$2OPnO6#Uo35<$8K#+(h#e)2 zh6&gO&VyapJGc%oh!?~YuqfbONN>NuAZS2vm}h7>;sTW?O>77@3Z~6Vl>ie;PfSKo zox;Oz(&8htd7=4GewpH=Y*@bbsd8LhP6F@s=2oBl^j@lxx{CVPH`#+--G90NN3O$K z?acPn(B$p}s+&wA%;sFJCNVADqm%%SGT5;i1=Fq9w-r41PAIgauyM^^{(UOfF648U zW5;zhB_dY1ff_H1;%IP?wNiwnJ*@G$L7H0Z)!)9-vjorIh-1YD8b*Gx>P^s%)0ANe zu^mWO4YMA2QN*X4X;6k_pk%Z&&bi&2S~TyBHz_$GP!lLgSrVmL9QH##9PH0=PzVSP zQUGoVus@2-jLb?ILu($$GGv0#UVmu8^L?sp_;Q))P zw4gAls-UU@kPiX%btP@hon7UbSvh&NH6`6|--Sj-$Hp@;>PHR?P0wJI5J;5Ln0Ubw zqvGs;zAm}(b#MyGVa-MwcE`dpK~ zVNnz!*jDBW7N0E&WV5$!h|r(Y4mvBeW7%9Vt7{?u8{1PUg9ZIWdZVwd$0aW)XUJvA zHbxrRDQ6pH!9o_C1&2(2H2)n@b~c!7cKfK6eW$|N>-~G*OON@h-RE{B8A?~i^*u?> zH77F*k2QsY2zB3>#Zp>+KmWPt8eg7iSCq8UmutrP^J>WoUb0!O1&afCTAo)YuS$J- zU0`3*9DTcFsSD?Jbq`%${J5_BXl)m@oU1?=MAsz(6{Bg@L>D# z6xjH89tlg`bH!f6>`p=Rev|dyjN2-ss5}c5|8p{CCp4UrHT*dBLJglgnW~2I-ES`Z z8pzukD)k^WvuusGBTm}5ot}mM!42$D@%#u_Cm-kpu+HFLEPR9}C79h0&H;wAV^QF! zs1smKFsGMBAY3y%6v7GCNbu$chlGMDJ*6orH4xxf90pPd$H6fH_$UPBacN{=Yi&V8 z10?>$o8zVZ?c9;L*0E?@?r41xqNEJ@`gp-?Eh4-;Jbv0Uc@dYHl?dx!f=RzyjanC^ zOx5=~u4A&#W-9pWM3sSwzBqU>09OQTUwT@k*>>sL|!!680gfC34P4i1OJ#Uug( zMDBV*QpUd8pvuTYWaTHNXa~v_C2Hr;UabIJ5f#vX5TE}fIF@3@Ha~2?|2DciLMSBb zl%ly^XGfmJgD=LIG(4nEQ9(=5ny5TTT)*=u6g$>zm2~Y@<1M~uo*nLY{Tr(q+tlZ@V|`s`gNM{@^b%#T$BZ&sZS*765@d@`hb$V#Ye&#Il^fa` zXWq?o7`TG6yP5N#$)D`Z;FwN{h};B~Rn*OC(;}3Op1pKM@|_&VjOESpp8+IS4E)DgtolM8qTj z!Udj`mXR5X!{_9pvhxa4ASJMpl7gbDi1OO<%Id~o2)w+tsUy4vT36QD*IZZI(Ekq6 z_PRc3=oj#DMEL%yiw}g$A>X;uQ!typCXF5^mk)ywzmK}!RI}V!8Meh%z2pQR9&M`Q z-ugVPXc28BJ-vZtbI9d8v&p!i^9fO6Hx@}3#pl&Slwa~14I9|>$K6O!6=HdcyPj#B zrtju6F#jf7st86;DTN=*XX!OD6u^E`D*26SLMpfev#eNge3kc&FXppC%`qY-dM(U~ zd;>8px#0=Ss$9z+`({vQz!%w$ZEGie}4=R zEadPQf`1%C0xBjE@T&PHYoDT`x&Q-&czR)xe=?MoN?V(Xx2&S7rns>QUh0|EM%CO^ z+zP)+MT_Wr)BP^B2P&5(2Y*jBHZ)zNO+$4RKDRKloK8bC1zTPFycrpU;HT04w!1|< zD}cxj$%h>y`wQ*-FAT!}+~6IkNIaEX9UEIrRK??bAxT5Uk5_TW;9-)sdd!DOQjJAD zw_91lyKP399Hw`w=xjBM@bC02UMFg27Sjx6d-< zbav-?oy&3grnxrPbT`gl*6QWvqIA&a-kzmf(AdZ3%@1y0b^{66;IRk0L{j%`B6n#i ze&U#Cpc;@cfMr1BMF(h(5sBPKb7-_zKmdSaJTakO!4ckJ(Wpq^MT!efh)D`e4GB&2 z4})T}@jls@oXmVgN_cc>MoMuB{6Nq9>50P18EfmHKh%3xM@=I9hh{Gx%1x{ZS%^i& zb{EYr3{6jesx0?@KQ=M-v1=o z|LIh-f@{Sj+am03sw+A=87caDM>O4IkS?%}{goIUti<48C0=N>55f=Y?;99m;2j!) z35@ddjP{R)h9r5V#HK~YN1*VbuxvaoFE%-^Afc!Xn_HfhTpAWvTbCVEh-xVb?g(f~ z?e1&~@c6lB-oU;00xHNr$h}m_Fyt#xAvyfs#Yo3LgU-~sCQk(|2iUOC0tQds0S0h9ilI^;U zNofm|@Ero^xE{3Mkq#p}VnKU@u7UzU7k~wB{bhlE>O9muIuHmSHD4&;E{6o{yUTrJ zQ_~=H)W$&&L24~(!StA9z+LWBSP@N44GDx~RmPPyx0E)fU5lu%scvuW>-TR@m7}IJ z?jCJ?Gd7MGjMbq&KZ1MBGj*^)&}V^ysGPiw6~NZ{JULkHI?#}eivz&h4I)g~6XQ}~ zftvrDZQGA$B=97*pgCiniQszEbT?*vG>dEtEpHaszPEgo`8GqR#z!0V2tV|y|ERKI z0-XWf1jz%NG2sq{LLe|Cg2kH!`A6&kgFv8BzEJPr{n$aMCoB;e9iNWFd4?s2!qPJG z3xcx3!tx5sE3&I1Dr+l>6U*wF6Oz0jO)XtTZB^a-u>=3k!6CnQ!~dkP4-G#o_**)7 z_xn>~X5Dda*fBm@!SEiv8n2Ta3d+pAy0tKn!13}{U9JXy+N&@v3b7ik zI)ZqO#a=96t((HdF3mMGX=vIkYa|*nvB)B#l<5oow_us5W<; zYVv20b2xFtI@_PU3E-aqk?;J12BCoU;g8;r_jqF?!hj2miU{<)K!r$(O^c1kA^;;T z8M3dLqA~&iJFNgxURhLI$6J-#P*VrA^=fJBitfnm?hETF8Qix~cpa+LL#BzG3cm(*u;2DI3o4XW&>oo zS4^~TVPN!urG|?0EC@)i0G-9^)|e8pCaNk=`(=@pzpR=Oca?T`xS47^Je&v z-pM9^@{bRKU=n~g{3Ft0!{Ra%ak$)q zg3O}ajPjJM3O`sSDzrAeIHr8{lXq0c%Yw*24?yRE8jiU|k|10sguQE@=TFd{iEJsy{d_essl&CAIv#1#~kq9GNqipsLupz4N( zx@KPpyt+NIrMnZ_Sk?8qyYcP7P*qLS@UQ-@q{~Zxx_i>WJ6=1xTVcDzJKtI~#${o_ zq{pwIFxYxUFbl6(I?%7nllly#@&RU|N$xCPJEexcWo00V9PiPS|GA8}e^~|u1Nmhc zXr1%a)B;e9V3J^5CO(XZT1SVP50Wk?N1c&XQHf51=D?_Vs+yYNvJKFh4(gWI73xXq zUG?2P?_zZ5sAUJoh9*Mj=-$8*>l-IOq6O)65SOS+%9g%*k0y1dZ*1VNYh z95zq{=(8;9)V(;#*3~tV+qAhwW3(Xp$-JR{S}cm-f}VC|3CIWZ!s>KuWU6a><%Poe zZXZl%8|H1h&+AY#Kh!4OpTFzFoPsMt7~coA74{FHlPwx zKbJ80oTYVMgX{%zM{jFR;2xKoE){W|8aBT2<^pM166iDM6$M&8tx7d9E(K)xG`}xx#khB|C15LGp?^n?hs|NJ_&e%P4>9; o1`Lw?IsEUB_mAQ3MDrWq!^76S4onO9Pyl~?ff?9;1q1*4Ur=g4l>h($ literal 0 HcmV?d00001 From da3895cae57ebb224b6b4de90528baa5dfb2ec2d Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Sat, 1 Apr 2017 04:11:30 -0400 Subject: [PATCH 85/90] Add README Anki Flashcards section --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 8658dad..90fc253 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,19 @@ Notebooks also detail:


    +## Anki Flashcards: Coding and Design + +

    + +
    +

    + +The provided [Anki flashcard deck](https://apps.ankiweb.net/) uses spaced repetition to help you retain key concepts. + +* [Coding deck](anki_cards/Coding.apkg) + +Great for use while on-the-go. + ## Notebook Structure Each challenge has two notebooks, a **challenge notebook** for you to solve and a **solution notebook** for reference. From 99710adce1b048d999091141f16420932a9780d7 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Sat, 1 Apr 2017 04:12:00 -0400 Subject: [PATCH 86/90] Add README System Design Primer mention --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 90fc253..f5fc063 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,24 @@ The provided [Anki flashcard deck](https://apps.ankiweb.net/) uses spaced repeti Great for use while on-the-go. +### Design Resource: The System Design Primer + +Looking for resources to help you prep for the **System Design** and **Object-Oriented Design interviews**? + +

    + +
    +

    + +Check out the sister repo [The System Design Primer](https://github.com/donnemartin/system-design-primer), which contains additional Anki decks: + +* [System design deck](resources/flash_cards/System%20Design.apkg) +* [System design exercises deck](resources/flash_cards/System%20Design%20Exercises.apkg) +* [Object oriented design exercises deck](resources/flash_cards/OO%20Design.apkg) + +
    +![](https://camo.githubusercontent.com/e45e39c36eebcc4c66e1aecd4e4145112d8e88e3/687474703a2f2f692e696d6775722e636f6d2f6a6a3341354e382e706e67) + ## Notebook Structure Each challenge has two notebooks, a **challenge notebook** for you to solve and a **solution notebook** for reference. From 292777a21efa8e23035a8e8bb328aaddf7da3e0f Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Sat, 1 Apr 2017 04:14:32 -0400 Subject: [PATCH 87/90] Tweak README intro --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f5fc063..3ecf924 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,10 @@ interactive-coding-challenges **Continually updated, interactive and test-driven coding challenges**. -Challenges focus on **algorithms** and **data structures** that are typically found in **coding interviews**. +Challenges focus on **algorithms** and **data structures** found in **coding interviews**. Each challenge has one or more reference solutions that are: + * Fully functional * Unit tested * Easy-to-understand @@ -18,6 +19,7 @@ Each challenge has one or more reference solutions that are: Challenges will soon provide on-demand [incremental hints](https://github.com/donnemartin/interactive-coding-challenges/issues/22) to help you arrive at the optimal solution. Notebooks also detail: + * Constraints * Test cases * Algorithms From d4d08fa7601f3a53607b235b89c25fe141c23ddf Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Sat, 1 Apr 2017 04:14:52 -0400 Subject: [PATCH 88/90] Tweak README Notebook Structure intro --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ecf924..baf6490 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Check out the sister repo [The System Design Primer](https://github.com/donnemar ## Notebook Structure -Each challenge has two notebooks, a **challenge notebook** for you to solve and a **solution notebook** for reference. +Each challenge has two notebooks, a **challenge notebook** with unit tests for you to solve and a **solution notebook** for reference. ### Problem Statement From f09dfe741e4fbb6bd3e90375a8fd8b30402d6bc7 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Sat, 1 Apr 2017 04:15:39 -0400 Subject: [PATCH 89/90] Update README Contributing link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index baf6490..b38b385 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,7 @@ Unit tested, fully functional implementations of the following algorithms: ### Misc -* [Contributing](#contributing) +* [Contributing](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) * [Credits](#credits) * [Contact Info](#contact-info) * [License](#license) From 503c0b8a793b9e942a259a20c28bb2d7fb72711b Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Mon, 3 Apr 2017 06:24:02 -0400 Subject: [PATCH 90/90] Add README April 2017 update note --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index b38b385..993a54f 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,12 @@

    +## April 2017 - Huge Update! + +**Overhauled** to now include **120 challenges and solutions** and added [Anki Flash Cards](#anki-flashcards-coding-and-design). + +Also included are **unit tested reference implementations** of various [data structures](#reference-implementations-data-structures) and [algorithms](#reference-implementations-algorithms). + interactive-coding-challenges ============