diff --git a/README.md b/README.md index 1b9fe49..993a54f 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,21 @@

+## 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 ============ **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 +25,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 @@ -31,9 +39,40 @@ 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. + +### 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. +Each challenge has two notebooks, a **challenge notebook** with unit tests for you to solve and a **solution notebook** for reference. ### Problem Statement @@ -66,28 +105,74 @@ 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 -* [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** + +### 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) + +### 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) + +### 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 @@ -98,7 +183,7 @@ Challenges, solutions, and unit tests are presented in the form of **IPython/Jup ### Misc -* [Contributing](#contributing) +* [Contributing](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) * [Credits](#credits) * [Contact Info](#contact-info) * [License](#license) @@ -122,6 +207,8 @@ Challenges, solutions, and unit tests are presented in the form of **IPython/Jup | 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) | @@ -132,7 +219,7 @@ Challenges, solutions, and unit tests are presented in the form of **IPython/Jup | 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) |
-

+


@@ -153,7 +240,7 @@ Challenges, solutions, and unit tests are presented in the form of **IPython/Jup | 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) |
-

+


@@ -169,10 +256,11 @@ Challenges, solutions, and unit tests are presented in the form of **IPython/Jup | 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) |
-

+


@@ -189,20 +277,23 @@ Challenges, solutions, and unit tests are presented in the form of **IPython/Jup | 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) |
-

+


@@ -215,16 +306,21 @@ Challenges, solutions, and unit tests are presented in the form of **IPython/Jup | 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) |
-

+


@@ -234,20 +330,30 @@ Challenges, solutions, and unit tests are presented in the form of **IPython/Jup | 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) | | 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) |
-

+


@@ -256,15 +362,20 @@ Challenges, solutions, and unit tests are presented in the form of **IPython/Jup | 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) | | 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) |
-

+


@@ -273,14 +384,19 @@ Challenges, solutions, and unit tests are presented in the form of **IPython/Jup | 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) |
-

+


@@ -289,6 +405,20 @@ Challenges, solutions, and unit tests are presented in the form of **IPython/Jup | 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) | @@ -366,7 +496,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.* @@ -382,6 +512,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 @@ -392,11 +523,20 @@ 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.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](https://github.com/donnemartin/interactive-coding-challenges/blob/master/CONTRIBUTING.md) are welcome! + ## Contributing 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 @@ -407,6 +547,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 diff --git a/anki_cards/Coding.apkg b/anki_cards/Coding.apkg new file mode 100644 index 0000000..9585c36 Binary files /dev/null and b/anki_cards/Coding.apkg differ 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 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 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 +} diff --git a/bit_manipulation/__init__.py b/bit_manipulation/__init__.py new file mode 100644 index 0000000..e69de29 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 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
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 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 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 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 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 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 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 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, 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 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 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 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 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 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 +} 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 +} 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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