From f7c4ac0d45266ca8c59420038dcc1485d363dffc Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Wed, 29 Mar 2017 04:35:42 -0400 Subject: [PATCH] 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