{ "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": {}, "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": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Overwriting test_merge_ranges.py\n" ] } ], "source": [ "%%writefile test_merge_ranges.py\n", "import unittest\n", "\n", "\n", "class TestMergeRanges(unittest.TestCase):\n", "\n", " def test_merge_ranges(self):\n", " solution = Solution()\n", " self.assertRaises(TypeError, solution.merge_ranges, None)\n", " self.assertEqual(solution.merge_ranges([]), [])\n", " array = [(2, 3), (7, 9)]\n", " expected = [(2, 3), (7, 9)]\n", " self.assertEqual(solution.merge_ranges(array), expected)\n", " array = [(3, 5), (2, 3), (7, 9), (8, 10)]\n", " expected = [(2, 5), (7, 10)]\n", " self.assertEqual(solution.merge_ranges(array), expected)\n", " array = [(2, 3), (3, 5), (7, 9), (8, 10), (1, 11)]\n", " expected = [(1, 11)]\n", " self.assertEqual(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": {}, "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.7.2" } }, "nbformat": 4, "nbformat_minor": 1 }