{ "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 count 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": {}, "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": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Overwriting test_power_set.py\n" ] } ], "source": [ "%%writefile test_power_set.py\n", "import unittest\n", "\n", "\n", "class TestPowerSet(unittest.TestCase):\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", " self.assertEqual(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": {}, "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.7.2" } }, "nbformat": 4, "nbformat_minor": 1 }