interactive-coding-challenges/arrays_strings/permutation/permutation_solution.ipynb
William Chargin 556a9adc11 Add duplicate-characters test for permutations (#158)
Summary:
The current test cases for the "string permutation checker" problem do
not include a test case where the inputs have the same elements at
different multiplicities. Without this test case, the implementation

    return s1 is not None and s2 is not None and set(s1) == set(s2)

would pass all tests.

Test Plan:
First, change one of the implementations to the above implementation,
and see that the original tests still pass. Then, apply this patch to
add the new test case. Then, revert the implementation change to see all
tests pass again.
2017-04-04 05:58:53 -04:00

268 lines
7.1 KiB
Python

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This notebook was prepared by [Donne Martin](http://donnemartin.com). 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 string is a permutation of another string.\n",
"\n",
"* [Constraints](#Constraints)\n",
"* [Test Cases](#Test-Cases)\n",
"* [Algorithm: Compare Sorted Strings](#Algorithm:-Compare-Sorted-Strings)\n",
"* [Code: Compare Sorted Strings](#Code:-Compare-Sorted-Strings)\n",
"* [Algorithm: Hashmap Lookup](#Algorithm:-Hash-Map-Lookup)\n",
"* [Code: Hashmap Lookup](#Code:-Hash-Map-Lookup)\n",
"* [Unit Test](#Unit-Test)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Constraints\n",
"\n",
"* Can we assume the string is ASCII?\n",
" * Yes\n",
" * Note: Unicode strings could require special handling depending on your language\n",
"* Is whitespace important?\n",
" * Yes\n",
"* Is this case sensitive? 'Nib', 'bin' is not a match?\n",
" * Yes\n",
"* Can we use additional data structures?\n",
" * Yes\n",
"* Can we assume this fits in memory?\n",
" * Yes"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Test Cases\n",
"\n",
"* One or more None inputs -> False\n",
"* One or more empty strings -> False\n",
"* 'Nib', 'bin' -> False\n",
"* 'act', 'cat' -> True\n",
"* 'a ct', 'ca t' -> True\n",
"* 'dog', 'doggo' -> False"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Algorithm: Compare Sorted Strings\n",
"\n",
"Permutations contain the same strings but in different orders. This approach could be slow for large strings due to sorting.\n",
"\n",
"* Sort both strings\n",
"* If both sorted strings are equal\n",
" * return True\n",
"* Else\n",
" * return False\n",
"\n",
"Complexity:\n",
"* Time: O(n log n) from the sort, in general\n",
"* Space: O(n)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Code: Compare Sorted Strings"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"class Permutations(object):\n",
"\n",
" def is_permutation(self, str1, str2):\n",
" if str1 is None or str2 is None:\n",
" return False\n",
" return sorted(str1) == sorted(str2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Algorithm: Hash Map Lookup\n",
"\n",
"We'll keep a hash map (dict) to keep track of characters we encounter. \n",
"\n",
"Steps:\n",
"* Scan each character\n",
"* For each character in each string:\n",
" * If the character does not exist in a hash map, add the character to a hash map\n",
" * Else, increment the character's count\n",
"* If the hash maps for each string are equal\n",
" * Return True\n",
"* Else\n",
" * Return False\n",
"\n",
"Notes:\n",
"* Since the characters are in ASCII, we could potentially use an array of size 128 (or 256 for extended ASCII), where each array index is equivalent to an ASCII value\n",
"* Instead of using two hash maps, you could use one hash map and increment character values based on the first string and decrement based on the second string\n",
"* You can short circuit if the lengths of each string are not equal, although len() in Python is generally O(1) unlike other languages like C where getting the length of a string is O(n)\n",
"\n",
"Complexity:\n",
"* Time: O(n)\n",
"* Space: O(n)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Code: Hash Map Lookup"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"from collections import defaultdict\n",
"\n",
"\n",
"class PermutationsAlt(object):\n",
"\n",
" def is_permutation(self, str1, str2):\n",
" if str1 is None or str2 is None:\n",
" return False\n",
" if len(str1) != len(str2):\n",
" return False\n",
" unique_counts1 = defaultdict(int)\n",
" unique_counts2 = defaultdict(int)\n",
" for char in str1:\n",
" unique_counts1[char] += 1\n",
" for char in str2:\n",
" unique_counts2[char] += 1\n",
" return unique_counts1 == unique_counts2"
]
},
{
"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_permutation_solution.py\n"
]
}
],
"source": [
"%%writefile test_permutation_solution.py\n",
"from nose.tools import assert_equal\n",
"\n",
"\n",
"class TestPermutation(object):\n",
"\n",
" def test_permutation(self, func):\n",
" assert_equal(func(None, 'foo'), False)\n",
" assert_equal(func('', 'foo'), False)\n",
" assert_equal(func('Nib', 'bin'), False)\n",
" assert_equal(func('act', 'cat'), True)\n",
" assert_equal(func('a ct', 'ca t'), True)\n",
" assert_equal(func('dog', 'doggo'), False)\n",
" print('Success: test_permutation')\n",
"\n",
"\n",
"def main():\n",
" test = TestPermutation()\n",
" permutations = Permutations()\n",
" test.test_permutation(permutations.is_permutation)\n",
" try:\n",
" permutations_alt = PermutationsAlt()\n",
" test.test_permutation(permutations_alt.is_permutation)\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": 4,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Success: test_permutation\n",
"Success: test_permutation\n"
]
}
],
"source": [
"run -i test_permutation_solution.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
}