├── .gitignore ├── LICENSE ├── README.md ├── python ├── arrays │ ├── __init__.py │ ├── core.py │ ├── dynamic_array.py │ ├── sorted_array.py │ └── unsorted_array.py ├── bags │ ├── __init__.py │ └── bag.py ├── dictionaries │ └── hash_table.py ├── graphs │ └── graph.py ├── k_largest_elements.py ├── linked_lists │ ├── __init__.py │ ├── doubly_linked_list.py │ ├── singly_linked_list.py │ └── sorted_singly_linked_list.py ├── profiling │ ├── __init__.py │ └── stack_profiling.py ├── queues │ ├── heap.py │ ├── queue.py │ └── queue_linked_list.py ├── stacks │ ├── __init__.py │ ├── stack.py │ └── stack_dynamic_array.py ├── tests │ ├── __init__.py │ ├── test_bag.py │ ├── test_bst.py │ ├── test_core.py │ ├── test_doubly_linked_list.py │ ├── test_dynamic_array.py │ ├── test_graph.py │ ├── test_hash_table.py │ ├── test_heap.py │ ├── test_queue.py │ ├── test_singly_linked_list.py │ ├── test_sorted_array.py │ ├── test_sorted_singly_linked_list.py │ ├── test_stack.py │ └── test_unsorted_array.py └── trees │ └── bst.py └── readme └── thumbs ├── CH01_UN01_La_Rocca3.md.jpg ├── CH02_UN06_La_Rocca3.md.jpg ├── CH03_UN08_La_Rocca3.md.jpg ├── CH04_UN04_La_Rocca3.md.jpg ├── CH05_UN05_La_Rocca3.md.jpg ├── CH06_UN04_La_Rocca3.md.jpg ├── CH06_UN11_La_Rocca3.md.jpg ├── CH06_UN12_La_Rocca3.md.jpg ├── CH07_UN07_La_Rocca3.md.jpg ├── CH08_UN03_La_Rocca3.md.jpg ├── CH09_UN04_La_Rocca3.md.jpg ├── CH10_UN14_La_Rocca3.md.jpg ├── CH11_UN01_La_Rocca3.md.jpg ├── CH12_UN08_La_Rocca3.md.jpg ├── CH13_UN06_La_Rocca3.md.jpg └── LaRocca-MEAP-HI.png /.gitignore: -------------------------------------------------------------------------------- 1 | **/__pycache__/ 2 | 3 | # Coverage 4 | **/.coverage 5 | **/htmlcov 6 | 7 | # Profiling 8 | profiling/results 9 | 10 | # C# 11 | c#/bin 12 | c#/obj 13 | c#/*.csproj 14 | *.sln 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grokking Data Structures 2 | 3 | [![Grokking Data Structures](./readme/thumbs/LaRocca-MEAP-HI.png)](https://www.manning.com/books/grokking-data-structures) 4 | 5 | ### To Run All Tests: 6 | From the `./python` folder, run 7 | ```console 8 | python -m unittest tests/test_* 9 | ``` 10 | 11 | ## Note on Python 12 | 13 | The code in the book also doesn’t have type hints, which you can find instead in the version hosted here on GitHub. This decision was made to reduce code clutter and to avoid additional cognitive load for beginners who may not be familiar with type hints in Python. Similarly, the book doesn't include tests. 14 | 15 | However, in this repository you have the chance to find the full code with type hints, and tests. 16 | 17 | # **Introduction** 18 | ## | [Chapter 1](https://livebook.manning.com/book/grokking-data-structures/chapter-1) | 19 | ![Data structures vignette](./readme/thumbs/CH01_UN01_La_Rocca3.md.jpg) 20 | 21 | # **Static (Unsorted) Arrays** 22 | ## | [Chapter 2](https://livebook.manning.com/book/grokking-data-structures/chapter-2) | [Python](https://github.com/mlarocca/gda/blob/main/python/arrays/unsorted_array.py) | 23 | 24 | 25 | ![Arrays in memory](./readme/thumbs/CH02_UN06_La_Rocca3.md.jpg) 26 | 27 | ### Run Tests: 28 | From the `./python` folder, run 29 | ```console 30 | python -m unittest tests/test_unsorted_array.py 31 | ``` 32 | 33 | # **Sorted Arrays** 34 | ## | [Chapter 3](https://livebook.manning.com/book/grokking-data-structures/chapter-3) | [Python](https://github.com/mlarocca/gda/blob/main/python/arrays/sorted_array.py) | 35 | 36 | ![Binary search](./readme/thumbs/CH03_UN08_La_Rocca3.md.jpg) 37 | 38 | ### Run Tests: 39 | From the `./python` folder, run 40 | ```console 41 | python -m unittest tests/test_sorted_array.py 42 | ``` 43 | 44 | # **Big-O Notation** 45 | ## | [Chapter 4](https://livebook.manning.com/book/grokking-data-structures/chapter-4) | 46 | 47 | ![Big-O notation and the real world](./readme/thumbs/CH04_UN04_La_Rocca3.md.jpg) 48 | 49 | # **Dynamic Arrays** 50 | ## | [Chapter 5](https://livebook.manning.com/book/grokking-data-structures/chapter-5) | [Python](https://github.com/mlarocca/gda/blob/main/python/arrays/dynamic_array.py) | 51 | 52 | ![Doubling strategy to grow a dynamic array](./readme/thumbs/CH05_UN05_La_Rocca3.md.jpg) 53 | 54 | ### Run Tests: 55 | From the `./python` folder, run 56 | ```console 57 | python -m unittest tests/test_dynamic_array.py 58 | ``` 59 | 60 | # **Linked Lists** 61 | ## | [Chapter 6](https://livebook.manning.com/book/grokking-data-structures/chapter-6) | 62 | 63 | ### Singly-Linked Lists | [Python](https://github.com/mlarocca/gda/blob/main/python/linked_lists/singly_linked_list.py) | 64 | 65 | ![Singly-linked list](./readme/thumbs/CH06_UN04_La_Rocca3.md.jpg) 66 | 67 | #### Run Tests: 68 | From the `./python` folder, run 69 | ```console 70 | python -m unittest tests/test_singly_linked_list.py 71 | ``` 72 | 73 | ### Sorted Singly-Linked Lists | [Python](https://github.com/mlarocca/gda/blob/main/python/linked_lists/sorted_singly_linked_list.py) | 74 | 75 | ![Insertion in a sorted singly-linked list](./readme/thumbs/CH06_UN11_La_Rocca3.md.jpg) 76 | 77 | #### Run Tests: 78 | From the `./python` folder, run 79 | ```console 80 | python -m unittest tests/test_sorted_singly_linked_list.py 81 | ``` 82 | 83 | ### Doubly-Linked Lists | [Python](https://github.com/mlarocca/gda/blob/main/python/linked_lists/doubly_linked_list.py) | 84 | 85 | ![A node of a doubly-linked list](./readme/thumbs/CH06_UN12_La_Rocca3.md.jpg) 86 | 87 | #### Run Tests: 88 | From the `./python` folder, run 89 | ```console 90 | python -m unittest tests/test_doubly_linked_list.py 91 | ``` 92 | 93 | # **Bags** 94 | ## | [Chapter 7](https://livebook.manning.com/book/grokking-data-structures/chapter-7) | [Python](https://github.com/mlarocca/gda/blob/main/python/bags/bag.py) | 95 | 96 | ![Bag data structure](./readme/thumbs/CH07_UN07_La_Rocca3.md.jpg) 97 | 98 | ### Run Tests: 99 | From the `./python` folder, run 100 | ```console 101 | python -m unittest tests/test_bag.py 102 | ``` 103 | 104 | # **Stacks** 105 | ## | [Chapter 8](https://livebook.manning.com/book/grokking-data-structures/chapter-8) | [Python](https://github.com/mlarocca/gda/blob/main/python/stacks/stack.py) | 106 | 107 | ![A Stack in action](./readme/thumbs/CH08_UN03_La_Rocca3.md.jpg) 108 | 109 | ### Run Tests: 110 | From the `./python` folder, run 111 | ```console 112 | python -m unittest tests/test_stack.py 113 | ``` 114 | 115 | # **Queues** 116 | ## | [Chapter 9](https://livebook.manning.com/book/grokking-data-structures/chapter-9) | [Python](https://github.com/mlarocca/gda/blob/main/python/queues/queue.py) | 117 | 118 | ![A Queue in action](./readme/thumbs/CH09_UN04_La_Rocca3.md.jpg) 119 | 120 | ### Run Tests: 121 | From the `./python` folder, run 122 | ```console 123 | python -m unittest tests/test_queue.py 124 | ``` 125 | 126 | # **Priority queues and heaps** 127 | ## | [Chapter 10](https://livebook.manning.com/book/grokking-data-structures/chapter-10) | [Python](https://github.com/mlarocca/grokking_data_structures/blob/main/python/queues/heap.py) | 128 | 129 | ![A heap](./readme/thumbs/CH10_UN14_La_Rocca3.md.jpg) 130 | 131 | ### Run Tests: 132 | From the `./python` folder, run 133 | ```console 134 | python -m unittest tests/test_heap.py 135 | ``` 136 | 137 | # **Binary search trees** 138 | ## | [Chapter 11](https://livebook.manning.com/book/grokking-data-structures/chapter-11) | [Python](https://github.com/mlarocca/grokking_data_structures/blob/main/python/trees/bst.py) | 139 | 140 | ![A generic tree](./readme/thumbs/CH11_UN01_La_Rocca3.md.jpg) 141 | 142 | ### Run Tests: 143 | From the `./python` folder, run 144 | ```console 145 | python -m unittest tests/test_bst.py 146 | ``` 147 | 148 | # **Dictionaries and hash tables** 149 | ## | [Chapter 12](https://livebook.manning.com/book/grokking-data-structures/chapter-12) | [Python](https://github.com/mlarocca/grokking_data_structures/blob/main/python/dictionaries/hash_table.py) | 150 | 151 | ![From object to hash](./readme/thumbs/CH12_UN08_La_Rocca3.md.jpg) 152 | 153 | ### Run Tests: 154 | From the `./python` folder, run 155 | ```console 156 | python -m unittest tests/test_hash_table.py 157 | ``` 158 | 159 | # **Graphs** 160 | ## | [Chapter 13](https://livebook.manning.com/book/grokking-data-structures/chapter-13) | [Python](https://github.com/mlarocca/grokking_data_structures/blob/main/python/graphs/graph.py) | 161 | 162 | ![An undirected graph](./readme/thumbs/CH13_UN06_La_Rocca3.md.jpg) 163 | 164 | ### Run Tests: 165 | From the `./python` folder, run 166 | ```console 167 | python -m unittest tests/test_graph.py 168 | ``` -------------------------------------------------------------------------------- /python/arrays/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlarocca/grokking_data_structures/9c78516f70e44ddc910778a059e6a5d242fcc447/python/arrays/__init__.py -------------------------------------------------------------------------------- /python/arrays/core.py: -------------------------------------------------------------------------------- 1 | import array 2 | from typing import Union 3 | 4 | class Array: 5 | '''Return a new array whose items are restricted by typecode, and 6 | that can contain at most `size` elements. 7 | 8 | Arrays behave very much like Python list, except that 9 | the type of objects stored in them is constrained. The type is specified 10 | at object creation time by using a type code, which is a single character. 11 | The following type codes are defined: 12 | 13 | Type code C Type Minimum size in bytes 14 | 'b' signed integer 1 15 | 'B' unsigned integer 1 16 | 'u' Unicode character 2 17 | 'h' signed integer 2 18 | 'H' unsigned integer 2 19 | 'i' signed integer 2 20 | 'I' unsigned integer 2 21 | 'l' signed integer 4 22 | 'L' unsigned integer 4 23 | 'q' signed integer 8 24 | 'Q' unsigned integer 8 25 | 'f' floating point 4 26 | 'd' floating point 8 27 | 28 | Parameters: 29 | max_capacity (int): The maximum number of elements the array can hold. 30 | typecode (str, optional): The typecode of the array. Defaults to 'l' for int. 31 | 32 | ''' 33 | 34 | def __init__(self, size: int, typecode: str = 'l'): 35 | if size <= 0: 36 | raise ValueError(f'Invalid array size (must be positive): {size}') 37 | self._size = size 38 | self._array = array.array(typecode, [0] * size) 39 | 40 | 41 | def __len__(self): 42 | ''' 43 | Return the number of elements in the array. 44 | 45 | Parameters: 46 | None 47 | 48 | Returns: 49 | int: The number of elements in the array. 50 | ''' 51 | 52 | return self._size 53 | 54 | 55 | def __getitem__(self, index: int) -> Union[int, float]: 56 | ''' 57 | Get the value at the given index. 58 | 59 | Parameters: 60 | index (int): The index to get the value from. 61 | 62 | Returns: 63 | Union[int, float]: The value at the given index. 64 | ''' 65 | 66 | if index < 0 or index >= self._size: 67 | raise IndexError('array index out of range') 68 | return self._array[index] 69 | 70 | 71 | def __setitem__(self, index: int, val: Union[int, float]) -> None: 72 | ''' 73 | Set the value at the given index. 74 | 75 | Parameters: 76 | index (int): The index to set the value at. 77 | val (Union[int, float]): The value to set. 78 | 79 | Returns: 80 | None 81 | ''' 82 | 83 | if index < 0 or index >= self._size: 84 | raise IndexError('array assignment index out of range') 85 | self._array[index] = val 86 | 87 | 88 | def __repr__(self): 89 | ''' 90 | Return the string representation of the array. 91 | 92 | Parameters: 93 | None 94 | 95 | Returns: 96 | str: The string representation of the array. 97 | ''' 98 | 99 | return repr(self._array) -------------------------------------------------------------------------------- /python/arrays/dynamic_array.py: -------------------------------------------------------------------------------- 1 | import arrays.core as core 2 | from typing import Union 3 | 4 | class DynamicArray: 5 | '''Return a new dynamic _unsorted_ array whose items are restricted by typecode. 6 | The initial capacity of the array is by default 1, but this can be changed 7 | by passing a value for the initial_capacity argument. 8 | 9 | This array is not limited in the number of elements they can store, it will 10 | seamlessly expand and shrink automatically when needed. 11 | The initial capacity can be set, however, to optimize initial allocation, if 12 | the user knows the approximate number of elements that will be needed. 13 | 14 | Arrays represent basic values and behave very much like Python list, except 15 | the type of objects stored in them is constrained. The type is specified 16 | at object creation time by using a type code, which is a single character. 17 | The following type codes are defined: 18 | 19 | Type code C Type Minimum size in bytes 20 | 'b' signed integer 1 21 | 'B' unsigned integer 1 22 | 'u' Unicode character 2 23 | 'h' signed integer 2 24 | 'H' unsigned integer 2 25 | 'i' signed integer 2 26 | 'I' unsigned integer 2 27 | 'l' signed integer 4 28 | 'L' unsigned integer 4 29 | 'q' signed integer 8 30 | 'Q' unsigned integer 8 31 | 'f' floating point 4 32 | 'd' floating point 8 33 | 34 | Parameters: 35 | initial_capacity (int, optional): The maximum number of elements the array can hold. 36 | typecode (str, optional): The typecode of the array. Defaults to 'l' for int. 37 | 38 | ''' 39 | def __init__(self, initial_capacity: int = 1, typecode: str = 'l') -> None: 40 | self._array = core.Array(initial_capacity, typecode) 41 | self._capacity = initial_capacity 42 | self._size = 0 43 | self._typecode = typecode 44 | 45 | 46 | def __len__(self) -> int: 47 | ''' 48 | Return the number of elements in the array. 49 | 50 | Parameters: 51 | None 52 | 53 | Returns: 54 | int: The number of elements in the array. 55 | ''' 56 | 57 | return self._size 58 | 59 | 60 | def __getitem__(self, index) -> Union[int, float]: 61 | ''' 62 | Get the value at the given index. 63 | 64 | Parameters: 65 | index (int): The index to get the value from. 66 | 67 | Returns: 68 | Union[int, float]: The value at the given index. 69 | ''' 70 | 71 | if index >= self._size: 72 | raise IndexError(f'Index out of bound: {index}') 73 | return self._array[index] 74 | 75 | 76 | def __repr__(self) -> str: 77 | ''' 78 | Return the string representation of the array. 79 | 80 | Parameters: 81 | None 82 | 83 | Returns: 84 | str: The string representation of the array. 85 | ''' 86 | 87 | return repr(self._array._array[:self._size]) 88 | 89 | 90 | def __iter__(self): 91 | ''' 92 | Iterate over the values in the sorted array. 93 | 94 | Parameters: 95 | None 96 | 97 | Functionality: 98 | Iterates over the values in the unsorted array. The iteration starts at index 0 and 99 | goes on until it reaches the last element in the array. 100 | ''' 101 | 102 | for i in range(self._size): 103 | yield self._array[i] 104 | 105 | 106 | def _is_full(self): 107 | '''Check if the array is full. 108 | 109 | Parameters: None 110 | 111 | Returns: bool: True if the array is full, False otherwise. 112 | 113 | Functionality: Checks if the size of the array is greater than or equal to the capacity. 114 | Returns True if so, False otherwise. 115 | ''' 116 | 117 | return self._size >= self._capacity 118 | 119 | 120 | def _double_size(self): 121 | ''' 122 | Double the size of the underlying static array. 123 | 124 | Parameters: 125 | None 126 | 127 | Functionality: 128 | Creates a new array with double the capacity of the old one. 129 | Copies all elements from the old array into the new larger array. 130 | ''' 131 | 132 | assert(self._capacity == self._size) # Invariant: this is called only when capacity == size 133 | old_array = self._array 134 | self._array = core.Array(self._capacity * 2, self._typecode) 135 | self._capacity *= 2 136 | for i in range(self._size): 137 | self._array[i] = old_array[i] 138 | 139 | assert(self._array._size == self._capacity) # Invariant: the size of the new static array should be equal to the new capacity 140 | 141 | 142 | def _halve_size(self): 143 | ''' 144 | Resize the static array to half the capacity. 145 | 146 | Parameters: 147 | None 148 | 149 | Functionality: 150 | Halves the size of the underlying static array. 151 | 152 | Creates a new array with half the capacity of the old one. 153 | Copies all elements from the old array into the new smaller array. 154 | ''' 155 | 156 | assert(self._capacity > 1 and self._size <= self._capacity/4) # Invariant: this is called only when capacity > 1 and size <= capacity/4 157 | old_array = self._array 158 | self._array = core.Array(self._capacity // 2, self._typecode) 159 | self._capacity //= 2 160 | for i in range(self._size): 161 | self._array[i] = old_array[i] 162 | 163 | assert(self._array._size == self._capacity) # Invariant: the size of the new static array should be equal to the new capacity 164 | 165 | 166 | def is_empty(self): 167 | ''' 168 | Check if the array is empty. 169 | 170 | Parameters: 171 | None 172 | 173 | Returns: 174 | bool: True if the array is empty, False otherwise. 175 | 176 | Functionality: 177 | Checks if no elements is stored in the array. Returns True if so, False otherwise. 178 | ''' 179 | 180 | return len(self) == 0 181 | 182 | 183 | def insert(self, value: Union[int, float]) -> None: 184 | ''' 185 | Insert a new value into the unsorted array. 186 | 187 | Parameters: 188 | value (any): The value to insert into the sorted array. 189 | 190 | Returns: 191 | None 192 | 193 | Functionality: 194 | Inserts the given value into the unsorted array. 195 | The new element is added at the end of the array. 196 | ''' 197 | 198 | if self._is_full(): 199 | self._double_size() 200 | 201 | # By now, we are sure that self._size < len(self._array) 202 | self._array[self._size] = value 203 | self._size += 1 204 | 205 | 206 | def find(self, target: Union[int, float]) -> Union[int, None]: 207 | ''' 208 | Search for a target value in the unsorted array. 209 | 210 | Parameters: 211 | target (any): The value to search for in the sorted array. 212 | 213 | Returns: 214 | int or None: The index of the target value if found, otherwise None. 215 | 216 | Functionality: 217 | Performs a linear search over the values in the sorted array. 218 | Returns the index of the leftmost occurrence of the target value, if found. 219 | Otherwise returns None. 220 | ''' 221 | 222 | for i in range(self._size): 223 | if self._array[i] == target: 224 | return i 225 | # Element not found, reached the end of the array 226 | return None 227 | 228 | 229 | def delete(self, target: Union[int, float]) -> None: 230 | ''' 231 | Delete a target value from the array. 232 | 233 | Parameters: 234 | target (any): The value to delete from the sorted array. 235 | 236 | Returns: 237 | None 238 | 239 | Functionality: 240 | Finds the leftmost index of the target value using the find method. 241 | If the target is not found, raises a ValueError. 242 | Otherwise, deletes the target value by shifting all values after it to the left, 243 | to keep the remaining elements in the order they were inserted. 244 | ''' 245 | 246 | index = self.find(target) 247 | if index is None: 248 | raise ValueError(f'Unable to delete element {target}: the entry is not in the array') 249 | 250 | # Must shift all the elements after the position of the target 251 | for i in range(index, self._size - 1): 252 | self._array[i] = self._array[i + 1] 253 | self._size -= 1 254 | 255 | # Check if we should shrink the array 256 | if self._capacity > 1 and self._size <= self._capacity/4: 257 | self._halve_size() 258 | -------------------------------------------------------------------------------- /python/arrays/sorted_array.py: -------------------------------------------------------------------------------- 1 | import arrays.core as core 2 | from typing import Union 3 | 4 | class SortedArray: 5 | '''Return a new sorted array whose items are restricted by typecode, and 6 | that can contain at most `max_size` elements. 7 | 8 | Arrays represent basic values and behave very much like Python list, except 9 | the type of objects stored in them is constrained. The type is specified 10 | at object creation time by using a type code, which is a single character. 11 | The following type codes are defined: 12 | 13 | Type code C Type Minimum size in bytes 14 | 'b' signed integer 1 15 | 'B' unsigned integer 1 16 | 'u' Unicode character 2 17 | 'h' signed integer 2 18 | 'H' unsigned integer 2 19 | 'i' signed integer 2 20 | 'I' unsigned integer 2 21 | 'l' signed integer 4 22 | 'L' unsigned integer 4 23 | 'q' signed integer 8 24 | 'Q' unsigned integer 8 25 | 'f' floating point 4 26 | 'd' floating point 8 27 | 28 | Parameters: 29 | max_size (int): The maximum number of elements the array can hold. 30 | typecode (str, optional): The typecode of the array. Defaults to 'l' for int. 31 | 32 | ''' 33 | def __init__(self, max_size: int, typecode: str = 'l'): 34 | self._array = core.Array(max_size, typecode) 35 | self._max_size = max_size 36 | # The actual number of elements stored in the array 37 | self._size = 0 38 | 39 | 40 | def __len__(self) -> int: 41 | ''' 42 | Return the number of elements in the array. 43 | 44 | Parameters: 45 | None 46 | 47 | Returns: 48 | int: The number of elements in the array. 49 | ''' 50 | 51 | return self._size 52 | 53 | 54 | def __getitem__(self, index) -> Union[int, float]: 55 | ''' 56 | Get the value at the given index. 57 | 58 | Parameters: 59 | index (int): The index to get the value from. 60 | 61 | Returns: 62 | Union[int, float]: The value at the given index. 63 | ''' 64 | 65 | if index < 0 or index >= self._size: 66 | raise IndexError(f'Index out of bound: {index}') 67 | return self._array[index] 68 | 69 | 70 | def __repr__(self) -> str: 71 | ''' 72 | Return the string representation of the array. 73 | 74 | Parameters: 75 | None 76 | 77 | Returns: 78 | str: The string representation of the array. 79 | ''' 80 | 81 | return f'SortedArray({repr(self._array._array[:self._size])})' 82 | 83 | 84 | def __iter__(self): 85 | ''' 86 | Iterate over the values in the sorted array. 87 | 88 | Parameters: 89 | None 90 | 91 | Functionality: 92 | Iterates over the values in the sorted array. The iteration starts at index 0 and 93 | goes on until it reaches the end of the array (not the full maximum capacity of the array, 94 | just the last stored elements). 95 | ''' 96 | 97 | for i in range(self._size): 98 | yield self._array[i] 99 | 100 | 101 | def max_size(self) -> int: 102 | ''' 103 | Return the number of elements that the array can hold. 104 | 105 | Parameters: 106 | None 107 | 108 | Returns: 109 | int: The maximum size of the array. 110 | 111 | ''' 112 | 113 | return self._max_size 114 | 115 | 116 | def insert(self, value: Union[int, float]) -> None: 117 | ''' 118 | Insert a new value into the sorted array. 119 | 120 | Parameters: 121 | value (any): The value to insert into the sorted array. 122 | 123 | Returns: 124 | None 125 | 126 | Functionality: 127 | Inserts the given value into the sorted array while maintaining the sorted order. 128 | If the array is already full, raises a ValueError. 129 | Otherwise, shifts elements to the right to make room for the new value and inserts 130 | it in the correct position to keep the array sorted. 131 | ''' 132 | 133 | if self._size >= self._max_size: 134 | raise ValueError(f'The array is already full, maximum size: {self._max_size}') 135 | for i in range(self._size, 0, -1): 136 | if self._array[i-1] <= value: 137 | # Found the right place for the element 138 | self._array[i] = value 139 | self._size += 1 140 | return 141 | else: 142 | self._array[i] = self._array[i-1] 143 | # If it gets here, it means we need to add the new value as the first entry 144 | self._array[0] = value 145 | self._size += 1 146 | 147 | 148 | def linear_search(self, target: Union[int, float]) -> Union[int, None]: 149 | ''' 150 | Search for a target value in the sorted array using a naive linear search. 151 | 152 | Parameters: 153 | target (any): The value to search for in the sorted array. 154 | 155 | Returns: 156 | int or None: The index of the target value if found, otherwise None. 157 | 158 | Functionality: 159 | Performs a linear search over the values in the sorted array. 160 | Since the array is sorted, we can stop searching once we pass the point 161 | where the target value would be located. 162 | Returns the index of the target value if found, otherwise returns None. 163 | ''' 164 | 165 | for i in range(self._size): 166 | if self._array[i] == target: 167 | return i 168 | elif self._array[i] > target: 169 | # The array is sorted, we can't find the target in the rest of the array 170 | return None 171 | # Element not found, reached the end of the array 172 | return None 173 | 174 | 175 | def binary_search(self, target: Union[int, float]) -> Union[int, None]: 176 | ''' 177 | Search for a target value in the sorted array using binary search. 178 | 179 | Parameters: 180 | target (any): The value to search for in the sorted array. 181 | 182 | Returns: 183 | int or None: The index of the target value if found, otherwise None. 184 | 185 | Functionality: 186 | Performs a binary search on the sorted array. 187 | Keeps track of left and right indices, and calculates the midpoint index. 188 | Checks if the midpoint value matches the target. If so, returns the midpoint index. 189 | Otherwise, recurses on either the left or right half of the array depending on if the 190 | midpoint value is greater than or less than the target. 191 | Returns the index if found, otherwise returns None if the target is not found. 192 | ''' 193 | 194 | left = 0 195 | right = self._size - 1 196 | while left <= right: 197 | mid_index = (left + right) // 2 198 | mid_val = self._array[mid_index] 199 | if mid_val == target: 200 | return mid_index 201 | elif mid_val > target: 202 | right = mid_index - 1 203 | else: 204 | left = mid_index + 1 205 | return None 206 | 207 | def delete(self, target: Union[int, float]) -> None: 208 | ''' 209 | Delete a target value from the sorted array. 210 | 211 | Parameters: 212 | target (any): The value to delete from the sorted array. 213 | 214 | Returns: 215 | None 216 | 217 | Functionality: 218 | Finds the index of the target value using the find method. 219 | If the target is not found, raises a ValueError. 220 | Otherwise, shifts all elements after the target to the left to fill in the gap. 221 | If it succeeds, it decrements the size of the array by 1. 222 | ''' 223 | 224 | index = self.binary_search(target) 225 | if index is None: 226 | raise ValueError(f'Unable to delete element {target}: the entry is not in the array') 227 | 228 | # Must shift all the elements after the position of the target 229 | for i in range(index, self._size - 1): 230 | self._array[i] = self._array[i + 1] 231 | self._size -= 1 232 | -------------------------------------------------------------------------------- /python/arrays/unsorted_array.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | from arrays.core import Array 3 | 4 | class UnsortedArray: 5 | '''Return a new unsorted array whose items are restricted by typecode, and 6 | that can contain at most `max_size` elements. 7 | 8 | Arrays represent basic values and behave very much like Python list, except 9 | the type of objects stored in them is constrained. The type is specified 10 | at object creation time by using a type code, which is a single character. 11 | The following type codes are defined: 12 | 13 | Type code C Type Minimum size in bytes 14 | 'b' signed integer 1 15 | 'B' unsigned integer 1 16 | 'u' Unicode character 2 17 | 'h' signed integer 2 18 | 'H' unsigned integer 2 19 | 'i' signed integer 2 20 | 'I' unsigned integer 2 21 | 'l' signed integer 4 22 | 'L' unsigned integer 4 23 | 'q' signed integer 8 24 | 'Q' unsigned integer 8 25 | 'f' floating point 4 26 | 'd' floating point 8 27 | 28 | Parameters: 29 | max_size (int): The maximum number of elements the array can hold. 30 | typecode (str, optional): The typecode of the array. Defaults to 'l' for int. 31 | 32 | ''' 33 | def __init__(self, max_size: int, typecode: str = 'l'): 34 | self._array = Array(max_size, typecode) 35 | self._max_size = max_size 36 | # The actual number of elements stored in the array 37 | self._size = 0 38 | 39 | 40 | def __len__(self) -> int: 41 | ''' 42 | Return the number of elements in the array. 43 | 44 | Parameters: 45 | None 46 | 47 | Returns: 48 | int: The number of elements in the array. 49 | ''' 50 | 51 | return self._size 52 | 53 | 54 | def __getitem__(self, index) -> Union[int, float]: 55 | ''' 56 | Get the value at the given index. 57 | 58 | Parameters: 59 | index (int): The index to get the value from. 60 | 61 | Returns: 62 | Union[int, float]: The value at the given index. 63 | ''' 64 | 65 | if index < 0 or index >= self._size: 66 | raise IndexError(f'Index out of bound: {index}') 67 | return self._array[index] 68 | 69 | 70 | def __repr__(self) -> str: 71 | ''' 72 | Return the string representation of the array. 73 | 74 | Parameters: 75 | None 76 | 77 | Returns: 78 | str: The string representation of the array. 79 | ''' 80 | 81 | return f'UnsortedArray({repr(self._array._array[:self._size])})' 82 | 83 | 84 | def max_size(self) -> int: 85 | ''' 86 | Return the number of elements that the array can hold. 87 | 88 | Parameters: 89 | None 90 | 91 | Returns: 92 | int: The maximum size of the array. 93 | 94 | ''' 95 | 96 | return self._max_size 97 | 98 | 99 | def insert(self, new_entry) -> None: 100 | ''' 101 | Insert an entry into an unsorted array. 102 | 103 | Parameters: 104 | new_entry (Any): The entry to insert. 105 | ''' 106 | 107 | if self._size >= len(self._array): 108 | raise ValueError('The array is already full') 109 | else: 110 | self._array[self._size] = new_entry 111 | self._size += 1 112 | 113 | 114 | def delete(self, index) -> None: 115 | ''' 116 | Delete an entry at the given index from an unsorted array. 117 | 118 | Parameters: 119 | index (int): The index of the entry to delete. 120 | ''' 121 | 122 | if self._size == 0: 123 | raise ValueError('Delete from an empty array') 124 | elif index < 0 or index >= self._size: 125 | raise ValueError(f'Index {index} out of range.') 126 | else: 127 | self._array[index] = self._array[self._size-1] 128 | self._size -= 1 129 | 130 | 131 | def find(self, target) -> int: 132 | ''' 133 | Find the index of a target entry in an unsorted array. 134 | 135 | Parameters: 136 | target (Any): The entry to search for. 137 | 138 | Returns: 139 | int: The index of the first occurrence of the target entry, if found, else None. 140 | ''' 141 | 142 | for index in range(0, self._size): 143 | if self._array[index] == target: 144 | return index 145 | # Couldn't find the target 146 | return None 147 | 148 | def traverse(self, callback): 149 | ''' 150 | Traverse an unsorted array and call a callback function on each element. 151 | 152 | Parameters: 153 | callback (function): The function to call on each element. 154 | ''' 155 | 156 | for index in range(self._size): 157 | callback(self._array[index]) 158 | -------------------------------------------------------------------------------- /python/bags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlarocca/grokking_data_structures/9c78516f70e44ddc910778a059e6a5d242fcc447/python/bags/__init__.py -------------------------------------------------------------------------------- /python/bags/bag.py: -------------------------------------------------------------------------------- 1 | """Module providing an implementation for bag.""" 2 | 3 | from typing import Any 4 | from linked_lists.singly_linked_list import SinglyLinkedList 5 | 6 | class Bag: 7 | """ A class modeling the bag container. 8 | """ 9 | def __init__(self) -> None: 10 | """ Creates an empty bag. 11 | """ 12 | self._data = SinglyLinkedList() 13 | 14 | 15 | def __iter__(self): 16 | """ 17 | Iterate over the values in the bag. 18 | 19 | Parameters: 20 | None 21 | 22 | Returns: 23 | Iterator: An iterator over the values in the bag. 24 | """ 25 | 26 | return iter(self._data) 27 | 28 | 29 | def __len__(self): 30 | """ 31 | Return the size of the bag. 32 | 33 | Parameters: 34 | None 35 | 36 | Returns: 37 | int: The number of values stored in the bag. 38 | """ 39 | return len(self._data) 40 | 41 | 42 | def __str__(self): 43 | """ 44 | Return the string representation of the bag. 45 | 46 | Parameters: 47 | None 48 | 49 | Returns: 50 | str: The string representation of the bag. 51 | """ 52 | return str(','.join(map(str, self))) 53 | 54 | 55 | def __repr__(self): 56 | """ 57 | Return the string (internal) representation of the bag. 58 | 59 | Parameters: 60 | None 61 | 62 | Returns: 63 | str: The string representation of the bag. 64 | """ 65 | return f'Bag({",".join(map(str, self))})' 66 | 67 | 68 | def is_empty(self) -> bool: 69 | """ 70 | Check if the bag is empty. 71 | 72 | Parameters: 73 | None 74 | 75 | Returns: 76 | bool: True if the bag is empty, False otherwise. 77 | """ 78 | 79 | return self._data.is_empty() 80 | 81 | 82 | def insert(self, value: Any) -> None: 83 | """ 84 | Insert a new value into the bag. 85 | 86 | Parameters: 87 | value (Any): The value to insert into the bag. 88 | 89 | Returns: 90 | None 91 | """ 92 | self._data.insert_in_front(value) 93 | -------------------------------------------------------------------------------- /python/dictionaries/hash_table.py: -------------------------------------------------------------------------------- 1 | from math import floor, sqrt 2 | from decimal import Decimal 3 | from typing import Any, Callable 4 | from linked_lists.singly_linked_list import SinglyLinkedList 5 | 6 | class HashTable: 7 | """ A hash table with chaining implementation. 8 | """ 9 | 10 | __A__ = Decimal((sqrt(5) - 1) / 2) 11 | 12 | def __init__(self, buckets: int, extract_key: Callable[..., Any]=hash) -> None: 13 | """ Create an empty hash table. Chaining is used for collision resolution. 14 | 15 | Parameters: 16 | buckets: The size of the hash table, that is, the number of hash chains. 17 | Note that the size of the hash table is not the number of elements it can store, 18 | which is unbounded. 19 | The size must be a positive integer. 20 | extract_key: A function that extracts the key from any value stored in the table. 21 | Note that, for the table to work properly, keys must be integers uniquely 22 | identifying the values stored in the table. 23 | If not given, the built-in `hash` function is used. 24 | 25 | """ 26 | if buckets <= 0: 27 | raise ValueError(f'Invalid size for the hash table (must be positive): {buckets}') 28 | self._m = buckets 29 | self._data = [SinglyLinkedList() for _ in range(buckets)] 30 | self._extract_key = extract_key 31 | 32 | 33 | def __len__(self): 34 | """ Return the size of the hash table. 35 | """ 36 | return sum((len(bucket) for bucket in self._data)) 37 | 38 | 39 | def _hash(self, key: int): 40 | """ Computes the index in this hash table associated with a key. 41 | """ 42 | return floor(abs(self._m * ((Decimal(key) * HashTable.__A__) % 1))) 43 | 44 | 45 | def is_empty(self) -> int: 46 | """ Check if the hash table is empty. 47 | """ 48 | return len(self) == 0 49 | 50 | 51 | def search(self, key: int) -> Any: 52 | """ 53 | Search for a value with the given key in the hash table. 54 | 55 | Parameters: 56 | key: The key to search for. 57 | 58 | Returns: 59 | The value associated with the key if found, None otherwise. 60 | """ 61 | index = self._hash(key) 62 | value_matches_key = lambda v: self._extract_key(v) == key 63 | return self._data[index].search(value_matches_key) 64 | 65 | 66 | def insert(self, value: Any) -> None: 67 | """ Insert a value in the hash table. 68 | 69 | Parameters: 70 | value: The value to check add to the hash table. 71 | """ 72 | index = self._hash(self._extract_key(value)) 73 | self._data[index].insert_in_front(value) 74 | 75 | 76 | def contains(self, value: Any) -> bool: 77 | """ Check if a value is in the hash table. 78 | 79 | Parameters: 80 | value: The value to check for existence in the hash table. 81 | 82 | Returns: 83 | True if the value is found in the hash table, False otherwise. 84 | """ 85 | return self.search(self._extract_key(value)) is not None 86 | 87 | 88 | def delete(self, value: Any) -> None: 89 | """ Delete a value from the hash table. 90 | 91 | Parameters: 92 | value: The value to be deleted from the hash table. 93 | """ 94 | index = self._hash(self._extract_key(value)) 95 | try: 96 | self._data[index].delete(value) 97 | except ValueError as exc: 98 | raise ValueError(f'No element with value {value} was found in the hash table.') from exc 99 | -------------------------------------------------------------------------------- /python/graphs/graph.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import Any, Type, Tuple 3 | from linked_lists.singly_linked_list import SinglyLinkedList 4 | from queues.queue import Queue 5 | from stacks.stack import Stack 6 | 7 | class Graph: 8 | """A class modeling a simple, directed, unweighted graph.""" 9 | 10 | class Vertex: 11 | """A class representing a vertex in a graph. Vertices are identified by a unique key.""" 12 | 13 | def __init__(self, key: Any): 14 | """Initialize a Vertex instance. 15 | 16 | Parameters: 17 | key: The unique identifier for this vertex. 18 | 19 | """ 20 | self.id = key 21 | self._adj_list = SinglyLinkedList() 22 | 23 | def __eq__(self, other: Type[Graph.Vertex]): 24 | return self.id == other.id 25 | 26 | def __hash__(self) -> int: 27 | return hash(repr(self)) # pragma: no cover 28 | 29 | def __repr__(self) -> str: 30 | return f'Vertex({repr(self.id)})' 31 | 32 | def __str__(self) -> str: 33 | return f'<{str(self.id)}>' 34 | 35 | def has_edge_to(self, destination_vertex: Type[Graph.Vertex]) -> bool: 36 | """Check if there is an edge from this vertex to the destination vertex. 37 | 38 | Parameters: 39 | destination_vertex (Vertex): The destination vertex. 40 | 41 | Returns: 42 | bool: True if there is an edge from this vertex to the destination, False otherwise. 43 | """ 44 | return self._adj_list.search(lambda v: v == destination_vertex) is not None 45 | 46 | def add_edge_to(self, destination_vertex: Type[Graph.Vertex]) -> None: 47 | """Add an edge from this vertex to the destination vertex. 48 | 49 | Parameters: 50 | destination_vertex (Vertex): The destination vertex. 51 | """ 52 | if self.has_edge_to(destination_vertex): 53 | raise ValueError(f'Edge already exists: {self} -> {destination_vertex}') 54 | self._adj_list.insert_in_front(destination_vertex) 55 | 56 | def delete_edge_to(self, destination_vertex: Type[Graph.Vertex]): 57 | """Remove the edge from this vertex to the destination vertex, if it exists. 58 | 59 | Parameters: 60 | destination_vertex (Vertex): The destination vertex. 61 | """ 62 | try: 63 | self._adj_list.delete(destination_vertex) 64 | except ValueError as e: 65 | raise ValueError(f'Edge does not exist: {self} -> {destination_vertex}') from e 66 | 67 | def outgoing_edges(self) -> list[Tuple[Any, Any]]: 68 | """Get a list of outgoing edges from this vertex. 69 | 70 | Returns: 71 | list[Tuple[Any, Any]]: A list of tuples of the form (source, destination). 72 | For both vertices we return their keys. 73 | """ 74 | return [(self.id, v.id) for v in self._adj_list] 75 | 76 | 77 | def __init__(self): 78 | self._adj = {} 79 | 80 | 81 | def __repr__(self) -> str: 82 | def edges_repr(edges): 83 | return f"[{', '.join((f'->{u}' for (_, u) in edges))}]" 84 | 85 | adj_lst_repr = (f'{repr(v)}: {edges_repr(v.outgoing_edges())}' for v in self._adj.values()) 86 | return f'Graph({" | ".join(adj_lst_repr)})' 87 | 88 | 89 | def _get_vertex(self, key: Any) -> Type[Graph.Vertex]: 90 | if key not in self._adj: 91 | raise ValueError(f'Vertex {key} does not exist!') 92 | return self._adj[key] 93 | 94 | 95 | def insert_vertex(self, key: Any) -> None: 96 | """Add a vertex to the graph. 97 | 98 | Parameters: 99 | key: The unique identifier of the vertex to add. 100 | 101 | Raises: 102 | ValueError: If the vertex already exists. 103 | """ 104 | if key in self._adj: 105 | raise ValueError(f'Vertex {key} already exists!') 106 | self._adj[key] = Graph.Vertex(key) 107 | 108 | 109 | def has_vertex(self, key: Any) -> bool: 110 | """Check if the graph contains a vertex with the given key. 111 | 112 | Parameters: 113 | key: The unique identifier of the vertex to check. 114 | 115 | Returns: 116 | bool: True if the graph contains a vertex with the given key, False otherwise. 117 | """ 118 | return key in self._adj 119 | 120 | 121 | def delete_vertex(self, key: Any) -> None: 122 | """Delete a vertex from the graph. 123 | 124 | Parameters: 125 | key: The unique identifier of the vertex to delete. 126 | 127 | Raises: 128 | ValueError: If no vertex with the given key exists. 129 | 130 | """ 131 | v = self._get_vertex(key) 132 | for u in self._adj.values(): 133 | if u != v and u.has_edge_to(v): 134 | u.delete_edge_to(v) 135 | del self._adj[key] 136 | 137 | 138 | def get_vertices(self) -> set[Any]: 139 | """Get a list of the unique identifiers of all vertices in the graph. 140 | 141 | Returns: 142 | set[Any]: A list of the unique identifiers of all vertices in the graph. 143 | """ 144 | return set(self._adj.keys()) 145 | 146 | 147 | def vertex_count(self) -> int: 148 | """Get the number of vertices in the graph. 149 | 150 | Returns: 151 | int: The number of vertices in the graph. 152 | """ 153 | return len(self._adj) 154 | 155 | 156 | def insert_edge(self, key1: Any, key2: Any) -> None: 157 | """Add an edge between two vertices in the graph. 158 | 159 | Parameters: 160 | key1: The unique identifier of the first vertex. 161 | key2: The unique identifier of the second vertex. 162 | 163 | Raises: 164 | ValueError: If the edge already exists. 165 | """ 166 | v1 = self._get_vertex(key1) 167 | v2 = self._get_vertex(key2) 168 | v1.add_edge_to(v2) 169 | 170 | 171 | def has_edge(self, key1: Any, key2: Any) -> bool: 172 | """Check if the graph contains an edge between two vertices. 173 | 174 | Parameters: 175 | key1: The unique identifier of the first vertex. 176 | key2: The unique identifier of the second vertex. 177 | 178 | Returns: 179 | bool: True if the graph contains an edge between the two vertices, False otherwise. 180 | 181 | Raises: 182 | ValueError: If any of the vertices does not exist. 183 | """ 184 | v1 = self._get_vertex(key1) 185 | v2 = self._get_vertex(key2) 186 | return v1.has_edge_to(v2) 187 | 188 | 189 | def delete_edge(self, key1: Any, key2: Any) -> None: 190 | """Delete an edge between two vertices in the graph. 191 | 192 | Parameters: 193 | key1: The unique identifier of the first vertex. 194 | key2: The unique identifier of the second vertex. 195 | 196 | Raises: 197 | ValueError: If the edge does not exist, or any of the vertices does not exist. 198 | """ 199 | v1 = self._get_vertex(key1) 200 | v2 = self._get_vertex(key2) 201 | v1.delete_edge_to(v2) 202 | 203 | 204 | def get_edges(self) -> set[tuple[Any, Any]]: 205 | """Get a list of all edges in the graph. 206 | 207 | Returns: 208 | set[tuple[Any, Any]]: A list of all edges in the graph. 209 | Each edge is returned as a (source, destination) pair of vertex keys. 210 | """ 211 | return set(e for v in self._adj.values() for e in v.outgoing_edges()) 212 | 213 | 214 | def edge_count(self) -> int: 215 | """Get the number of edges in the graph. 216 | 217 | Returns: 218 | int: The number of edges in the graph. 219 | """ 220 | return sum(len(v.outgoing_edges()) for v in self._adj.values()) 221 | 222 | 223 | def bfs(self, start_vertex: Any, target_vertex: Any) -> list[Any]: 224 | """Perform a breadth-first search from a given vertex. 225 | Looks for the shortest path from the start vertex to the target vertex. 226 | 227 | Parameters: 228 | start_vertex: The unique identifier of the start vertex. 229 | target_vertex: The unique identifier of the target vertex. 230 | 231 | Returns: 232 | list[Any]: A list of vertex keys representing the shortest path from the start vertex 233 | to the target vertex. 234 | """ 235 | if not self.has_vertex(start_vertex): 236 | raise ValueError(f'Start vertex {start_vertex} does not exist!') 237 | if not self.has_vertex(target_vertex): 238 | raise ValueError(f'Target vertex {target_vertex} does not exist!') 239 | 240 | def reconstruct_path(pred: dict[Any, Any], target: Any) -> list[Any]: 241 | # Reconstruct the path from start to target by going back until it finds a vertex 242 | # without predecessor: That can only be the start vertex 243 | path = [] 244 | while target: 245 | path.append(target) 246 | target = pred[target] 247 | return path[::-1] 248 | 249 | distance = {v: float('inf') for v in self._adj} 250 | predecessor = {v: None for v in self._adj} 251 | 252 | queue = Queue(self.vertex_count()) 253 | # Initially, we add the start vertex to the queue 254 | queue.enqueue(start_vertex) 255 | distance[start_vertex] = 0 256 | 257 | while not queue.is_empty(): 258 | u = queue.dequeue() 259 | if u == target_vertex: 260 | # We have found the shortest path to the target 261 | return reconstruct_path(predecessor, target_vertex) 262 | 263 | # For each of u's neighbors, we check if there was already a shorter path to them 264 | for (_, v) in self._get_vertex(u).outgoing_edges(): 265 | if distance[v] == float('inf'): 266 | distance[v] = distance[u] + 1 267 | predecessor[v] = u 268 | queue.enqueue(v) 269 | 270 | #At this point, we know there is no path from the start to the target vertex 271 | return None 272 | 273 | def dfs(self, start_vertex: Any, color: dict[Any, str] = None) -> Tuple[bool, dict[Any, str]]: 274 | """Perform a depth-first search from a given vertex. 275 | Looks for the shortest path from the start vertex to the target vertex. 276 | 277 | Parameters: 278 | start_vertex: The unique identifier of the start vertex. 279 | color: A dictionary mapping vertices to colors. If not provided, 280 | a new dictionary is created. 281 | 282 | Returns: 283 | Tuple[bool, dict[Any, str]]: A tuple containing a boolean indicating whether the graph 284 | is acyclic, and the up-to-date dictionary mapping vertices to colors. 285 | """ 286 | if not self.has_vertex(start_vertex): 287 | raise ValueError(f'Start vertex {start_vertex} does not exist!') 288 | if color is None: 289 | color = {v: 'white' for v in self._adj} 290 | acyclic = True 291 | stack = Stack() 292 | stack.push((False, start_vertex)) 293 | while not stack.is_empty(): 294 | (mark_as_black, v) = stack.pop() 295 | col = color.get(v, 'white') 296 | if mark_as_black: 297 | color[v] = 'black' 298 | elif col == 'grey': 299 | acyclic = False 300 | elif col == 'white': 301 | color[v] = 'grey' 302 | stack.push((True, v)) 303 | for (_, w) in self._get_vertex(v).outgoing_edges(): 304 | stack.push((False, w)) 305 | return acyclic, color 306 | -------------------------------------------------------------------------------- /python/k_largest_elements.py: -------------------------------------------------------------------------------- 1 | from queues.heap import Heap 2 | 3 | def k_largest_elements(arr, k): 4 | """Finds the k largest elements in an array (or as many as there are in the array if k > len(arr)). 5 | Returns a list of tuples, where each tuple is (index, value). 6 | """ 7 | heap = Heap(element_priority=lambda x: -x[1]) 8 | for i in range(len(arr)): 9 | if len(heap) >= k: 10 | if heap.peek()[1] < arr[i]: 11 | heap.top() # remove the smallest element, we don't need the value 12 | heap.insert((i, arr[i])) 13 | heap._validate() 14 | else: 15 | heap.insert((i, arr[i])) 16 | return [heap.top() for _ in range(k)] 17 | 18 | if __name__ == '__main__': 19 | arr = [55, 71, 43, 59, 10, 20, 15, 44, 11, 234, 23,-45] 20 | print(k_largest_elements(arr, 6)) 21 | -------------------------------------------------------------------------------- /python/linked_lists/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlarocca/grokking_data_structures/9c78516f70e44ddc910778a059e6a5d242fcc447/python/linked_lists/__init__.py -------------------------------------------------------------------------------- /python/linked_lists/singly_linked_list.py: -------------------------------------------------------------------------------- 1 | """Module providing an implementation for singly-linked list.""" 2 | from __future__ import annotations 3 | import copy 4 | from typing import Any, Callable, List, Optional 5 | 6 | class SinglyLinkedList: 7 | """ 8 | This class models a singly-linked list data structure. 9 | 10 | A singly-linked list consists of nodes where each node has a reference 11 | to the next node in the list. 12 | 13 | Functionality: 14 | 15 | - Stores nodes containing arbitrary data. 16 | - Supports common linked list operations like insertion, deletion search and traversal. 17 | """ 18 | 19 | class Node: 20 | """ 21 | A node in a singly linked list. Each node contains data and 22 | a reference to the next node. 23 | 24 | Attributes: 25 | 26 | data: The data held in the node. Can be any arbitrary object. 27 | _next: A reference to the next node in the list. 28 | """ 29 | 30 | def __init__(self, data: Any, next_node: SinglyLinkedList.Node = None) -> None: 31 | """ 32 | Initialize a new Node object. 33 | 34 | Parameters: 35 | 36 | - data (Any): The data for the node. Can be any arbitrary object. 37 | - next_node (Node, optional): The next Node in the list. Defaults to None. 38 | """ 39 | 40 | self._data = data 41 | self._next = next_node 42 | 43 | 44 | def __str__(self) -> str: 45 | """ 46 | Return a string representation of the node's data. 47 | 48 | Parameters: 49 | None 50 | 51 | Returns: 52 | str: String representation of the node's data. 53 | """ 54 | 55 | return str(self.data()) 56 | 57 | 58 | def __repr__(self) -> str: 59 | """ 60 | Return a string (internal) representation of the node's data. 61 | 62 | Parameters: 63 | None 64 | 65 | Returns: 66 | str: String representation of the node's data. 67 | """ 68 | 69 | return repr(self.data()) 70 | 71 | 72 | def data(self) -> Any: 73 | """ 74 | Get the data stored in this node. 75 | 76 | Parameters: 77 | None 78 | 79 | Returns: 80 | Any: The data stored in this node. Can be any arbitrary object. 81 | """ 82 | 83 | return self._data 84 | 85 | 86 | def next(self) -> SinglyLinkedList.Node: 87 | """ 88 | Return the successor of the current node. 89 | 90 | Parameters: 91 | None 92 | 93 | Returns: 94 | Node: The next node in the singly-linked list. 95 | """ 96 | 97 | return self._next 98 | 99 | 100 | def has_next(self) -> bool: 101 | """ 102 | Check if the node has a successor. 103 | 104 | Parameters: 105 | None 106 | 107 | Returns: 108 | bool: True if the node has a next node, False otherwise. 109 | """ 110 | 111 | return self._next is not None 112 | 113 | 114 | def append(self, next_node: Optional[SinglyLinkedList.Node]) -> None: 115 | """ 116 | Append a node to the current one. 117 | 118 | Parameters: 119 | next_node (Optional[Node]): The node to append after the current node, or 120 | `None` to indicate no successor. 121 | """ 122 | 123 | self._next = next_node 124 | 125 | 126 | # --- SinglyLinkedList methods --- 127 | 128 | 129 | def __init__(self) -> None: 130 | """ 131 | Initialize a new empty SinglyLinkedList. 132 | 133 | Parameters: 134 | None 135 | 136 | Attributes: 137 | _head (Node): The head node of the list. Initialized to None. 138 | """ 139 | 140 | self._head = None 141 | 142 | 143 | def __len__(self): 144 | """ 145 | Return the length of the linked list. 146 | 147 | Parameters: 148 | None 149 | 150 | Returns: 151 | int: The number of nodes in the linked list. 152 | """ 153 | 154 | return len(self.traverse(lambda x: x)) 155 | 156 | 157 | def __repr__(self) -> str: 158 | """ 159 | Return the string (internal) representation of the linked list. 160 | 161 | Parameters: 162 | None 163 | 164 | Returns: 165 | str: The string representation of the linked list nodes. 166 | """ 167 | 168 | return f'SinglyLinkedList({"->".join(self.traverse(repr))})' 169 | 170 | 171 | def __str__(self) -> str: 172 | """ 173 | Return the string representation of the linked list. 174 | 175 | Parameters: 176 | None 177 | 178 | Returns: 179 | str: The string representation of the linked list nodes. 180 | 181 | Functionality: 182 | Traverses the linked list using the traverse() method, 183 | passing in repr() to convert each node to a string. 184 | Joins the node string representations with '->' and returns the result. 185 | """ 186 | 187 | return '->'.join(self.traverse(str)) 188 | 189 | 190 | def __iter__(self): 191 | ''' 192 | Iterate over the values in the linked list. 193 | 194 | Parameters: 195 | None 196 | 197 | Functionality: 198 | Iterates over the values in the linked list. The iteration starts at the beginning 199 | of the list and goes on until it reaches the tail of the list. 200 | ''' 201 | 202 | current = self._head 203 | while current is not None: 204 | data = current.data() 205 | current = current.next() 206 | yield data 207 | 208 | 209 | def size(self) -> int: 210 | """ 211 | Return the length of the linked list. 212 | 213 | Parameters: 214 | None 215 | 216 | Returns: 217 | int: The number of nodes in the linked list. 218 | """ 219 | 220 | size = 0 221 | current = self._head 222 | while current is not None: 223 | size += 1 224 | current = current.next() 225 | return size 226 | 227 | 228 | def is_empty(self) -> bool: 229 | """ 230 | Check if the linked list is empty. 231 | 232 | Parameters: 233 | None 234 | 235 | Returns: 236 | bool: True if the linked list is empty, False otherwise. 237 | """ 238 | 239 | return self._head is None 240 | 241 | 242 | def insert_in_front(self, data: Any) -> None: 243 | """ 244 | Add a node to the beginning of the list. 245 | 246 | Parameters: 247 | - data (Any): The data for the new node to add. 248 | """ 249 | 250 | old_head = self._head 251 | self._head = SinglyLinkedList.Node(data, old_head) 252 | 253 | 254 | def insert_to_back(self, data: Any) -> None: 255 | """ 256 | Append a node to the end of the list. 257 | 258 | Parameters: 259 | - data (Any): The data for the new node to append. 260 | 261 | Warning: this method requires traversing a linear number (O(n)) 262 | of nodes. 263 | """ 264 | 265 | current = self._head 266 | if current is None: 267 | self._head = SinglyLinkedList.Node(data) 268 | else: 269 | while current.next() is not None: 270 | current = current.next() 271 | current.append(SinglyLinkedList.Node(data)) 272 | 273 | 274 | def get(self, index): 275 | """ 276 | Get the data at the given index. 277 | 278 | Parameters: 279 | index (int): The index of the element to retrieve, starting from the head of the list. 280 | 281 | Returns: 282 | Any: A deep copy of the data at the given index if found. 283 | 284 | Error Handling: 285 | Raising an IndexError if index is invalid. 286 | """ 287 | if index < 0: 288 | raise IndexError("Index must be non-negative") 289 | current = self._head 290 | current_index = 0 291 | while current_index < index and current is not None: 292 | current = current.next() 293 | current_index += 1 294 | if current is None: 295 | raise IndexError("Index out of bounds") 296 | # Here we know that we are at the right index 297 | return copy.deepcopy(current.data()) 298 | 299 | 300 | def traverse(self, functor: Callable[..., Any]) -> List[Any]: 301 | """ 302 | Traverse the linked list and apply a function to each node's data. 303 | 304 | Parameters: 305 | functor (Callable): The function (or functor) to apply to each node's data. 306 | 307 | Returns: 308 | List[Any]: A list containing the result of applying the function 309 | to each node's data. 310 | """ 311 | 312 | current = self._head 313 | result = [] 314 | while current is not None: 315 | result.append(functor(current.data())) 316 | current = current.next() 317 | return result 318 | 319 | 320 | def _search(self, target: Any) -> Optional[SinglyLinkedList.Node]: 321 | """ 322 | Search the list for a node with the data matching `target`, and return the node found. 323 | 324 | Parameters: 325 | - target (Any): The data to search for in the list. 326 | 327 | Returns: 328 | - Optional[Node]: The node containing the target data if found, 329 | otherwise None. 330 | """ 331 | current = self._head 332 | while current is not None: 333 | if current.data() == target: 334 | return current 335 | current = current.next() 336 | return None 337 | 338 | 339 | def search(self, predicate: Callable[..., Any]) -> Optional[Any]: 340 | """ 341 | Search the list for the first node whose data matches the predicate function. 342 | 343 | Parameters: 344 | predicate (Callable): The predicate function to evaluate node data against. 345 | Should accept a single parameter for the node data. 346 | 347 | Returns: 348 | Optional[Any]: The first element for which predicate(element) is True, 349 | or None if no match is found. 350 | """ 351 | current = self._head 352 | while current is not None: 353 | if predicate(current.data()): 354 | return current.data() 355 | current = current.next() 356 | return None 357 | 358 | 359 | def delete(self, target: Any) -> None: 360 | """ 361 | Delete the first node with the given data from the list. 362 | 363 | Parameters: 364 | target (Any): The data value to delete from the list. 365 | 366 | Returns: 367 | None 368 | 369 | Error Handling: 370 | If no match is found after full traversal, raises a ValueError. 371 | """ 372 | 373 | current = self._head 374 | previous = None 375 | while current is not None: 376 | if current.data() == target: 377 | if previous is None: 378 | self._head = current.next() 379 | else: 380 | previous.append(current.next()) 381 | return 382 | previous = current 383 | current = current.next() 384 | # If it gets here, it hasn't found the target 385 | raise ValueError(f'No element with value {target} was found in the list.') 386 | 387 | 388 | def delete_from_front(self) -> Any: 389 | """ 390 | Delete the first node in the list and return the data it held. 391 | 392 | Parameters: 393 | None 394 | 395 | Returns: 396 | The data held by the node that was deleted from the front. 397 | 398 | Error Handling: 399 | If the list is empty, raises a ValueError. 400 | """ 401 | 402 | if self.is_empty(): 403 | raise ValueError('Delete on an empty list.') 404 | data = self._head.data() 405 | self._head = self._head.next() 406 | return data 407 | -------------------------------------------------------------------------------- /python/linked_lists/sorted_singly_linked_list.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | from .singly_linked_list import SinglyLinkedList 3 | 4 | class SortedSinglyLinkedList(SinglyLinkedList): 5 | """ A sorted version of the singly-linked lists. 6 | """ 7 | def insert_in_front(self, data: Any) -> None: 8 | raise NotImplementedError('This method is not available for sorted lists') 9 | 10 | 11 | def insert_to_back(self, data: Any) -> None: 12 | raise NotImplementedError('This method is not available for sorted lists') 13 | 14 | def insert(self, new_data: Any) -> None: 15 | """ 16 | Insert a new value into the sorted singly linked list. 17 | 18 | Parameters: 19 | new_data (Any): The new data value to insert. 20 | 21 | Returns: 22 | None 23 | """ 24 | current = self._head 25 | previous = None 26 | while current is not None: 27 | if current.data() >= new_data: 28 | if previous is None: 29 | self._head = SinglyLinkedList.Node(new_data, current) # Add the element at the beginning of the list 30 | else: 31 | previous.append(SinglyLinkedList.Node(new_data, current)) # General case 32 | return 33 | previous = current 34 | current = current.next() 35 | if previous is None: 36 | self._head = SinglyLinkedList.Node(new_data) # The list is empty 37 | else: 38 | previous.append(SinglyLinkedList.Node(new_data, None)) # Add the element at the end of the list 39 | -------------------------------------------------------------------------------- /python/profiling/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlarocca/grokking_data_structures/9c78516f70e44ddc910778a059e6a5d242fcc447/python/profiling/__init__.py -------------------------------------------------------------------------------- /python/profiling/stack_profiling.py: -------------------------------------------------------------------------------- 1 | """Profile different stack implementations to analyze performance.""" 2 | import cProfile 3 | import pstats 4 | from pstats import SortKey 5 | import random 6 | 7 | from typing import Type 8 | from stacks.stack import Stack 9 | from stacks.stack_dynamic_array import Stack as ArrayStack 10 | 11 | ITERATIONS = 10000000 12 | RUNS = 1 13 | 14 | def random_actions(prof, actions: int, stack: Type[Stack], array_stack: Type[ArrayStack]) -> None: 15 | """Generate random push/pop actions on the stack.""" 16 | for i in range(actions): 17 | action = random.choice(["push", "push", "pop"]) 18 | if action == "push": 19 | val = random.randint(0, 100) 20 | prof.runcall(stack.push, val) 21 | prof.runcall(array_stack.push, val) 22 | else: 23 | try: 24 | prof.runcall(stack.pop) 25 | prof.runcall(array_stack.pop) 26 | except ValueError as _: 27 | pass 28 | 29 | 30 | def start_profiling(prof, iterations: int) -> None: 31 | stack = Stack() 32 | array_stack = ArrayStack() 33 | random_actions(prof, iterations, stack, array_stack) 34 | 35 | if __name__ == '__main__': 36 | pro = cProfile.Profile() 37 | for _ in range(RUNS): 38 | start_profiling(pro, ITERATIONS) 39 | st = pstats.Stats(pro).sort_stats(SortKey.FILENAME, SortKey.CUMULATIVE).strip_dirs() 40 | #ps = st.strip_dirs().stats 41 | print(st.print_stats('stack')) 42 | print(st.print_stats('linked_list')) 43 | -------------------------------------------------------------------------------- /python/queues/heap.py: -------------------------------------------------------------------------------- 1 | from typing import Any, List, Optional 2 | 3 | class Heap: 4 | """ Implementation of a binary heap. 5 | The heap is a max-heap, meaning that the element with the highest priority is at the root. 6 | The priority of an element, however, can be computed using a function passed 7 | as an argument to the constructor. 8 | """ 9 | 10 | def __init__(self, elements: List[Any] = None, element_priority = lambda x: x) -> None: 11 | """Constructor for the heap. 12 | 13 | Args: 14 | elements: The elements for initializing the heap. By default, the heap is empty. 15 | element_priority: A function that extracts the priority of an element. 16 | By default, the priority is the element itself. 17 | """ 18 | self._priority = element_priority 19 | if elements is not None and len(elements) > 0: 20 | self._heapify(elements) 21 | else: 22 | self._elements = [] 23 | 24 | 25 | def __len__(self) -> int: 26 | """Size of the heap. 27 | 28 | Returns: The number of elements in the heap. 29 | """ 30 | return len(self._elements) 31 | 32 | 33 | def _has_lower_priority(self, element_1: Any, element_2: Any) -> bool: 34 | """Checks if the first element has lower priority to the second element.""" 35 | return self._priority(element_1) < self._priority(element_2) 36 | 37 | 38 | def _has_higher_priority(self, element_1: Any, element_2: Any) -> bool: 39 | """Checks if the first element has higher priority to the second element.""" 40 | return self._priority(element_1) > self._priority(element_2) 41 | 42 | 43 | def _validate(self) -> bool: 44 | """Checks that the three invariants for heaps are abided by. 45 | 1. Every node has at most 2 children. (Guaranteed by construction) 46 | 2. The heap tree is complete and left-adjusted.(Also guaranteed by construction) 47 | 3. Every node holds the highest priority in the subtree rooted at that node. 48 | 49 | Returns: True if all the heap invariants are met. 50 | """ 51 | current_index = 0 52 | first_leaf = self._first_leaf_index() 53 | while current_index < first_leaf: 54 | current_element: float = self._elements[current_index] 55 | first_child = self._left_child_index(current_index) 56 | last_child_guard = min(first_child + 2, len(self)) 57 | for child_index in range(first_child, last_child_guard): 58 | if self._has_lower_priority(current_element, self._elements[child_index]): 59 | return False # pragma: no cover 60 | current_index += 1 61 | return True 62 | 63 | 64 | def _left_child_index(self, index) -> int: 65 | """Computes the index of the left child of a heap node. 66 | 67 | Args: 68 | index: The index of current node, for which we need to find children's indices. 69 | 70 | Returns: The index of the left-most child for current heap node. 71 | """ 72 | return index * 2 + 1 73 | 74 | 75 | def _parent_index(self, index: int) -> int: 76 | """Computes the index of the parent of a heap node. 77 | 78 | Args: 79 | index: The index of current node, for which we need to find its parent's indices. 80 | 81 | Returns: The index of the parent of current heap node. 82 | 83 | """ 84 | return (index - 1) // 2 85 | 86 | 87 | def _highest_priority_child_index(self, index: int) -> Optional[int]: 88 | """Finds, among the children of a heap node, the one child with highest priority. 89 | In case multiple children have the same priority, the left-most is returned. 90 | 91 | Args: 92 | index: The index of the heap node whose children are searched. 93 | 94 | Returns: The index of the child of current heap node with highest priority, or None if 95 | current node has no child. 96 | """ 97 | first_index = self._left_child_index(index) 98 | 99 | if first_index >= len(self): 100 | # The current element has no children 101 | return None 102 | 103 | if first_index + 1 >= len(self): 104 | # The current element only has one child 105 | return first_index 106 | 107 | if self._has_higher_priority(self._elements[first_index], self._elements[first_index + 1]): 108 | return first_index 109 | else: 110 | return first_index + 1 111 | 112 | 113 | def _first_leaf_index(self): 114 | """Computes the index of the first leaf of the heap. 115 | A leaf is the first node that has no children. 116 | For a binary heap, we know that's exactly the node in the middle of the array. 117 | """ 118 | return len(self) // 2 119 | 120 | 121 | def _push_down(self, index: int) -> None: 122 | """Pushes down the root of a sub-heap towards its leaves to reinstate heap invariants. 123 | If any of the children of the element has a higher priority, then it swaps current 124 | element with its highest-priority child C, and recursively checks the sub-heap previously 125 | rooted at that C. 126 | 127 | Args: 128 | index: The index of the root of the sub-heap. 129 | """ 130 | 131 | # INVARIANT: 0 <= index < n 132 | assert 0 <= index < len(self._elements) 133 | element = self._elements[index] 134 | current_index = index 135 | while True: 136 | child_index = self._highest_priority_child_index(current_index) 137 | if child_index is None: 138 | break 139 | if self._has_lower_priority(element, self._elements[child_index]): 140 | self._elements[current_index] = self._elements[child_index] 141 | current_index = child_index 142 | else: 143 | break 144 | 145 | self._elements[current_index] = element 146 | 147 | 148 | def _bubble_up(self, index: int) -> None: 149 | """Bubbles up towards the root an element, to reinstate heap's invariants. 150 | If the parent P of an element has lower priority, then swaps current element and its parent, 151 | and then recursively check the position previously held by the P. 152 | 153 | Args: 154 | index: The index of the element to bubble up. 155 | """ 156 | # INVARIANT: 0 <= index < n 157 | assert 0 <= index < len(self._elements) 158 | element = self._elements[index] 159 | while index > 0: 160 | parent_index = self._parent_index(index) 161 | parent = self._elements[parent_index] 162 | if self._has_higher_priority(element, parent): 163 | self._elements[index] = parent 164 | index = parent_index 165 | else: 166 | break 167 | 168 | self._elements[index] = element 169 | 170 | 171 | def _heapify(self, elements: List[Any]) -> None: 172 | """Initializes the heap with a list of elements and priorities. 173 | 174 | Args: 175 | elements: The list of elements to add to the heap. 176 | priorities: The priorities for those elements (in the same order they are presented). 177 | """ 178 | self._elements = elements[:] 179 | last_inner_node_index = self._first_leaf_index() - 1 180 | for index in range(last_inner_node_index, -1, -1): 181 | self._push_down(index) 182 | 183 | 184 | def is_empty(self) -> bool: 185 | """Checks if the heap is empty. 186 | 187 | Returns: True if the heap is empty. 188 | 189 | """ 190 | return len(self) == 0 191 | 192 | 193 | def top(self) -> Any: 194 | """Removes and returns the highest-priority element in the heap. 195 | If the heap is empty, raises a `ValueError`. 196 | 197 | Returns: The element with highest priority in the heap. 198 | """ 199 | if self.is_empty(): 200 | raise ValueError('Method top called on an empty heap.') 201 | if len(self) == 1: 202 | element = self._elements.pop() 203 | else: 204 | element = self._elements[0] 205 | self._elements[0] = self._elements.pop() 206 | self._push_down(0) 207 | 208 | return element 209 | 210 | 211 | def peek(self) -> Any: 212 | """Returns, WITHOUT removing it, the highest-priority element in the heap. 213 | If the heap is empty, raises a `ValueError`. 214 | 215 | Returns: A reference to the element with highest priority in the heap. 216 | """ 217 | if self.is_empty(): 218 | raise ValueError('Method peek called on an empty heap.') 219 | return self._elements[0] 220 | 221 | 222 | def insert(self, element: Any) -> None: 223 | """Add a new element/priority pair to the heap 224 | 225 | Args: 226 | element: The new element to add. 227 | priority: The priority associated with the new element 228 | """ 229 | self._elements.append(element) 230 | self._bubble_up(len(self._elements) - 1) 231 | -------------------------------------------------------------------------------- /python/queues/queue.py: -------------------------------------------------------------------------------- 1 | """Module providing an implementation for queue, using a statically-sized 2 | array to store the elements.""" 3 | 4 | from typing import Any 5 | 6 | class Queue: 7 | """ A class modeling the queue container. 8 | """ 9 | 10 | def __init__(self, max_size): 11 | """ Creates a static array with size `max_size`. 12 | """ 13 | if max_size <= 1: 14 | raise ValueError(f'Invalid size (a queue must have at least two elements): {max_size}') 15 | self._data = [None] * max_size 16 | self._max_size = max_size 17 | self._front = 0 18 | self._rear = 0 19 | self._size = 0 20 | 21 | 22 | def __len__(self): 23 | """ 24 | Return the size of the queue. 25 | 26 | Parameters: 27 | None 28 | 29 | Returns: 30 | int: The number of values stored in the queue. 31 | """ 32 | return self._size 33 | 34 | 35 | def __iter__(self): 36 | """ Iterates on the elements of a queue. 37 | Warning: by doing so, the queue will be emptied. 38 | """ 39 | while not self.is_empty(): 40 | yield self.dequeue() 41 | 42 | 43 | def __str__(self): 44 | """ 45 | Return the string representation of the queue. 46 | 47 | Parameters: 48 | None 49 | 50 | Returns: 51 | str: The string representation of the queue. 52 | """ 53 | def iterate(): 54 | # We don't normally allow iterating on the elements of a queue without dequeueing them 55 | if self.is_empty(): 56 | return 57 | front = self._front 58 | if front > self._rear: 59 | while front < self._max_size: 60 | yield self._data[front] 61 | front += 1 62 | front = 0 63 | while front < self._rear: 64 | yield self._data[front] 65 | front += 1 66 | 67 | return str([x for x in iterate()]) 68 | 69 | 70 | def __repr__(self): 71 | """ 72 | Return the string (internal) representation of the queue. 73 | 74 | Parameters: 75 | None 76 | 77 | Returns: 78 | str: The string representation of the queue. 79 | """ 80 | return f'Queue({str(self)})' 81 | 82 | 83 | def is_empty(self) -> bool: 84 | """ 85 | Check if the queue is empty. 86 | 87 | Parameters: 88 | None 89 | 90 | Returns: 91 | bool: True if the queue is empty, False otherwise. 92 | """ 93 | return len(self) == 0 94 | 95 | 96 | 97 | def is_full(self) -> bool: 98 | """ 99 | Check if the queue is full. 100 | 101 | Parameters: 102 | None 103 | 104 | Returns: 105 | bool: True if the queue is full, False otherwise. 106 | """ 107 | return len(self) == self._max_size 108 | 109 | 110 | def enqueue(self, value: Any) -> None: 111 | """ 112 | Add a new value to the rear of the queue. 113 | 114 | Parameters: 115 | value (Any): The value to insert into the queue. 116 | 117 | Returns: 118 | None 119 | """ 120 | if self.is_full(): 121 | raise ValueError('The queue is already full!') 122 | self._data[self._rear] = value 123 | self._rear = (self._rear + 1) % self._max_size 124 | self._size += 1 125 | 126 | 127 | def dequeue(self) -> Any: 128 | """ 129 | Remove and return the oldest value added to the queue. 130 | 131 | Parameters: 132 | None 133 | 134 | Returns: 135 | Any: The value removed from the queue. 136 | 137 | Raises: 138 | ValueError: If the queue is empty. 139 | """ 140 | if self.is_empty(): 141 | raise ValueError("Cannot dequeue from an empty queue") 142 | 143 | value = self._data[self._front] 144 | self._front = (self._front + 1) % self._max_size 145 | self._size -= 1 146 | return value 147 | -------------------------------------------------------------------------------- /python/queues/queue_linked_list.py: -------------------------------------------------------------------------------- 1 | """Module providing an implementation for queue, using doubly-linked lists to store the elements.""" 2 | 3 | from typing import Any 4 | from linked_lists.doubly_linked_list import DoublyLinkedList 5 | 6 | class Queue: 7 | """ A class modeling the queue container. 8 | """ 9 | 10 | def __init__(self): 11 | """ Creates an empty linked_list. 12 | """ 13 | self._data = DoublyLinkedList() 14 | 15 | 16 | def __len__(self): 17 | """ 18 | Return the size of the queue. 19 | 20 | Parameters: 21 | None 22 | 23 | Returns: 24 | int: The number of values stored in the queue. 25 | """ 26 | return len(self._data) 27 | 28 | 29 | 30 | def __iter__(self): 31 | """ Iterates on the elements of a queue. 32 | Warning: by doing so, the queue will be emptied. 33 | """ 34 | while not self.is_empty(): 35 | yield self.dequeue() 36 | 37 | 38 | def __str__(self): 39 | """ 40 | Return the string representation of the queue. 41 | 42 | Parameters: 43 | None 44 | 45 | Returns: 46 | str: The string representation of the queue. 47 | """ 48 | return str(self._data) 49 | 50 | 51 | def __repr__(self): 52 | """ 53 | Return the string (internal) representation of the queue. 54 | 55 | Parameters: 56 | None 57 | 58 | Returns: 59 | str: The string representation of the queue. 60 | """ 61 | return f'Queue({str(self._data)})' 62 | 63 | 64 | def is_empty(self) -> bool: 65 | """ 66 | Check if the queue is empty. 67 | 68 | Parameters: 69 | None 70 | 71 | Returns: 72 | bool: True if the queue is empty, False otherwise. 73 | """ 74 | return self._data.is_empty() 75 | 76 | 77 | def enqueue(self, value: Any) -> None: 78 | """ 79 | Add a new value to the rear of the queue. 80 | 81 | Parameters: 82 | value (Any): The value to insert into the queue. 83 | 84 | Returns: 85 | None 86 | """ 87 | self._data.insert_to_back(value) 88 | 89 | 90 | def dequeue(self) -> Any: 91 | """ 92 | Remove and return the last value added to the queue. 93 | 94 | Parameters: 95 | None 96 | 97 | Returns: 98 | Any: The value removed from the queue. 99 | 100 | Raises: 101 | ValueError: If the queue is empty. 102 | """ 103 | if self.is_empty(): 104 | raise ValueError("Cannot dequeue from an empty queue") 105 | return self._data.delete_from_front() 106 | -------------------------------------------------------------------------------- /python/stacks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlarocca/grokking_data_structures/9c78516f70e44ddc910778a059e6a5d242fcc447/python/stacks/__init__.py -------------------------------------------------------------------------------- /python/stacks/stack.py: -------------------------------------------------------------------------------- 1 | """Module providing an implementation for stack, using singly-linked lists to store the elements.""" 2 | 3 | import copy 4 | from typing import Any 5 | from linked_lists.singly_linked_list import SinglyLinkedList 6 | 7 | class Stack: 8 | """ A class modeling the stack container. 9 | """ 10 | def __init__(self) -> None: 11 | """ Creates an empty stack. 12 | """ 13 | self._data = SinglyLinkedList() 14 | 15 | 16 | def __len__(self): 17 | """ 18 | Return the size of the stack. 19 | 20 | Parameters: 21 | None 22 | 23 | Returns: 24 | int: The number of values stored in the stack. 25 | """ 26 | return len(self._data) 27 | 28 | 29 | 30 | def __iter__(self): 31 | """ Iterates on the elements of a stack. 32 | Warning: by doing so, the queue will be emptied. 33 | """ 34 | while not self.is_empty(): 35 | yield self.pop() 36 | 37 | 38 | def __str__(self): 39 | """ 40 | Return the string representation of the stack. 41 | 42 | Parameters: 43 | None 44 | 45 | Returns: 46 | str: The string representation of the stack. 47 | """ 48 | return str(self._data) 49 | 50 | 51 | def __repr__(self): 52 | """ 53 | Return the string (internal) representation of the stack. 54 | 55 | Parameters: 56 | None 57 | 58 | Returns: 59 | str: The string representation of the stack. 60 | """ 61 | return f'Stack({str(self._data)})' 62 | 63 | 64 | def is_empty(self) -> bool: 65 | """ 66 | Check if the stack is empty. 67 | 68 | Parameters: 69 | None 70 | 71 | Returns: 72 | bool: True if the stack is empty, False otherwise. 73 | """ 74 | return self._data.is_empty() 75 | 76 | 77 | def push(self, value: Any) -> None: 78 | """ 79 | Add a new value to the stack. 80 | 81 | Parameters: 82 | value (Any): The value to insert into the stack. 83 | 84 | Returns: 85 | None 86 | """ 87 | self._data.insert_in_front(value) 88 | 89 | 90 | def pop(self) -> Any: 91 | """ 92 | Remove and return the last value added to the stack. 93 | 94 | Parameters: 95 | None 96 | 97 | Returns: 98 | Any: The value removed from the stack. 99 | 100 | Raises: 101 | ValueError: If the stack is empty. 102 | """ 103 | if self.is_empty(): 104 | raise ValueError("Cannot pop from an empty stack") 105 | return self._data.delete_from_front() 106 | 107 | 108 | def peek(self) -> Any: 109 | """ 110 | Return the last value added to the stack without removing it. 111 | 112 | Parameters: 113 | None 114 | 115 | Returns: 116 | Any: The value at the top of the stack. 117 | 118 | Raises: 119 | ValueError: If the stack is empty. 120 | """ 121 | if self.is_empty(): 122 | raise ValueError("Cannot peek at an empty stack") 123 | # We need to deepcopy the data from the list, otherwise 124 | # anyone with a reference can change the underlying data. 125 | return copy.deepcopy(self._data._head.data()) 126 | -------------------------------------------------------------------------------- /python/stacks/stack_dynamic_array.py: -------------------------------------------------------------------------------- 1 | """Module providing an implementation for stack, using singly-linked lists to store the elements.""" 2 | 3 | import copy 4 | from typing import Any 5 | 6 | class Stack: 7 | """ A class modeling the stack container. 8 | """ 9 | def __init__(self) -> None: 10 | """ Creates an empty stack. 11 | """ 12 | self._data = [] 13 | 14 | 15 | def __len__(self): 16 | """ 17 | Return the size of the stack. 18 | 19 | Parameters: 20 | None 21 | 22 | Returns: 23 | int: The number of values stored in the stack. 24 | """ 25 | return len(self._data) 26 | 27 | 28 | def __iter__(self): 29 | """ Iterates on the elements of a stack. 30 | Warning: by doing so, the queue will be emptied. 31 | """ 32 | while not self.is_empty(): 33 | yield self.pop() 34 | 35 | 36 | def __str__(self): 37 | """ 38 | Return the string representation of the stack. 39 | 40 | Parameters: 41 | None 42 | 43 | Returns: 44 | str: The string representation of the stack. 45 | """ 46 | return str(self._data[::-1]) 47 | 48 | 49 | def __repr__(self): 50 | """ 51 | Return the string (internal) representation of the stack. 52 | 53 | Parameters: 54 | None 55 | 56 | Returns: 57 | str: The string representation of the stack. 58 | """ 59 | return f'Stack({str(self._data[::-1])})' 60 | 61 | 62 | def is_empty(self) -> bool: 63 | """ 64 | Check if the stack is empty. 65 | 66 | Parameters: 67 | None 68 | 69 | Returns: 70 | bool: True if the stack is empty, False otherwise. 71 | """ 72 | return len(self._data) == 0 73 | 74 | 75 | def push(self, value: Any) -> None: 76 | """ 77 | Add a new value to the stack. 78 | 79 | Parameters: 80 | value (Any): The value to insert into the stack. 81 | 82 | Returns: 83 | None 84 | """ 85 | self._data.append(value) 86 | 87 | 88 | def pop(self) -> Any: 89 | """ 90 | Remove and return the last value added to the stack. 91 | 92 | Parameters: 93 | None 94 | 95 | Returns: 96 | Any: The value removed from the stack. 97 | 98 | Raises: 99 | ValueError: If the stack is empty. 100 | """ 101 | if self.is_empty(): 102 | raise ValueError("Cannot pop from an empty stack") 103 | return self._data.pop() 104 | 105 | 106 | def peek(self) -> Any: 107 | """ 108 | Return the last value added to the stack without removing it. 109 | 110 | Parameters: 111 | None 112 | 113 | Returns: 114 | Any: The value at the top of the stack. 115 | 116 | Raises: 117 | ValueError: If the stack is empty. 118 | """ 119 | if self.is_empty(): 120 | raise ValueError("Cannot peek at an empty stack") 121 | # We need to deepcopy the data from the list, otherwise 122 | # anyone with a reference can change the underlying data. 123 | return copy.deepcopy(self._data[-1]) 124 | -------------------------------------------------------------------------------- /python/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlarocca/grokking_data_structures/9c78516f70e44ddc910778a059e6a5d242fcc447/python/tests/__init__.py -------------------------------------------------------------------------------- /python/tests/test_bag.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from bags.bag import Bag 3 | 4 | class TestBag(unittest.TestCase): 5 | 6 | def test_init(self): 7 | bag = Bag() 8 | self.assertEqual(len(bag), 0) 9 | 10 | 11 | def test_len(self): 12 | bag = Bag() 13 | self.assertEqual(len(bag), 0) 14 | 15 | bag.insert(1) 16 | self.assertEqual(len(bag), 1) 17 | 18 | bag.insert(2) 19 | self.assertEqual(len(bag), 2) 20 | 21 | bag.insert(2) 22 | self.assertEqual(len(bag), 3) 23 | 24 | 25 | def test_repr(self): 26 | bag = Bag() 27 | self.assertEqual(repr(bag), 'Bag()') 28 | 29 | bag.insert(1) 30 | self.assertEqual(repr(bag), 'Bag(1)') 31 | 32 | bag.insert(2) 33 | bag.insert(3.14) 34 | # When more than one element is in the bag, we can't put constraints on the order 35 | self.assertTrue(repr(bag).startswith('Bag(')) 36 | self.assertSetEqual(set(repr(bag).replace('Bag(', '').replace(')', '').split(',')), {'1', '2', '3.14'}) 37 | 38 | 39 | def test_str(self): 40 | bag = Bag() 41 | self.assertEqual(str(bag), '') 42 | 43 | bag.insert('a') 44 | self.assertEqual(str(bag), 'a') 45 | 46 | bag.insert('b') 47 | bag.insert('c') 48 | # When more than one element is in the bag, we can't put constraints on the order 49 | self.assertEqual(set(str(bag).split(',')), {'c', 'b', 'a'}) 50 | 51 | 52 | def test_iter(self): 53 | bag = Bag() 54 | 55 | # Iterate over empty list 56 | self.assertEqual(list(bag), []) 57 | 58 | # Iterate over non-empty list 59 | bag.insert(1) 60 | bag.insert(2) 61 | bag.insert(3.14) 62 | bag.insert("AC") 63 | 64 | expected = {1, 2, 3.14, "AC"} 65 | self.assertSetEqual(set(bag), expected) 66 | 67 | 68 | def test_is_empty(self): 69 | bag = Bag() 70 | 71 | # Check empty bag 72 | self.assertTrue(bag.is_empty()) 73 | 74 | # Check non-empty bag 75 | bag.insert(1) 76 | self.assertFalse(bag.is_empty()) 77 | -------------------------------------------------------------------------------- /python/tests/test_bst.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from trees.bst import BinarySearchTree 3 | 4 | class TestBinarySearchTree(unittest.TestCase): 5 | 6 | def test_repr(self): 7 | bst = BinarySearchTree() 8 | self.assertEqual(repr(bst), 'BinarySearchTree()') 9 | 10 | bst.insert(1) 11 | self.assertEqual(repr(bst), 'BinarySearchTree(1 ()())') 12 | 13 | bst.insert(2) 14 | bst.insert(-3.14) 15 | self.assertEqual(repr(bst), 'BinarySearchTree(1 (-3.14 ()())(2 ()()))') 16 | 17 | 18 | def test_str(self): 19 | bst = BinarySearchTree() 20 | self.assertEqual(str(bst), '') 21 | 22 | bst.insert('a') 23 | self.assertEqual(str(bst), 'a ()()') 24 | 25 | bst.insert('b') 26 | bst.insert('d') 27 | self.assertEqual(str(bst), 'a ()(b ()(d ()()))') 28 | bst.insert('c') 29 | self.assertEqual(str(bst), 'a ()(b ()(d (c ()())()))') 30 | bst.insert('Z') 31 | self.assertEqual(str(bst), 'a (Z ()())(b ()(d (c ()())()))') 32 | 33 | def test_len(self): 34 | bst = BinarySearchTree() 35 | self.assertEqual(len(bst), 0) 36 | 37 | bst.insert('a') 38 | self.assertEqual(len(bst), 1) 39 | 40 | bst.insert('b') 41 | bst.insert('d') 42 | self.assertEqual(len(bst), 3) 43 | bst.insert('c') 44 | self.assertEqual(len(bst), 4) 45 | bst.insert('Z') 46 | self.assertEqual(len(bst), 5) 47 | 48 | def test_contains(self): 49 | bst = BinarySearchTree() 50 | self.assertFalse(bst.contains('a')) 51 | self.assertFalse(bst.contains('b')) 52 | self.assertFalse(bst.contains('c')) 53 | self.assertFalse(bst.contains('d')) 54 | self.assertFalse(bst.contains('Z')) 55 | 56 | bst.insert('a') 57 | self.assertTrue(bst.contains('a')) 58 | self.assertFalse(bst.contains('b')) 59 | self.assertFalse(bst.contains('c')) 60 | self.assertFalse(bst.contains('d')) 61 | self.assertFalse(bst.contains('Z')) 62 | 63 | bst.insert('b') 64 | bst.insert('d') 65 | self.assertTrue(bst.contains('a')) 66 | self.assertTrue(bst.contains('b')) 67 | self.assertFalse(bst.contains('c')) 68 | self.assertTrue(bst.contains('d')) 69 | self.assertFalse(bst.contains('Z')) 70 | 71 | bst.insert('c') 72 | self.assertTrue(bst.contains('c')) 73 | bst.insert('Z') 74 | self.assertTrue(bst.contains('Z')) 75 | bst.insert('A') 76 | self.assertTrue(bst.contains('A')) 77 | 78 | def test_insert_delete(self): 79 | bst = BinarySearchTree() 80 | 81 | # Delete from an empty BST 82 | with self.assertRaises(ValueError): 83 | bst.delete(6) 84 | 85 | bst.insert(6) 86 | bst.insert(4) 87 | bst.insert(7) 88 | bst.insert(5) 89 | bst.insert(2) 90 | bst.insert(3) 91 | bst.insert(9) 92 | bst.insert(1) 93 | bst.insert(6) 94 | bst.insert(8) 95 | bst.insert(11) 96 | 97 | self.assertEqual(len(bst), 11) 98 | 99 | # Value to be deleted not found 100 | with self.assertRaises(ValueError): 101 | bst.delete(0) 102 | 103 | # Delete a node with no children 104 | self.assertTrue(bst.contains(1)) 105 | self.assertTrue(bst.contains(8)) 106 | bst.delete(1) 107 | bst.delete(8) 108 | self.assertEqual(len(bst), 9) 109 | self.assertFalse(bst.contains(1)) 110 | self.assertTrue(bst.contains(2)) 111 | self.assertTrue(bst.contains(3)) 112 | self.assertTrue(bst.contains(4)) 113 | self.assertTrue(bst.contains(5)) 114 | self.assertTrue(bst.contains(6)) 115 | self.assertTrue(bst.contains(7)) 116 | self.assertFalse(bst.contains(8)) 117 | self.assertTrue(bst.contains(9)) 118 | self.assertFalse(bst.contains(10)) 119 | self.assertTrue(bst.contains(11)) 120 | 121 | # Delete a node with only a left child 122 | bst.delete(9) 123 | self.assertEqual(len(bst), 8) 124 | self.assertFalse(bst.contains(1)) 125 | self.assertTrue(bst.contains(2)) 126 | self.assertTrue(bst.contains(3)) 127 | self.assertTrue(bst.contains(4)) 128 | self.assertTrue(bst.contains(5)) 129 | self.assertTrue(bst.contains(6)) 130 | self.assertTrue(bst.contains(7)) 131 | self.assertFalse(bst.contains(8)) 132 | self.assertFalse(bst.contains(9)) 133 | self.assertFalse(bst.contains(10)) 134 | self.assertTrue(bst.contains(11)) 135 | 136 | # Delete a node with only a right child 137 | bst.delete(2) 138 | self.assertEqual(len(bst), 7) 139 | self.assertFalse(bst.contains(1)) 140 | self.assertFalse(bst.contains(2)) 141 | self.assertTrue(bst.contains(3)) 142 | self.assertTrue(bst.contains(4)) 143 | self.assertTrue(bst.contains(5)) 144 | self.assertTrue(bst.contains(6)) 145 | self.assertTrue(bst.contains(7)) 146 | self.assertFalse(bst.contains(8)) 147 | self.assertFalse(bst.contains(9)) 148 | self.assertFalse(bst.contains(10)) 149 | self.assertTrue(bst.contains(11)) 150 | 151 | # Delete a node with both children 152 | bst.delete(4) 153 | self.assertEqual(len(bst), 6) 154 | self.assertFalse(bst.contains(1)) 155 | self.assertFalse(bst.contains(2)) 156 | self.assertTrue(bst.contains(3)) 157 | self.assertFalse(bst.contains(4)) 158 | self.assertTrue(bst.contains(5)) 159 | self.assertTrue(bst.contains(6)) 160 | self.assertTrue(bst.contains(7)) 161 | self.assertFalse(bst.contains(8)) 162 | self.assertFalse(bst.contains(9)) 163 | self.assertFalse(bst.contains(10)) 164 | self.assertTrue(bst.contains(11)) 165 | 166 | # Delete the root node 167 | bst.delete(6) 168 | self.assertEqual(len(bst), 5) 169 | self.assertFalse(bst.contains(1)) 170 | self.assertFalse(bst.contains(2)) 171 | self.assertTrue(bst.contains(3)) 172 | self.assertFalse(bst.contains(4)) 173 | self.assertTrue(bst.contains(5)) 174 | # There are two occurrences of the value 6! 175 | self.assertTrue(bst.contains(6)) 176 | self.assertTrue(bst.contains(7)) 177 | self.assertFalse(bst.contains(8)) 178 | self.assertFalse(bst.contains(9)) 179 | self.assertFalse(bst.contains(10)) 180 | self.assertTrue(bst.contains(11)) 181 | 182 | # It must have replaced the old root with 183 | # the other occurrence of the same value 184 | self.assertEqual(bst._root._value, 6) 185 | 186 | bst.delete(6) 187 | self.assertEqual(len(bst), 4) 188 | self.assertFalse(bst.contains(1)) 189 | self.assertFalse(bst.contains(2)) 190 | self.assertTrue(bst.contains(3)) 191 | self.assertFalse(bst.contains(4)) 192 | self.assertTrue(bst.contains(5)) 193 | self.assertFalse(bst.contains(6)) 194 | self.assertTrue(bst.contains(7)) 195 | self.assertFalse(bst.contains(8)) 196 | self.assertFalse(bst.contains(9)) 197 | self.assertFalse(bst.contains(10)) 198 | self.assertTrue(bst.contains(11)) 199 | 200 | self.assertEqual(bst._root._value, 5) 201 | bst.insert(1) 202 | bst.insert(2) 203 | bst.insert(6) 204 | self.assertEqual(len(bst), 7) 205 | self.assertTrue(bst.contains(1)) 206 | self.assertTrue(bst.contains(2)) 207 | self.assertTrue(bst.contains(3)) 208 | self.assertFalse(bst.contains(4)) 209 | self.assertTrue(bst.contains(5)) 210 | self.assertTrue(bst.contains(6)) 211 | self.assertTrue(bst.contains(7)) 212 | self.assertFalse(bst.contains(8)) 213 | self.assertFalse(bst.contains(9)) 214 | self.assertFalse(bst.contains(10)) 215 | self.assertTrue(bst.contains(11)) 216 | 217 | # Delete a node with both children, with the successor of the node having a left subtree 218 | bst = BinarySearchTree() 219 | 220 | bst.insert('H') 221 | bst.insert('F') 222 | bst.insert('I') 223 | bst.insert('G') 224 | bst.insert('B') 225 | bst.insert('E') 226 | bst.insert('K') 227 | bst.insert('A') 228 | bst.insert('J') 229 | bst.insert('L') 230 | bst.insert('C') 231 | bst.insert('D') 232 | self.assertEqual(len(bst), 12) 233 | 234 | bst.delete('F') 235 | self.assertEqual(len(bst), 11) 236 | self.assertTrue(bst.contains('A')) 237 | self.assertTrue(bst.contains('B')) 238 | self.assertTrue(bst.contains('C')) 239 | self.assertTrue(bst.contains('D')) 240 | self.assertTrue(bst.contains('E')) 241 | self.assertFalse(bst.contains('F')) 242 | self.assertTrue(bst.contains('G')) 243 | self.assertTrue(bst.contains('H')) 244 | self.assertTrue(bst.contains('I')) 245 | self.assertTrue(bst.contains('J')) 246 | self.assertTrue(bst.contains('K')) 247 | self.assertTrue(bst.contains('L')) 248 | 249 | 250 | 251 | def test_iterate(self): 252 | bst = BinarySearchTree() 253 | 254 | self.assertEqual([v for v in bst], []) 255 | 256 | bst.insert(6) 257 | 258 | self.assertEqual([v for v in bst], [6]) 259 | 260 | bst.insert(4) 261 | 262 | self.assertEqual([v for v in bst], [4, 6]) 263 | 264 | bst.insert(7) 265 | bst.insert(5) 266 | bst.insert(2) 267 | bst.insert(3) 268 | bst.insert(9) 269 | bst.insert(1) 270 | bst.insert(6) 271 | bst.insert(8) 272 | bst.insert(11) 273 | 274 | self.assertEqual([v for v in bst], [1, 2, 3, 4, 5, 6, 6, 7, 8, 9, 11]) 275 | -------------------------------------------------------------------------------- /python/tests/test_core.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from arrays.core import Array 3 | 4 | class TestArray(unittest.TestCase): 5 | # __init__ 6 | def test_init_valid(self): 7 | """Test initializing Array with valid arguments""" 8 | arr = Array(5) 9 | self.assertEqual(len(arr), 5) 10 | self.assertEqual(arr[0], 0) 11 | arr = Array(3, 'f') 12 | self.assertEqual(len(arr), 3) 13 | self.assertEqual(arr[0], 0.0) 14 | 15 | def test_init_invalid_size(self): 16 | """Test initializing Array with invalid size""" 17 | with self.assertRaises(ValueError): 18 | Array(-1) 19 | 20 | def test_init_invalid_typecode(self): 21 | """Test initializing Array with invalid typecode""" 22 | with self.assertRaises(ValueError): 23 | Array(5, 'x') 24 | 25 | # __get_item__ 26 | def test_get_item_valid(self): 27 | """Test getting an item with a valid index""" 28 | arr = Array(5) 29 | self.assertEqual(arr[0], 0) 30 | self.assertEqual(arr[1], 0) 31 | self.assertEqual(arr[2], 0) 32 | self.assertEqual(arr[3], 0) 33 | self.assertEqual(arr[4], 0) 34 | 35 | arr = Array(3, 'd') 36 | self.assertEqual(arr[0], 0.0) 37 | self.assertEqual(arr[1], 0.0) 38 | self.assertEqual(arr[2], 0.0) 39 | 40 | 41 | def test_get_item_invalid(self): 42 | """Test getting an item with an invalid index""" 43 | arr = Array(5) 44 | with self.assertRaises(IndexError): 45 | arr[-1] 46 | with self.assertRaises(IndexError): 47 | arr[5] 48 | 49 | # __set_item__ 50 | def test_set_item_valid(self): 51 | """Test setting an item with a valid index""" 52 | arr = Array(5) 53 | arr[0] = -1 54 | arr[1] = 3 55 | arr[2] = 33 56 | arr[3] = 42 57 | arr[4] = -1000 58 | self.assertEqual(arr[0], -1) 59 | self.assertEqual(arr[1], 3) 60 | self.assertEqual(arr[2], 33) 61 | self.assertEqual(arr[3], 42) 62 | self.assertEqual(arr[4], -1000) 63 | 64 | def test_set_item_invalid_value(self): 65 | """Test setting an element with an invalid value""" 66 | arr = Array(5) 67 | with self.assertRaises(Exception): 68 | arr[0] = 1.3 69 | with self.assertRaises(Exception): 70 | arr[1] = '1' 71 | 72 | def test_set_item_invalid_index(self): 73 | """Test setting an item with an invalid index""" 74 | arr = Array(5) 75 | with self.assertRaises(IndexError): 76 | arr[-1] = 1 77 | with self.assertRaises(IndexError): 78 | arr[5] = 1 79 | 80 | # __len__ 81 | def test_len(self): 82 | """Test getting the length of the array""" 83 | arr = Array(5) 84 | self.assertEqual(len(arr), 5) 85 | 86 | arr = Array(31, 'd') 87 | self.assertEqual(len(arr), 31) 88 | 89 | # __repr__ 90 | def test_repr(self): 91 | """Test getting the string representation of the array""" 92 | arr = Array(5) 93 | arr[0] = 1 94 | arr[1] = 2 95 | arr[3] = -2 96 | self.assertEqual(repr(arr), "array('l', [1, 2, 0, -2, 0])") 97 | -------------------------------------------------------------------------------- /python/tests/test_doubly_linked_list.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from linked_lists.doubly_linked_list import DoublyLinkedList 3 | 4 | class TestDoublyLinkedList(unittest.TestCase): 5 | 6 | def test_init(self): 7 | linked_list = DoublyLinkedList() 8 | self.assertIsNone(linked_list._head) 9 | 10 | 11 | def test_len(self): 12 | linked_list = DoublyLinkedList() 13 | self.assertEqual(len(linked_list), 0) 14 | 15 | linked_list.insert_in_front(1) 16 | self.assertEqual(len(linked_list), 1) 17 | 18 | linked_list.insert_in_front(2) 19 | self.assertEqual(len(linked_list), 2) 20 | 21 | linked_list.insert_to_back(2) 22 | self.assertEqual(len(linked_list), 3) 23 | 24 | 25 | def test_repr(self): 26 | linked_list = DoublyLinkedList() 27 | self.assertEqual(repr(linked_list), 'DoublyLinkedList()') 28 | 29 | linked_list.insert_in_front(1) 30 | self.assertEqual(repr(linked_list), 'DoublyLinkedList(1)') 31 | 32 | linked_list.insert_in_front(2) 33 | self.assertEqual(repr(linked_list), 'DoublyLinkedList(2<->1)') 34 | 35 | linked_list.insert_to_back(3.14) 36 | self.assertEqual(repr(linked_list), 'DoublyLinkedList(2<->1<->3.14)') 37 | 38 | 39 | def test_str(self): 40 | linked_list = DoublyLinkedList() 41 | self.assertEqual(str(linked_list), '') 42 | 43 | linked_list.insert_in_front('a') 44 | self.assertEqual(str(linked_list), 'a') 45 | 46 | linked_list.insert_in_front('b') 47 | self.assertEqual(str(linked_list), 'b<->a') 48 | 49 | linked_list.insert_to_back('c') 50 | self.assertEqual(str(linked_list), 'b<->a<->c') 51 | 52 | 53 | def test_iter(self): 54 | linked_list = DoublyLinkedList() 55 | 56 | # Iterate over empty list 57 | self.assertEqual(list(linked_list), []) 58 | 59 | # Iterate over non-empty list 60 | linked_list.insert_in_front(1) 61 | linked_list.insert_in_front(2) 62 | linked_list.insert_to_back(3.14) 63 | linked_list.insert_to_back("AC") 64 | 65 | expected = [2, 1, 3.14, "AC"] 66 | self.assertEqual(list(linked_list), expected) 67 | 68 | 69 | def test_size(self): 70 | linked_list = DoublyLinkedList() 71 | self.assertEqual(linked_list.size(), 0) 72 | 73 | linked_list.insert_in_front(1) 74 | self.assertEqual(linked_list.size(), 1) 75 | 76 | linked_list.insert_in_front(2) 77 | self.assertEqual(linked_list.size(), 2) 78 | 79 | 80 | def test_is_empty(self): 81 | linked_list = DoublyLinkedList() 82 | self.assertTrue(linked_list.is_empty()) 83 | linked_list.insert_in_front(1) 84 | self.assertFalse(linked_list.is_empty()) 85 | linked_list.insert_in_front(2) 86 | linked_list.insert_in_front(3) 87 | self.assertFalse(linked_list.is_empty()) 88 | 89 | linked_list.delete(3) 90 | self.assertFalse(linked_list.is_empty()) 91 | 92 | linked_list.delete(2) 93 | self.assertFalse(linked_list.is_empty()) 94 | 95 | linked_list.delete(1) 96 | self.assertTrue(linked_list.is_empty()) 97 | 98 | 99 | def test_add_in_front(self): 100 | linked_list = DoublyLinkedList() 101 | 102 | # Add to empty list 103 | linked_list.insert_in_front(1) 104 | self.assertEqual(linked_list._head.data(), 1) 105 | 106 | # Add to non-empty list 107 | linked_list.insert_in_front(2) 108 | self.assertEqual(linked_list._head.data(), 2) 109 | self.assertEqual(linked_list._head.next().data(), 1) 110 | 111 | 112 | def test_add_to_back(self): 113 | linked_list = DoublyLinkedList() 114 | 115 | # Add to empty list 116 | linked_list.insert_to_back(1) 117 | self.assertEqual(linked_list._head.data(), 1) 118 | 119 | # Add to non-empty list 120 | linked_list.insert_in_front(2) 121 | linked_list.insert_to_back(3) 122 | self.assertEqual(linked_list._head.data(), 2) 123 | self.assertEqual(linked_list._head.next().data(), 1) 124 | self.assertEqual(linked_list._head.next().next().data(), 3) 125 | self.assertIsNone(linked_list._head.next().next().next()) 126 | 127 | 128 | def test_get_valid_index(self): 129 | linked_list = DoublyLinkedList() 130 | linked_list.insert_in_front(1) 131 | linked_list.insert_in_front(2) 132 | linked_list.insert_in_front(3) 133 | 134 | self.assertEqual(linked_list.get(0), 3) 135 | self.assertEqual(linked_list.get(1), 2) 136 | self.assertEqual(linked_list.get(2), 1) 137 | 138 | def test_get_invalid_index(self): 139 | linked_list = DoublyLinkedList() 140 | 141 | with self.assertRaises(IndexError): 142 | linked_list.get(-1) 143 | 144 | with self.assertRaises(IndexError): 145 | linked_list.get(0) # index out of bounds 146 | 147 | def test_get_returns_deep_copy(self): 148 | linked_list = DoublyLinkedList() 149 | linked_list.insert_in_front([['a', 'b'], 1, 2]) 150 | 151 | retrieved = linked_list.get(0) 152 | retrieved.append(3) 153 | retrieved[0].append('c') 154 | self.assertEqual(linked_list.get(0), [['a', 'b'], 1, 2]) 155 | 156 | 157 | def test_internal_search(self): 158 | linked_list = DoublyLinkedList() 159 | 160 | # Search empty list 161 | self.assertIsNone(linked_list._search(1)) 162 | 163 | # Search when not found 164 | linked_list.insert_in_front(2) 165 | linked_list.insert_in_front(1) 166 | self.assertIsNone(linked_list._search(3)) 167 | 168 | # Search when found 169 | linked_list.insert_in_front(3) 170 | found = linked_list._search(3) 171 | self.assertEqual(found.data(), 3) 172 | 173 | 174 | found = linked_list._search(2) 175 | self.assertEqual(found.data(), 2) 176 | 177 | 178 | def test_search_empty_list(self): 179 | linked_list = DoublyLinkedList() 180 | result = linked_list.search(lambda x: x == 1) 181 | self.assertIsNone(result) 182 | 183 | 184 | def test_search_not_found(self): 185 | linked_list = DoublyLinkedList() 186 | linked_list.insert_in_front(2) 187 | linked_list.insert_in_front(3) 188 | result = linked_list.search(lambda x: x == 1) 189 | self.assertIsNone(result) 190 | 191 | 192 | def test_search_found(self): 193 | linked_list = DoublyLinkedList() 194 | linked_list.insert_in_front(3) 195 | linked_list.insert_in_front(2) 196 | linked_list.insert_in_front(1) 197 | result = linked_list.search(lambda x: x == 1) 198 | self.assertEqual(result, 1) 199 | result = linked_list.search(lambda x: x == 2) 200 | self.assertEqual(result, 2) 201 | result = linked_list.search(lambda x: x == 3) 202 | self.assertEqual(result, 3) 203 | 204 | 205 | def test_search_multiple_matches(self): 206 | linked_list = DoublyLinkedList() 207 | linked_list.insert_in_front('AB') 208 | linked_list.insert_in_front('C') 209 | linked_list.insert_in_front('ABC') 210 | linked_list.insert_in_front('B') 211 | result = linked_list.search(lambda x: x[0] == 'A') 212 | self.assertEqual(result, 'ABC') 213 | 214 | 215 | def test_delete(self): 216 | linked_list = DoublyLinkedList() 217 | linked_list.insert_in_front(1) 218 | linked_list.insert_in_front(2) 219 | linked_list.insert_in_front(3) 220 | 221 | linked_list.delete(2) 222 | 223 | self.assertEqual(len(linked_list), 2) 224 | self.assertEqual(linked_list._head.data(), 3) 225 | self.assertEqual(linked_list._head.next().data(), 1) 226 | 227 | 228 | def test_delete_head(self): 229 | linked_list = DoublyLinkedList() 230 | linked_list.insert_in_front(1) 231 | linked_list.insert_in_front(2) 232 | linked_list.insert_in_front(3) 233 | 234 | linked_list.delete(3) 235 | self.assertEqual(len(linked_list), 2) 236 | self.assertEqual(linked_list._head.data(), 2) 237 | self.assertEqual(linked_list._head.next().data(), 1) 238 | 239 | linked_list.delete(2) 240 | self.assertEqual(len(linked_list), 1) 241 | self.assertEqual(linked_list._head.data(), 1) 242 | self.assertIsNone(linked_list._head.next()) 243 | 244 | linked_list.delete(1) 245 | self.assertEqual(len(linked_list), 0) 246 | 247 | def test_delete_tail(self): 248 | linked_list = DoublyLinkedList() 249 | linked_list.insert_in_front(1) 250 | linked_list.insert_in_front(2) 251 | linked_list.insert_in_front(3) 252 | 253 | linked_list.delete(1) 254 | self.assertEqual(len(linked_list), 2) 255 | self.assertEqual(linked_list._head.data(), 3) 256 | self.assertEqual(linked_list._head.next().data(), 2) 257 | 258 | linked_list.delete(2) 259 | self.assertEqual(len(linked_list), 1) 260 | self.assertEqual(linked_list._head.data(), 3) 261 | self.assertIsNone(linked_list._head.next()) 262 | 263 | linked_list.delete(3) 264 | self.assertEqual(len(linked_list), 0) 265 | self.assertIsNone(linked_list._head) 266 | self.assertIsNone(linked_list._tail) 267 | 268 | def test_delete_invalid(self): 269 | linked_list = DoublyLinkedList() 270 | linked_list.insert_in_front(3) 271 | linked_list.insert_in_front(2) 272 | linked_list.insert_in_front(1) 273 | 274 | with self.assertRaises(ValueError): 275 | linked_list.delete(4) 276 | 277 | def test_delete_and_insert(self): 278 | linked_list = DoublyLinkedList() 279 | linked_list.insert_in_front(2) 280 | linked_list.insert_in_front(3) 281 | linked_list.insert_to_back(1) 282 | 283 | linked_list.delete(2) 284 | self.assertEqual(len(linked_list), 2) 285 | self.assertEqual(linked_list._head.data(), 3) 286 | self.assertEqual(linked_list._head.next().data(), 1) 287 | 288 | linked_list.insert_to_back(5) 289 | self.assertEqual(len(linked_list), 3) 290 | self.assertEqual(linked_list._head.next().next().data(), 5) 291 | self.assertIsNone(linked_list._head.next().next().next()) 292 | 293 | linked_list.delete(3) 294 | linked_list.delete(5) 295 | self.assertEqual(len(linked_list), 1) 296 | self.assertEqual(linked_list._head.data(), 1) 297 | self.assertIsNone(linked_list._head.next()) 298 | 299 | linked_list.insert_in_front(6) 300 | linked_list.insert_to_back(7) 301 | linked_list.insert_to_back(8) 302 | 303 | self.assertEqual(len(linked_list), 4) 304 | self.assertIsNone(linked_list._head.prev()) 305 | self.assertIsNone(linked_list._head.next().next().next().next()) 306 | self.assertEqual(str(linked_list), "6<->1<->7<->8") 307 | 308 | 309 | def test_delete_from_front(self): 310 | linked_list = DoublyLinkedList() 311 | 312 | # Delete from empty list 313 | with self.assertRaises(ValueError): 314 | linked_list.delete_from_front() 315 | 316 | # Delete from list with one element 317 | linked_list.insert_in_front(1) 318 | self.assertEqual(linked_list.delete_from_front(), 1) 319 | self.assertTrue(linked_list.is_empty()) 320 | 321 | # Delete from list with multiple elements 322 | linked_list.insert_in_front(2) 323 | linked_list.insert_in_front(1) 324 | linked_list.insert_in_front(4) 325 | self.assertEqual(linked_list.delete_from_front(), 4) 326 | self.assertEqual(len(linked_list), 2) 327 | self.assertEqual(linked_list._head.data(), 1) 328 | self.assertIsNone(linked_list._head.prev()) 329 | 330 | self.assertEqual(linked_list.delete_from_front(), 1) 331 | self.assertEqual(len(linked_list), 1) 332 | self.assertEqual(linked_list._head.data(), 2) 333 | self.assertIsNone(linked_list._head.prev()) 334 | 335 | 336 | def test_delete_from_back(self): 337 | linked_list = DoublyLinkedList() 338 | 339 | # Delete from empty list 340 | with self.assertRaises(ValueError): 341 | linked_list.delete_from_back() 342 | 343 | # Delete from list with one element 344 | linked_list.insert_in_front(1) 345 | self.assertEqual(linked_list.delete_from_back(), 1) 346 | self.assertTrue(linked_list.is_empty()) 347 | 348 | # Delete from list with multiple elements 349 | linked_list.insert_in_front(2) 350 | linked_list.insert_in_front(1) 351 | linked_list.insert_in_front(3) 352 | self.assertEqual(linked_list.delete_from_back(), 2) 353 | self.assertEqual(len(linked_list), 2) 354 | self.assertEqual(linked_list._tail.data(), 1) 355 | self.assertIsNone(linked_list._tail.next()) 356 | self.assertEqual(linked_list.delete_from_back(), 1) 357 | self.assertEqual(len(linked_list), 1) 358 | self.assertEqual(linked_list._tail.data(), 3) 359 | self.assertIsNone(linked_list._tail.next()) 360 | 361 | 362 | class TestNode(unittest.TestCase): 363 | 364 | def test_init(self): 365 | data = 'test' 366 | node = DoublyLinkedList.Node(data) 367 | self.assertEqual(node.data(), data) 368 | self.assertIsNone(node.next()) 369 | 370 | def test_str(self): 371 | data = 'test' 372 | node = DoublyLinkedList.Node(data) 373 | self.assertEqual(str(node), str(data)) 374 | 375 | def test_repr(self): 376 | data = 'test' 377 | node = DoublyLinkedList.Node(data) 378 | self.assertEqual(repr(node), repr(data)) 379 | 380 | def test_append(self): 381 | node1 = DoublyLinkedList.Node(1) 382 | node2 = DoublyLinkedList.Node(2) 383 | node1.append(node2) 384 | self.assertEqual(node1.next(), node2) 385 | self.assertEqual(node2.prev(), node1) 386 | 387 | node1.append(None) 388 | self.assertIsNone(node1.next()) 389 | 390 | def test_has_next(self): 391 | node = DoublyLinkedList.Node(1) 392 | self.assertFalse(node.has_next()) 393 | 394 | new_node = DoublyLinkedList.Node(2) 395 | new_node.append(node) 396 | self.assertTrue(new_node.has_next()) 397 | 398 | def test_prepend(self): 399 | node1 = DoublyLinkedList.Node(1) 400 | node2 = DoublyLinkedList.Node(2) 401 | node1.prepend(node2) 402 | self.assertEqual(node1.prev(), node2) 403 | self.assertEqual(node2.next(), node1) 404 | 405 | node1.prepend(None) 406 | self.assertIsNone(node1.prev()) 407 | 408 | def test_has_prev(self): 409 | node = DoublyLinkedList.Node(1) 410 | self.assertFalse(node.has_prev()) 411 | 412 | new_node = DoublyLinkedList.Node(2) 413 | new_node.prepend(node) 414 | self.assertTrue(new_node.has_prev()) 415 | -------------------------------------------------------------------------------- /python/tests/test_dynamic_array.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from arrays.dynamic_array import DynamicArray 3 | 4 | class TestDynamicArray(unittest.TestCase): 5 | # __init__ 6 | 7 | def test_init(self): 8 | """Test initialization of DynamicArray.""" 9 | array = DynamicArray(5) 10 | self.assertEqual(len(array), 0) 11 | 12 | array = DynamicArray(3, 'f') 13 | self.assertEqual(len(array), 0) 14 | 15 | def test_init_capacity(self): 16 | """Test that the right capacity is set on initialization of DynamicArray.""" 17 | array = DynamicArray() 18 | self.assertEqual(len(array), 0) 19 | self.assertEqual(array._capacity, 1) 20 | 21 | array = DynamicArray(3, 'f') 22 | self.assertEqual(len(array), 0) 23 | self.assertEqual(array._capacity, 3) 24 | 25 | 26 | def test_init_invalid_size(self): 27 | """Test initializing Array with invalid size""" 28 | with self.assertRaises(ValueError): 29 | DynamicArray(-1) 30 | 31 | def test_init_invalid_typecode(self): 32 | """Test initializing Array with invalid typecode""" 33 | with self.assertRaises(ValueError): 34 | DynamicArray(5, 'x') 35 | 36 | 37 | # __len__ 38 | 39 | def test_len(self): 40 | """Test length property.""" 41 | array = DynamicArray(5, 'i') 42 | self.assertEqual(len(array), 0) 43 | array.insert(1) 44 | self.assertEqual(len(array), 1) 45 | 46 | 47 | # __get_item__ 48 | 49 | def test_getitem(self): 50 | """Test indexing into array.""" 51 | array = DynamicArray(5, 'i') 52 | array.insert(1) 53 | array.insert(2) 54 | self.assertEqual(array[0], 1) 55 | self.assertEqual(array[1], 2) 56 | 57 | def test_index_out_of_bounds(self): 58 | """Test that indexing past the end of the array raises an error.""" 59 | array = DynamicArray(5, 'i') 60 | array.insert(1) 61 | with self.assertRaises(IndexError): 62 | array[2] 63 | 64 | def test_negative_index(self): 65 | """Test that indexing with a negative index raises an error.""" 66 | array = DynamicArray(3, 'i') 67 | array.insert(431) 68 | with self.assertRaises(IndexError): 69 | array[-1] 70 | 71 | # __repr__ 72 | 73 | def test_repr(self): 74 | """Test string representation.""" 75 | array = DynamicArray(5, 'i') 76 | array.insert(1) 77 | array.insert(2) 78 | self.assertEqual(repr(array), "array('i', [1, 2])") 79 | 80 | 81 | # __iter__ 82 | 83 | def test_iter(self): 84 | """Test iteration over values in the array.""" 85 | array = DynamicArray(8, 'i') 86 | array.insert(1) 87 | array.insert(2) 88 | array.insert(3) 89 | iterated_values = [] 90 | for value in array: 91 | iterated_values.append(value) 92 | expected_values = [1, 2, 3] 93 | self.assertEqual(iterated_values, expected_values) 94 | 95 | def test_iter_empty(self): 96 | """Test that iteration over an empty array raises StopIteration.""" 97 | array = DynamicArray(3, 'i') 98 | with self.assertRaises(StopIteration): 99 | next(iter(array)) 100 | 101 | 102 | # insert 103 | 104 | def test_insert_full(self): 105 | """Test that insert resizes the array correctly""" 106 | array = DynamicArray(typecode = 'i') 107 | array.insert(1) 108 | self.assertEqual(array._capacity, 1) 109 | array.insert(2) 110 | self.assertEqual(len(array), 2) 111 | self.assertEqual(array._capacity, 2) 112 | array.insert(3) 113 | self.assertEqual(3, len(array)) 114 | self.assertEqual(array._capacity, 4) 115 | self.assertEqual([v for v in array], [1,2,3]) 116 | 117 | array = DynamicArray(3, 'i') 118 | array.insert(1) 119 | self.assertEqual(array._capacity, 3) 120 | array.insert(2) 121 | self.assertEqual(len(array), 2) 122 | self.assertEqual(array._capacity, 3) 123 | array.insert(3) 124 | self.assertEqual(3, len(array)) 125 | self.assertEqual(array._capacity, 3) 126 | array.insert(4) 127 | self.assertEqual(4, len(array)) 128 | self.assertEqual(array._capacity, 6) 129 | self.assertEqual([v for v in array], [1,2,3,4]) 130 | 131 | # search 132 | 133 | def test_find_found(self): 134 | """Test searching for a value that is in the array.""" 135 | array = DynamicArray(4, 'i') 136 | array.insert(2) 137 | array.insert(1) 138 | array.insert(3) 139 | self.assertEqual(array.find(2), 0) 140 | array.insert(-43) 141 | self.assertEqual(array.find(-43), 3) 142 | array = DynamicArray(3, 'd') 143 | array.insert(21.3) 144 | array.insert(3.1415) 145 | self.assertEqual(array.find(3.1415), 1) 146 | 147 | def test_find_not_found(self): 148 | """Test searching for a value that is not in the array.""" 149 | array = DynamicArray(5, 'i') 150 | array.insert(3) 151 | array.insert(1) 152 | array.insert(2) 153 | self.assertEqual(array.find(4), None) 154 | 155 | def test_find_empty(self): 156 | """Test searching an empty array.""" 157 | array = DynamicArray(2, 'i') 158 | self.assertEqual(array.find(1), None) 159 | 160 | def test_find_empty_chunk_not_included(self): 161 | """Test that only the filled portion of the array is searched.""" 162 | array = DynamicArray(5, 'i') 163 | array.insert(3) 164 | array.insert(1) 165 | array.insert(2) 166 | self.assertEqual(array.find(0), None) 167 | array.insert(0) 168 | self.assertEqual(array.find(0), 3) 169 | array = DynamicArray(5, 'd') 170 | array.insert(-1.0) 171 | array.insert(-3) 172 | array.insert(-2) 173 | self.assertEqual(array.find(0), None) 174 | self.assertEqual(array.find(0.0), None) 175 | array.insert(0) 176 | self.assertEqual(array.find(0), 3) 177 | self.assertEqual(array.find(-2), 2) 178 | self.assertEqual(array.find(-1), 0) 179 | array.delete(-2) 180 | self.assertEqual(array.find(-2), None) 181 | array.delete(-1) 182 | self.assertEqual(array.find(-1), None) 183 | 184 | # is_empty 185 | 186 | def test_is_empty(self): 187 | """Test the is_empty method.""" 188 | array = DynamicArray(2, 'i') 189 | self.assertTrue(array.is_empty()) 190 | array.insert(1) 191 | self.assertFalse(array.is_empty()) 192 | array.insert(3) 193 | array.insert(2) 194 | self.assertFalse(array.is_empty()) 195 | 196 | # delete 197 | 198 | def test_delete_found(self): 199 | """Test deleting a value that is in the array.""" 200 | array = DynamicArray(5, 'i') 201 | array.insert(1) 202 | array.insert(2) 203 | array.insert(3) 204 | self.assertEqual(len(array), 3) 205 | 206 | array.delete(2) 207 | self.assertEqual(len(array), 2) 208 | self.assertEqual(array[0], 1) 209 | self.assertEqual(array[1], 3) 210 | 211 | array = DynamicArray(7, 'i') 212 | array.insert(3) 213 | array.insert(1) 214 | array.insert(3) 215 | array.insert(2) 216 | array.insert(2) 217 | self.assertEqual(len(array), 5) 218 | 219 | array.delete(2) 220 | self.assertEqual(len(array), 4) 221 | self.assertEqual(array[0], 3) 222 | self.assertEqual(array[1], 1) 223 | self.assertEqual(array[2], 3) 224 | self.assertEqual(array[3], 2) 225 | 226 | array.delete(3) 227 | self.assertEqual(len(array), 3) 228 | array.delete(1) 229 | self.assertEqual(len(array), 2) 230 | self.assertEqual(array[0], 3) 231 | self.assertEqual(array[1], 2) 232 | 233 | array.insert(0) 234 | array.insert(-3) 235 | array.insert(2) 236 | self.assertEqual(len(array), 5) 237 | 238 | array.delete(-3) 239 | self.assertEqual(len(array), 4) 240 | self.assertEqual(array[0], 3) 241 | self.assertEqual(array[1], 2) 242 | self.assertEqual(array[2], 0) 243 | self.assertEqual(array[3], 2) 244 | 245 | array.delete(2) 246 | self.assertEqual(len(array), 3) 247 | 248 | array.delete(2) 249 | array.delete(0) 250 | self.assertEqual(len(array), 1) 251 | self.assertEqual(array[0], 3) 252 | 253 | array.delete(3) 254 | self.assertEqual(len(array), 0) 255 | 256 | def test_delete_not_found(self): 257 | """Test deleting a value that is not in the array.""" 258 | array = DynamicArray(15, 'i') 259 | array.insert(1) 260 | array.insert(3) 261 | array.insert(2) 262 | with self.assertRaises(ValueError): 263 | array.delete(4) 264 | 265 | def test_delete_empty(self): 266 | """Test deleting from an empty array.""" 267 | array = DynamicArray(5, 'i') 268 | with self.assertRaises(ValueError): 269 | array.delete(1) 270 | 271 | # resize (internal) 272 | 273 | def test_resizing(self): 274 | array = DynamicArray() 275 | self.assertEqual(len(array), 0) 276 | self.assertEqual(array._capacity, 1) 277 | array.insert(1) 278 | self.assertEqual(len(array), 1) 279 | self.assertEqual(array._capacity, 1) 280 | array.insert(2) 281 | self.assertEqual(len(array), 2) 282 | self.assertEqual(array._capacity, 2) 283 | array.insert(3) 284 | self.assertEqual(len(array), 3) 285 | self.assertEqual(array._capacity, 4) 286 | 287 | array.delete(2) 288 | self.assertEqual(len(array), 2) 289 | self.assertEqual(array._capacity, 4) 290 | array.delete(1) 291 | self.assertEqual(len(array), 1) 292 | self.assertEqual(array._capacity, 2) 293 | 294 | # Initial capacity > 1 295 | array = DynamicArray(3, 'i') 296 | self.assertEqual(len(array), 0) 297 | self.assertEqual(array._capacity, 3) 298 | array.insert(3) 299 | self.assertEqual(len(array), 1) 300 | self.assertEqual(array._capacity, 3) 301 | array.insert(1) 302 | self.assertEqual(len(array), 2) 303 | self.assertEqual(array._capacity, 3) 304 | array.insert(3) 305 | self.assertEqual(len(array), 3) 306 | self.assertEqual(array._capacity, 3) 307 | array.insert(2) 308 | self.assertEqual(len(array), 4) 309 | self.assertEqual(array._capacity, 6) 310 | array.insert(2) 311 | self.assertEqual(len(array), 5) 312 | self.assertEqual(array._capacity, 6) 313 | array.insert(21) 314 | self.assertEqual(len(array), 6) 315 | self.assertEqual(array._capacity, 6) 316 | array.insert(-31) 317 | self.assertEqual(len(array), 7) 318 | self.assertEqual(array._capacity, 12) 319 | 320 | array.delete(2) 321 | self.assertEqual(len(array), 6) 322 | self.assertEqual(array._capacity, 12) 323 | array.delete(21) 324 | self.assertEqual(len(array), 5) 325 | self.assertEqual(array._capacity, 12) 326 | array.delete(2) 327 | self.assertEqual(len(array), 4) 328 | self.assertEqual(array._capacity, 12) 329 | array.delete(3) 330 | self.assertEqual(len(array), 3) 331 | self.assertEqual(array._capacity, 6) 332 | array.delete(1) 333 | self.assertEqual(len(array), 2) 334 | self.assertEqual(array._capacity, 6) 335 | array.delete(-31) 336 | self.assertEqual(len(array), 1) 337 | self.assertEqual(array._capacity, 3) 338 | array.delete(3) 339 | self.assertEqual(len(array), 0) 340 | self.assertEqual(array._capacity, 1) 341 | -------------------------------------------------------------------------------- /python/tests/test_hash_table.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from dictionaries.hash_table import HashTable 3 | 4 | class TestHashTable(unittest.TestCase): 5 | 6 | def test_init_invalid_size(self): 7 | """Test initializing a HashTable with an invalid size""" 8 | with self.assertRaises(ValueError): 9 | HashTable(-1) 10 | 11 | def test_len(self): 12 | hash_table = HashTable(2) 13 | self.assertEqual(len(hash_table), 0) 14 | 15 | hash_table.insert('a') 16 | self.assertEqual(len(hash_table), 1) 17 | 18 | hash_table.insert('beta') 19 | hash_table.insert('delta') 20 | self.assertEqual(len(hash_table), 3) 21 | hash_table.insert('c') 22 | self.assertEqual(len(hash_table), 4) 23 | hash_table.insert('Jsut some random string') 24 | self.assertEqual(len(hash_table), 5) 25 | 26 | def test_is_empty(self): 27 | hash_table = HashTable(22) 28 | self.assertTrue(hash_table.is_empty()) 29 | 30 | hash_table.insert('a') 31 | self.assertFalse(hash_table.is_empty()) 32 | 33 | hash_table.insert('bba') 34 | self.assertFalse(hash_table.is_empty()) 35 | 36 | def test_contains(self): 37 | hash_table = HashTable(3) 38 | self.assertFalse(hash_table.contains('a')) 39 | self.assertFalse(hash_table.contains('b')) 40 | self.assertFalse(hash_table.contains('c')) 41 | self.assertFalse(hash_table.contains('d')) 42 | self.assertFalse(hash_table.contains('Z')) 43 | 44 | hash_table.insert('a') 45 | self.assertTrue(hash_table.contains('a')) 46 | self.assertFalse(hash_table.contains('b')) 47 | self.assertFalse(hash_table.contains('c')) 48 | self.assertFalse(hash_table.contains('d')) 49 | self.assertFalse(hash_table.contains('Z')) 50 | 51 | hash_table.insert('b') 52 | hash_table.insert('d') 53 | self.assertTrue(hash_table.contains('a')) 54 | self.assertTrue(hash_table.contains('b')) 55 | self.assertFalse(hash_table.contains('c')) 56 | self.assertTrue(hash_table.contains('d')) 57 | self.assertFalse(hash_table.contains('Z')) 58 | 59 | hash_table.insert('c') 60 | self.assertTrue(hash_table.contains('c')) 61 | hash_table.insert('Z') 62 | self.assertTrue(hash_table.contains('Z')) 63 | hash_table.insert('A') 64 | self.assertTrue(hash_table.contains('A')) 65 | 66 | def test_search(self): 67 | hash_table = HashTable(3) 68 | self.assertIsNone(hash_table.search(hash('a'))) 69 | self.assertIsNone(hash_table.search(hash('b'))) 70 | self.assertIsNone(hash_table.search(hash('c'))) 71 | self.assertIsNone(hash_table.search(hash('d'))) 72 | self.assertIsNone(hash_table.search(hash('Z'))) 73 | 74 | hash_table.insert('a') 75 | self.assertEqual(hash_table.search(hash('a')), 'a') 76 | self.assertIsNone(hash_table.search(hash('b'))) 77 | self.assertIsNone(hash_table.search(hash('c'))) 78 | self.assertIsNone(hash_table.search(hash('d'))) 79 | self.assertIsNone(hash_table.search(hash('Z'))) 80 | 81 | hash_table.insert('b') 82 | hash_table.insert('d') 83 | self.assertEqual(hash_table.search(hash('a')), 'a') 84 | self.assertEqual(hash_table.search(hash('b')), 'b') 85 | self.assertIsNone(hash_table.search(hash('c'))) 86 | self.assertEqual(hash_table.search(hash('d')), 'd') 87 | self.assertIsNone(hash_table.search(hash('Z'))) 88 | 89 | hash_table.insert('c') 90 | self.assertEqual(hash_table.search(hash('c')), 'c') 91 | hash_table.insert('XYZ') 92 | self.assertEqual(hash_table.search(hash('XYZ')), 'XYZ') 93 | hash_table.insert(1) 94 | self.assertEqual(hash_table.search(hash(1)), 1) 95 | 96 | # Custom hash function 97 | key = lambda x: x * x * x 98 | hash_table = HashTable(5, extract_key=key) 99 | 100 | hash_table.insert(-1) 101 | hash_table.insert(0) 102 | hash_table.insert(1) 103 | hash_table.insert(2) 104 | self.assertEqual(hash_table.search(key(-1)), -1) 105 | self.assertEqual(hash_table.search(key(1)), 1) 106 | self.assertEqual(hash_table.search(key(0)), 0) 107 | self.assertEqual(hash_table.search(key(2)), 2) 108 | self.assertIsNone(hash_table.search(key(-2))) 109 | self.assertIsNone(hash_table.search(key(-3))) 110 | self.assertIsNone(hash_table.search(key(3))) 111 | self.assertIsNone(hash_table.search(key(0.4))) 112 | 113 | 114 | def test_insert_delete(self): 115 | hash_table = HashTable(5) 116 | 117 | # Delete from an empty hash_table 118 | with self.assertRaises(ValueError): 119 | hash_table.delete(6) 120 | 121 | hash_table.insert(6) 122 | hash_table.insert(4) 123 | hash_table.insert(7) 124 | hash_table.insert(5) 125 | hash_table.insert(2) 126 | hash_table.insert(3) 127 | hash_table.insert(9) 128 | hash_table.insert(1) 129 | hash_table.insert(6) 130 | hash_table.insert(8) 131 | hash_table.insert(11) 132 | 133 | self.assertEqual(len(hash_table), 11) 134 | 135 | # Value to be deleted not found 136 | with self.assertRaises(ValueError): 137 | hash_table.delete(0) 138 | 139 | self.assertTrue(hash_table.contains(1)) 140 | self.assertTrue(hash_table.contains(8)) 141 | hash_table.delete(1) 142 | hash_table.delete(8) 143 | self.assertEqual(len(hash_table), 9) 144 | self.assertFalse(hash_table.contains(1)) 145 | self.assertTrue(hash_table.contains(2)) 146 | self.assertTrue(hash_table.contains(3)) 147 | self.assertTrue(hash_table.contains(4)) 148 | self.assertTrue(hash_table.contains(5)) 149 | self.assertTrue(hash_table.contains(6)) 150 | self.assertTrue(hash_table.contains(7)) 151 | self.assertFalse(hash_table.contains(8)) 152 | self.assertTrue(hash_table.contains(9)) 153 | self.assertFalse(hash_table.contains(10)) 154 | self.assertTrue(hash_table.contains(11)) 155 | 156 | hash_table.delete(9) 157 | self.assertEqual(len(hash_table), 8) 158 | self.assertFalse(hash_table.contains(1)) 159 | self.assertTrue(hash_table.contains(2)) 160 | self.assertTrue(hash_table.contains(3)) 161 | self.assertTrue(hash_table.contains(4)) 162 | self.assertTrue(hash_table.contains(5)) 163 | self.assertTrue(hash_table.contains(6)) 164 | self.assertTrue(hash_table.contains(7)) 165 | self.assertFalse(hash_table.contains(8)) 166 | self.assertFalse(hash_table.contains(9)) 167 | self.assertFalse(hash_table.contains(10)) 168 | self.assertTrue(hash_table.contains(11)) 169 | 170 | hash_table.delete(2) 171 | self.assertEqual(len(hash_table), 7) 172 | self.assertFalse(hash_table.contains(1)) 173 | self.assertFalse(hash_table.contains(2)) 174 | self.assertTrue(hash_table.contains(3)) 175 | self.assertTrue(hash_table.contains(4)) 176 | self.assertTrue(hash_table.contains(5)) 177 | self.assertTrue(hash_table.contains(6)) 178 | self.assertTrue(hash_table.contains(7)) 179 | self.assertFalse(hash_table.contains(8)) 180 | self.assertFalse(hash_table.contains(9)) 181 | self.assertFalse(hash_table.contains(10)) 182 | self.assertTrue(hash_table.contains(11)) 183 | 184 | hash_table.delete(4) 185 | self.assertEqual(len(hash_table), 6) 186 | self.assertFalse(hash_table.contains(1)) 187 | self.assertFalse(hash_table.contains(2)) 188 | self.assertTrue(hash_table.contains(3)) 189 | self.assertFalse(hash_table.contains(4)) 190 | self.assertTrue(hash_table.contains(5)) 191 | self.assertTrue(hash_table.contains(6)) 192 | self.assertTrue(hash_table.contains(7)) 193 | self.assertFalse(hash_table.contains(8)) 194 | self.assertFalse(hash_table.contains(9)) 195 | self.assertFalse(hash_table.contains(10)) 196 | self.assertTrue(hash_table.contains(11)) 197 | 198 | hash_table.delete(6) 199 | self.assertEqual(len(hash_table), 5) 200 | self.assertFalse(hash_table.contains(1)) 201 | self.assertFalse(hash_table.contains(2)) 202 | self.assertTrue(hash_table.contains(3)) 203 | self.assertFalse(hash_table.contains(4)) 204 | self.assertTrue(hash_table.contains(5)) 205 | 206 | # There are two occurrences of the value 6! 207 | self.assertTrue(hash_table.contains(6)) 208 | self.assertTrue(hash_table.contains(7)) 209 | self.assertFalse(hash_table.contains(8)) 210 | self.assertFalse(hash_table.contains(9)) 211 | self.assertFalse(hash_table.contains(10)) 212 | self.assertTrue(hash_table.contains(11)) 213 | 214 | hash_table.delete(6) 215 | self.assertEqual(len(hash_table), 4) 216 | self.assertFalse(hash_table.contains(1)) 217 | self.assertFalse(hash_table.contains(2)) 218 | self.assertTrue(hash_table.contains(3)) 219 | self.assertFalse(hash_table.contains(4)) 220 | self.assertTrue(hash_table.contains(5)) 221 | self.assertFalse(hash_table.contains(6)) 222 | self.assertTrue(hash_table.contains(7)) 223 | self.assertFalse(hash_table.contains(8)) 224 | self.assertFalse(hash_table.contains(9)) 225 | self.assertFalse(hash_table.contains(10)) 226 | self.assertTrue(hash_table.contains(11)) 227 | 228 | hash_table.insert(1) 229 | hash_table.insert(2) 230 | hash_table.insert(6) 231 | self.assertEqual(len(hash_table), 7) 232 | self.assertTrue(hash_table.contains(1)) 233 | self.assertTrue(hash_table.contains(2)) 234 | self.assertTrue(hash_table.contains(3)) 235 | self.assertFalse(hash_table.contains(4)) 236 | self.assertTrue(hash_table.contains(5)) 237 | self.assertTrue(hash_table.contains(6)) 238 | self.assertTrue(hash_table.contains(7)) 239 | self.assertFalse(hash_table.contains(8)) 240 | self.assertFalse(hash_table.contains(9)) 241 | self.assertFalse(hash_table.contains(10)) 242 | self.assertTrue(hash_table.contains(11)) 243 | -------------------------------------------------------------------------------- /python/tests/test_heap.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import random 3 | 4 | from queues.heap import Heap 5 | 6 | class HeapTest(unittest.TestCase): 7 | def test_init(self): 8 | heap = Heap() 9 | self.assertEqual(0, len(heap)) 10 | 11 | heap = Heap(elements=['A', 'B', 'C', 'D']) 12 | 13 | self.assertEqual(4, len(heap)) 14 | self.assertTrue(heap._validate()) 15 | 16 | heap = Heap(elements=['A', 'B', 'C', 'D'], element_priority=lambda x: -ord(x)) 17 | 18 | self.assertEqual(4, len(heap)) 19 | self.assertTrue(heap._validate()) 20 | 21 | 22 | def test_heapify(self): 23 | size = 4 + random.randint(0, 20) 24 | elements = [chr(i) for i in range(ord('A'), ord('Z'))[:size]] 25 | heap = Heap(elements=elements) 26 | 27 | self.assertEqual(size, len(heap)) 28 | self.assertTrue(heap._validate()) 29 | 30 | 31 | heap = Heap(elements=['A', 'B', 'C', 'D']) 32 | 33 | self.assertEqual(4, len(heap)) 34 | self.assertEqual('D', heap.peek()) 35 | 36 | heap = Heap(elements=['A', 'B', 'C', 'D'], element_priority=lambda x: -ord(x)) 37 | 38 | self.assertEqual(4, len(heap)) 39 | self.assertEqual('A', heap.peek()) 40 | 41 | 42 | def test_heapify_duplicates(self): 43 | # An heap populated with pairs, where the second element in the pair is the priority 44 | heap = Heap(elements=['A', 'A', 'A', 'D', 'D']) 45 | 46 | self.assertEqual(5, len(heap)) 47 | self.assertEqual('D', heap.top()) 48 | self.assertEqual('D', heap.top()) 49 | self.assertEqual('A', heap.top()) 50 | self.assertEqual(2, len(heap)) 51 | 52 | heap = Heap(elements=['A', 'A', 'A', 'A']) 53 | 54 | self.assertEqual(4, len(heap)) 55 | self.assertEqual('A', heap.top()) 56 | 57 | 58 | def test_insert_top(self): 59 | heap = Heap([3, 1, 4, 11, -1, 2, 10]) 60 | self.assertEqual(7, len(heap)) 61 | heap.insert(7) 62 | heap.insert(5) 63 | self.assertEqual(9, len(heap)) 64 | self.assertTrue(heap._validate()) 65 | heap.top() 66 | heap.top() 67 | heap.top() 68 | self.assertEqual(6, len(heap)) 69 | self.assertTrue(heap._validate()) 70 | 71 | # An heap populated with pairs, where the second element in the pair is the priority 72 | heap = Heap(None, element_priority=lambda x: x[1]) 73 | 74 | self.assertTrue(heap.is_empty()) 75 | 76 | heap.insert(('First', -1e14)) 77 | 78 | self.assertEqual('First', heap.top()[0]) 79 | self.assertTrue(heap.is_empty()) 80 | 81 | heap.insert(("b", 0)) 82 | heap.insert(("c", 0.99)) 83 | heap.insert(("second", 1)) 84 | heap.insert(("a", -11)) 85 | 86 | self.assertEqual('second', heap.top()[0]) 87 | self.assertEqual(3, len(heap)) 88 | 89 | for i in range(0, 10): 90 | heap.insert((str(i), random.random())) 91 | 92 | while not heap.is_empty(): 93 | self.assertTrue(heap._validate()) 94 | heap.top() 95 | 96 | 97 | def test_top_empty(self): 98 | heap = Heap() 99 | 100 | with self.assertRaises(ValueError): 101 | heap.top() 102 | 103 | with self.assertRaises(ValueError): 104 | heap.peek() 105 | -------------------------------------------------------------------------------- /python/tests/test_queue.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from queues.queue import Queue 3 | from queues.queue_linked_list import Queue as QueueWithLinkedList 4 | 5 | class TestQueueTemplate(): 6 | def new_queue(self): # pragma: no cover 7 | raise NotImplementedError() 8 | 9 | def test_init(self): 10 | queue = self.new_queue() 11 | self.assertEqual(len(queue), 0) 12 | 13 | def test_enqueue_dequeue(self): 14 | queue = self.new_queue() 15 | self.assertEqual(len(queue), 0) 16 | 17 | # Dequeue from empty queue 18 | with self.assertRaises(ValueError): 19 | queue.dequeue() 20 | 21 | queue.enqueue('A') 22 | self.assertEqual(len(queue), 1) 23 | self.assertEqual(queue.dequeue(), 'A') 24 | self.assertEqual(len(queue), 0) 25 | 26 | # Dequeue from empty queue 27 | with self.assertRaises(ValueError): 28 | queue.dequeue() 29 | 30 | queue.enqueue('A') 31 | queue.enqueue('BB') 32 | queue.enqueue('CCC') 33 | self.assertEqual(len(queue), 3) 34 | self.assertEqual(queue.dequeue(), 'A') 35 | queue.enqueue(4) 36 | self.assertEqual(queue.dequeue(), 'BB') 37 | self.assertEqual(queue.dequeue(), 'CCC') 38 | self.assertEqual(queue.dequeue(), 4) 39 | 40 | 41 | def test_len(self): 42 | queue = self.new_queue() 43 | self.assertEqual(len(queue), 0) 44 | 45 | queue.enqueue(1) 46 | self.assertEqual(len(queue), 1) 47 | 48 | queue.enqueue(2) 49 | self.assertEqual(len(queue), 2) 50 | 51 | queue.enqueue(2) 52 | self.assertEqual(len(queue), 3) 53 | 54 | 55 | def test_is_empty(self): 56 | queue = self.new_queue() 57 | 58 | # Check empty queue 59 | self.assertTrue(queue.is_empty()) 60 | 61 | # Check non-empty queue 62 | queue.enqueue(1) 63 | self.assertFalse(queue.is_empty()) 64 | 65 | 66 | def test_iter(self): 67 | queue = self.new_queue() 68 | queue.enqueue(1) 69 | queue.enqueue(2) 70 | queue.enqueue(3) 71 | queue.dequeue() 72 | queue.enqueue(4) 73 | queue.enqueue(5) 74 | queue.dequeue() 75 | queue.enqueue(6) 76 | 77 | iterated = [x for x in queue] 78 | self.assertEqual(iterated, [3, 4, 5, 6]) 79 | self.assertTrue(queue.is_empty()) 80 | 81 | 82 | class TestQueue(TestQueueTemplate, unittest.TestCase): 83 | """Tests a circular queue implemented with static arrays.""" 84 | def new_queue(self, size=10): 85 | return Queue(size) 86 | 87 | 88 | # Tests specific to the array implementation 89 | def test_init_with_invalid_size(self): 90 | # Negative 91 | with self.assertRaises(ValueError): 92 | Queue(-1) 93 | # Null 94 | with self.assertRaises(ValueError): 95 | Queue(0) 96 | # Just one element 97 | with self.assertRaises(ValueError): 98 | Queue(1) 99 | 100 | 101 | def test_init_with_valid_size(self): 102 | queue = self.new_queue(2) 103 | queue.enqueue(1) 104 | queue.enqueue(2) 105 | with self.assertRaises(ValueError): 106 | queue.enqueue(3) 107 | self.assertEqual(queue.dequeue(), 1) 108 | queue.enqueue(3) 109 | self.assertEqual(queue.dequeue(), 2) 110 | self.assertEqual(queue.dequeue(), 3) 111 | with self.assertRaises(ValueError): 112 | queue.dequeue() 113 | queue.enqueue(4) 114 | self.assertEqual(len(queue), 1) 115 | self.assertEqual(queue.dequeue(), 4) 116 | 117 | 118 | def test_enqueue_to_a_full_queue(self): 119 | queue = self.new_queue(4) 120 | 121 | queue.enqueue(1) 122 | queue.enqueue('a') 123 | queue.enqueue(3) 124 | queue.enqueue('d') 125 | self.assertEqual(len(queue), 4) 126 | 127 | with self.assertRaises(ValueError): 128 | queue.enqueue('one too many') 129 | 130 | 131 | def test_repr(self): 132 | queue = self.new_queue() 133 | self.assertEqual(repr(queue), 'Queue([])') 134 | 135 | queue.enqueue(1) 136 | self.assertEqual(repr(queue), 'Queue([1])') 137 | 138 | queue.enqueue(2) 139 | queue.enqueue(3.14) 140 | 141 | self.assertEqual(repr(queue), 'Queue([1, 2, 3.14])') 142 | 143 | 144 | def test_str(self): 145 | queue = self.new_queue(5) 146 | self.assertEqual(str(queue), '[]') 147 | 148 | queue.enqueue('a') 149 | self.assertEqual(str(queue), '[\'a\']') 150 | 151 | queue.enqueue('b') 152 | queue.enqueue('c') 153 | 154 | self.assertEqual(str(queue), '[\'a\', \'b\', \'c\']') 155 | 156 | queue.dequeue() 157 | queue.enqueue('d') 158 | queue.enqueue('e') 159 | queue.dequeue() 160 | queue.enqueue('f') 161 | 162 | self.assertEqual(str(queue), '[\'c\', \'d\', \'e\', \'f\']') 163 | 164 | 165 | class TestQueueLinkedList(TestQueueTemplate, unittest.TestCase): 166 | """Runs the tests for a queue implemented with linked lists.""" 167 | def new_queue(self): 168 | return QueueWithLinkedList() 169 | 170 | def test_repr(self): 171 | queue = self.new_queue() 172 | self.assertEqual(repr(queue), 'Queue()') 173 | 174 | queue.enqueue(1) 175 | self.assertEqual(repr(queue), 'Queue(1)') 176 | 177 | queue.enqueue(2) 178 | queue.enqueue(3.14) 179 | 180 | self.assertEqual(repr(queue), 'Queue(1<->2<->3.14)') 181 | 182 | 183 | def test_str(self): 184 | queue = self.new_queue() 185 | self.assertEqual(str(queue), '') 186 | 187 | queue.enqueue('a') 188 | self.assertEqual(str(queue), 'a') 189 | 190 | queue.enqueue('b') 191 | queue.enqueue('c') 192 | 193 | self.assertEqual(str(queue), 'a<->b<->c') 194 | -------------------------------------------------------------------------------- /python/tests/test_singly_linked_list.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from linked_lists.singly_linked_list import SinglyLinkedList 3 | 4 | class TestSinglyLinkedList(unittest.TestCase): 5 | 6 | def test_init(self): 7 | linked_list = SinglyLinkedList() 8 | self.assertIsNone(linked_list._head) 9 | 10 | 11 | def test_len(self): 12 | linked_list = SinglyLinkedList() 13 | self.assertEqual(len(linked_list), 0) 14 | 15 | linked_list.insert_in_front(1) 16 | self.assertEqual(len(linked_list), 1) 17 | 18 | linked_list.insert_in_front(2) 19 | self.assertEqual(len(linked_list), 2) 20 | 21 | linked_list.insert_to_back(2) 22 | self.assertEqual(len(linked_list), 3) 23 | 24 | 25 | def test_repr(self): 26 | linked_list = SinglyLinkedList() 27 | self.assertEqual(repr(linked_list), 'SinglyLinkedList()') 28 | 29 | linked_list.insert_in_front(1) 30 | self.assertEqual(repr(linked_list), 'SinglyLinkedList(1)') 31 | 32 | linked_list.insert_in_front(2) 33 | self.assertEqual(repr(linked_list), 'SinglyLinkedList(2->1)') 34 | 35 | linked_list.insert_to_back(3.14) 36 | self.assertEqual(repr(linked_list), 'SinglyLinkedList(2->1->3.14)') 37 | 38 | 39 | def test_str(self): 40 | linked_list = SinglyLinkedList() 41 | self.assertEqual(str(linked_list), '') 42 | 43 | linked_list.insert_in_front('a') 44 | self.assertEqual(str(linked_list), 'a') 45 | 46 | linked_list.insert_in_front('b') 47 | self.assertEqual(str(linked_list), 'b->a') 48 | 49 | linked_list.insert_to_back('c') 50 | self.assertEqual(str(linked_list), 'b->a->c') 51 | 52 | 53 | def test_iter(self): 54 | linked_list = SinglyLinkedList() 55 | 56 | # Iterate over empty list 57 | self.assertEqual(list(linked_list), []) 58 | 59 | # Iterate over non-empty list 60 | linked_list.insert_in_front(1) 61 | linked_list.insert_in_front(2) 62 | linked_list.insert_to_back(3.14) 63 | linked_list.insert_to_back("AC") 64 | 65 | expected = [2, 1, 3.14, "AC"] 66 | self.assertEqual(list(linked_list), expected) 67 | 68 | 69 | def test_size(self): 70 | linked_list = SinglyLinkedList() 71 | self.assertEqual(linked_list.size(), 0) 72 | 73 | linked_list.insert_in_front(1) 74 | self.assertEqual(linked_list.size(), 1) 75 | 76 | linked_list.insert_in_front(2) 77 | self.assertEqual(linked_list.size(), 2) 78 | 79 | 80 | def test_is_empty(self): 81 | linked_list = SinglyLinkedList() 82 | self.assertTrue(linked_list.is_empty()) 83 | linked_list.insert_in_front(1) 84 | self.assertFalse(linked_list.is_empty()) 85 | linked_list.insert_in_front(2) 86 | linked_list.insert_in_front(3) 87 | self.assertFalse(linked_list.is_empty()) 88 | 89 | linked_list.delete(3) 90 | self.assertFalse(linked_list.is_empty()) 91 | 92 | linked_list.delete(2) 93 | self.assertFalse(linked_list.is_empty()) 94 | 95 | linked_list.delete(1) 96 | self.assertTrue(linked_list.is_empty()) 97 | 98 | 99 | def test_add_in_front(self): 100 | linked_list = SinglyLinkedList() 101 | 102 | # Add to empty list 103 | linked_list.insert_in_front(1) 104 | self.assertEqual(linked_list._head.data(), 1) 105 | 106 | # Add to non-empty list 107 | linked_list.insert_in_front(2) 108 | self.assertEqual(linked_list._head.data(), 2) 109 | self.assertEqual(linked_list._head.next().data(), 1) 110 | 111 | 112 | def test_add_to_back(self): 113 | linked_list = SinglyLinkedList() 114 | 115 | # Add to empty list 116 | linked_list.insert_to_back(1) 117 | self.assertEqual(linked_list._head.data(), 1) 118 | 119 | # Add to non-empty list 120 | linked_list.insert_in_front(2) 121 | linked_list.insert_to_back(3) 122 | self.assertEqual(linked_list._head.data(), 2) 123 | self.assertEqual(linked_list._head.next().data(), 1) 124 | self.assertEqual(linked_list._head.next().next().data(), 3) 125 | self.assertIsNone(linked_list._head.next().next().next()) 126 | 127 | 128 | def test_get_valid_index(self): 129 | linked_list = SinglyLinkedList() 130 | linked_list.insert_in_front(1) 131 | linked_list.insert_in_front(2) 132 | linked_list.insert_in_front(3) 133 | 134 | self.assertEqual(linked_list.get(0), 3) 135 | self.assertEqual(linked_list.get(1), 2) 136 | self.assertEqual(linked_list.get(2), 1) 137 | 138 | def test_get_invalid_index(self): 139 | linked_list = SinglyLinkedList() 140 | 141 | with self.assertRaises(IndexError): 142 | linked_list.get(-1) 143 | 144 | with self.assertRaises(IndexError): 145 | linked_list.get(0) # index out of bounds 146 | 147 | def test_get_returns_deep_copy(self): 148 | linked_list = SinglyLinkedList() 149 | linked_list.insert_in_front([['a', 'b'], 1, 2]) 150 | 151 | retrieved = linked_list.get(0) 152 | retrieved.append(3) 153 | retrieved[0].append('c') 154 | self.assertEqual(linked_list.get(0), [['a', 'b'], 1, 2]) 155 | 156 | 157 | def test_internal_search(self): 158 | linked_list = SinglyLinkedList() 159 | 160 | # Search empty list 161 | self.assertIsNone(linked_list._search(1)) 162 | 163 | # Search when not found 164 | linked_list.insert_in_front(2) 165 | linked_list.insert_in_front(1) 166 | self.assertIsNone(linked_list._search(3)) 167 | 168 | # Search when found 169 | linked_list.insert_in_front(3) 170 | found = linked_list._search(3) 171 | self.assertEqual(found.data(), 3) 172 | 173 | 174 | found = linked_list._search(2) 175 | self.assertEqual(found.data(), 2) 176 | 177 | 178 | def test_search_empty_list(self): 179 | linked_list = SinglyLinkedList() 180 | result = linked_list.search(lambda x: x == 1) 181 | self.assertIsNone(result) 182 | 183 | def test_search_not_found(self): 184 | linked_list = SinglyLinkedList() 185 | linked_list.insert_in_front(2) 186 | linked_list.insert_in_front(3) 187 | result = linked_list.search(lambda x: x == 1) 188 | self.assertIsNone(result) 189 | 190 | def test_search_found(self): 191 | linked_list = SinglyLinkedList() 192 | linked_list.insert_in_front(3) 193 | linked_list.insert_in_front(2) 194 | linked_list.insert_in_front(1) 195 | result = linked_list.search(lambda x: x == 1) 196 | self.assertEqual(result, 1) 197 | result = linked_list.search(lambda x: x == 2) 198 | self.assertEqual(result, 2) 199 | result = linked_list.search(lambda x: x == 3) 200 | self.assertEqual(result, 3) 201 | 202 | def test_search_multiple_matches(self): 203 | linked_list = SinglyLinkedList() 204 | linked_list.insert_in_front('AB') 205 | linked_list.insert_in_front('C') 206 | linked_list.insert_in_front('ABC') 207 | linked_list.insert_in_front('B') 208 | result = linked_list.search(lambda x: x[0] == 'A') 209 | self.assertEqual(result, 'ABC') 210 | 211 | def test_delete(self): 212 | linked_list = SinglyLinkedList() 213 | linked_list.insert_in_front(1) 214 | linked_list.insert_in_front(2) 215 | linked_list.insert_in_front(3) 216 | 217 | linked_list.delete(2) 218 | 219 | self.assertEqual(len(linked_list), 2) 220 | self.assertEqual(linked_list._head.data(), 3) 221 | self.assertEqual(linked_list._head.next().data(), 1) 222 | 223 | 224 | def test_delete_head(self): 225 | linked_list = SinglyLinkedList() 226 | linked_list.insert_in_front(1) 227 | linked_list.insert_in_front(2) 228 | linked_list.insert_in_front(3) 229 | 230 | linked_list.delete(3) 231 | self.assertEqual(len(linked_list), 2) 232 | self.assertEqual(linked_list._head.data(), 2) 233 | self.assertEqual(linked_list._head.next().data(), 1) 234 | 235 | linked_list.delete(2) 236 | self.assertEqual(len(linked_list), 1) 237 | self.assertEqual(linked_list._head.data(), 1) 238 | self.assertIsNone(linked_list._head.next()) 239 | 240 | linked_list.delete(1) 241 | self.assertEqual(len(linked_list), 0) 242 | 243 | 244 | def test_delete_invalid(self): 245 | linked_list = SinglyLinkedList() 246 | linked_list.insert_in_front(3) 247 | linked_list.insert_in_front(2) 248 | linked_list.insert_in_front(1) 249 | 250 | with self.assertRaises(ValueError): 251 | linked_list.delete(4) 252 | 253 | 254 | def test_delete_from_front(self): 255 | linked_list = SinglyLinkedList() 256 | 257 | # Delete from empty list 258 | with self.assertRaises(ValueError): 259 | linked_list.delete_from_front() 260 | 261 | # Delete from list with one element 262 | linked_list.insert_in_front(1) 263 | self.assertEqual(linked_list.delete_from_front(), 1) 264 | self.assertTrue(linked_list.is_empty()) 265 | 266 | # Delete from list with multiple elements 267 | linked_list.insert_in_front(2) 268 | linked_list.insert_in_front(1) 269 | self.assertEqual(linked_list.delete_from_front(), 1) 270 | self.assertEqual(len(linked_list), 1) 271 | self.assertEqual(linked_list._head.data(), 2) 272 | 273 | 274 | class TestNode(unittest.TestCase): 275 | 276 | def test_init(self): 277 | data = 'test' 278 | node = SinglyLinkedList.Node(data) 279 | self.assertEqual(node.data(), data) 280 | self.assertIsNone(node.next()) 281 | 282 | def test_str(self): 283 | data = 'test' 284 | node = SinglyLinkedList.Node(data) 285 | self.assertEqual(str(node), str(data)) 286 | 287 | def test_repr(self): 288 | data = 'test' 289 | node = SinglyLinkedList.Node(data) 290 | self.assertEqual(repr(node), repr(data)) 291 | 292 | def test_next(self): 293 | node1 = SinglyLinkedList.Node(1) 294 | node2 = SinglyLinkedList.Node(2, node1) 295 | self.assertEqual(node2.next(), node1) 296 | 297 | def test_append(self): 298 | node1 = SinglyLinkedList.Node(1) 299 | node2 = SinglyLinkedList.Node(2) 300 | node1.append(node2) 301 | self.assertEqual(node1.next(), node2) 302 | 303 | node1.append(None) 304 | self.assertIsNone(node1.next()) 305 | 306 | def test_has_next(self): 307 | node = SinglyLinkedList.Node(1) 308 | self.assertFalse(node.has_next()) 309 | 310 | new_node = SinglyLinkedList.Node(2, node) 311 | self.assertTrue(new_node.has_next()) 312 | -------------------------------------------------------------------------------- /python/tests/test_sorted_array.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from arrays.sorted_array import SortedArray 3 | 4 | class TestSortedArray(unittest.TestCase): 5 | # __init__ 6 | 7 | def test_init(self): 8 | """Test initialization of SortedArray.""" 9 | array = SortedArray(5) 10 | self.assertEqual(len(array), 0) 11 | 12 | array = SortedArray(3, 'f') 13 | self.assertEqual(len(array), 0) 14 | 15 | def test_init_invalid_size(self): 16 | """Test initializing Array with invalid size""" 17 | with self.assertRaises(ValueError): 18 | SortedArray(-1) 19 | 20 | def test_init_invalid_typecode(self): 21 | """Test initializing Array with invalid typecode""" 22 | with self.assertRaises(ValueError): 23 | SortedArray(5, 'x') 24 | 25 | 26 | # __len__ 27 | 28 | def test_len(self): 29 | """Test length property.""" 30 | array = SortedArray(5, 'i') 31 | self.assertEqual(len(array), 0) 32 | array.insert(1) 33 | self.assertEqual(len(array), 1) 34 | 35 | 36 | # __get_item__ 37 | 38 | def test_getitem(self): 39 | """Test indexing into array.""" 40 | array = SortedArray(5, 'i') 41 | array.insert(1) 42 | array.insert(2) 43 | self.assertEqual(array[0], 1) 44 | self.assertEqual(array[1], 2) 45 | 46 | def test_index_out_of_bounds(self): 47 | """Test that indexing past the end of the array raises an error.""" 48 | array = SortedArray(5, 'i') 49 | array.insert(1) 50 | with self.assertRaises(IndexError): 51 | array[2] 52 | 53 | def test_negative_index(self): 54 | """Test that indexing with a negative index raises an error.""" 55 | array = SortedArray(3, 'i') 56 | array.insert(431) 57 | with self.assertRaises(IndexError): 58 | array[-1] 59 | 60 | # __repr__ 61 | 62 | def test_repr(self): 63 | """Test string representation.""" 64 | array = SortedArray(5, 'i') 65 | array.insert(1) 66 | array.insert(2) 67 | self.assertEqual(repr(array), "SortedArray(array('i', [1, 2]))") 68 | 69 | 70 | # __iter__ 71 | 72 | def test_iter(self): 73 | """Test iteration over values in the array.""" 74 | array = SortedArray(8, 'i') 75 | array.insert(1) 76 | array.insert(2) 77 | array.insert(3) 78 | iterated_values = [] 79 | for value in array: 80 | iterated_values.append(value) 81 | expected_values = [1, 2, 3] 82 | self.assertEqual(iterated_values, expected_values) 83 | 84 | def test_iter_empty(self): 85 | """Test that iteration over an empty array raises StopIteration.""" 86 | array = SortedArray(3, 'i') 87 | with self.assertRaises(StopIteration): 88 | next(iter(array)) 89 | 90 | 91 | # max_size 92 | 93 | def test_max_size(self): 94 | """Test the max_size method.""" 95 | array = SortedArray(7, 'i') 96 | self.assertEqual(array.max_size(), 7) 97 | array.insert(1) 98 | self.assertEqual(array.max_size(), 7) 99 | array.insert(-2) 100 | self.assertEqual(array.max_size(), 7) 101 | 102 | 103 | # insert 104 | 105 | def test_insert_full(self): 106 | """Test that insert raises an error if the array is full.""" 107 | array = SortedArray(2, 'i') 108 | array.insert(1) 109 | array.insert(2) 110 | with self.assertRaises(ValueError): 111 | array.insert(3) 112 | 113 | def test_insert_sorted(self): 114 | """Test that insert places values in the correct sorted position.""" 115 | array = SortedArray(6, 'i') 116 | array.insert(3) 117 | self.assertEqual(len(array), 1) 118 | self.assertEqual(array[0], 3) 119 | array.insert(1) 120 | self.assertEqual(len(array), 2) 121 | self.assertEqual(array[0], 1) 122 | self.assertEqual(array[1], 3) 123 | array.insert(2) 124 | self.assertEqual(len(array), 3) 125 | self.assertEqual(array[0], 1) 126 | self.assertEqual(array[1], 2) 127 | self.assertEqual(array[2], 3) 128 | 129 | def test_insert_first(self): 130 | """Test that insert places the first value at index 0.""" 131 | array = SortedArray(5, 'i') 132 | array.insert(1) 133 | self.assertEqual(array[0], 1) 134 | array.insert(-1) 135 | self.assertEqual(array[0], -1) 136 | self.assertEqual(len(array), 2) 137 | 138 | 139 | # search (linear) 140 | 141 | def test_linear_search_found(self): 142 | """Test searching for a value that is in the array.""" 143 | array = SortedArray(4, 'i') 144 | array.insert(2) 145 | array.insert(1) 146 | array.insert(3) 147 | self.assertEqual(array.linear_search(2), 1) 148 | array.insert(-43) 149 | self.assertEqual(array.linear_search(-43), 0) 150 | array = SortedArray(3, 'd') 151 | array.insert(21.3) 152 | array.insert(3.1415) 153 | self.assertEqual(array.linear_search(3.1415), 0) 154 | 155 | def test_linear_search_not_found(self): 156 | """Test searching for a value that is not in the array.""" 157 | array = SortedArray(5, 'i') 158 | array.insert(3) 159 | array.insert(1) 160 | array.insert(2) 161 | self.assertEqual(array.linear_search(4), None) 162 | 163 | def test_linear_search_empty(self): 164 | """Test searching an empty array.""" 165 | array = SortedArray(2, 'i') 166 | self.assertEqual(array.linear_search(1), None) 167 | 168 | def test_linear_search_empty_chunk_not_included(self): 169 | """Test that only the filled portion of the array is searched.""" 170 | array = SortedArray(5, 'i') 171 | array.insert(3) 172 | array.insert(1) 173 | array.insert(2) 174 | self.assertEqual(array.linear_search(0), None) 175 | array.insert(0) 176 | self.assertEqual(array.linear_search(0), 0) 177 | array = SortedArray(5, 'd') 178 | array.insert(-1.0) 179 | array.insert(-3) 180 | array.insert(-2) 181 | self.assertEqual(array.linear_search(0), None) 182 | self.assertEqual(array.linear_search(0.0), None) 183 | array.insert(0) 184 | self.assertEqual(array.linear_search(0), 3) 185 | 186 | 187 | # search (binary search) 188 | 189 | def test_find_found(self): 190 | """Test finding a value that is in the array.""" 191 | array = SortedArray(7, 'i') 192 | array.insert(1) 193 | array.insert(3) 194 | array.insert(2) 195 | self.assertEqual(array.binary_search(2), 1) 196 | array.insert(-1) 197 | array.insert(23) 198 | array.insert(-2) 199 | self.assertEqual(array.binary_search(2), 3) 200 | self.assertEqual(array.binary_search(-1), 1) 201 | self.assertEqual(array.binary_search(-2), 0) 202 | self.assertEqual(array.binary_search(23), 5) 203 | 204 | def test_find_not_found(self): 205 | """Test finding a value that is not in the array.""" 206 | array = SortedArray(5, 'i') 207 | array.insert(1) 208 | array.insert(2) 209 | array.insert(3) 210 | self.assertEqual(array.binary_search(4), None) 211 | self.assertEqual(array.binary_search(-10), None) 212 | 213 | def test_find_empty_chunk_not_included(self): 214 | """Test that only the filled portion of the array is searched.""" 215 | array = SortedArray(5, 'i') 216 | array.insert(3) 217 | array.insert(1) 218 | array.insert(2) 219 | self.assertEqual(array.binary_search(0), None) 220 | array.insert(0) 221 | self.assertEqual(array.binary_search(0), 0) 222 | array = SortedArray(5, 'd') 223 | array.insert(-1.0) 224 | array.insert(-3) 225 | array.insert(-2) 226 | self.assertEqual(array.binary_search(0), None) 227 | self.assertEqual(array.binary_search(0.0), None) 228 | array.insert(0) 229 | self.assertEqual(array.binary_search(0), 3) 230 | 231 | def test_find_empty(self): 232 | """Test finding a value in an empty array.""" 233 | array = SortedArray(5, 'i') 234 | self.assertEqual(array.binary_search(1), None) 235 | self.assertEqual(array.binary_search(0), None) 236 | 237 | 238 | # delete 239 | 240 | def test_delete_found(self): 241 | """Test deleting a value that is in the array.""" 242 | array = SortedArray(5, 'i') 243 | array.insert(1) 244 | array.insert(2) 245 | array.insert(3) 246 | self.assertEqual(len(array), 3) 247 | 248 | array.delete(2) 249 | self.assertEqual(len(array), 2) 250 | self.assertEqual(array[0], 1) 251 | self.assertEqual(array[1], 3) 252 | 253 | array = SortedArray(7, 'i') 254 | array.insert(3) 255 | array.insert(1) 256 | array.insert(3) 257 | array.insert(2) 258 | array.insert(2) 259 | self.assertEqual(len(array), 5) 260 | 261 | array.delete(2) 262 | self.assertEqual(len(array), 4) 263 | self.assertEqual(array[0], 1) 264 | self.assertEqual(array[1], 2) 265 | self.assertEqual(array[2], 3) 266 | self.assertEqual(array[3], 3) 267 | 268 | array.delete(3) 269 | self.assertEqual(len(array), 3) 270 | array.delete(1) 271 | self.assertEqual(len(array), 2) 272 | self.assertEqual(array[0], 2) 273 | self.assertEqual(array[1], 3) 274 | 275 | array.insert(0) 276 | array.insert(-3) 277 | array.insert(2) 278 | self.assertEqual(len(array), 5) 279 | 280 | array.delete(-3) 281 | self.assertEqual(len(array), 4) 282 | self.assertEqual(array[0], 0) 283 | self.assertEqual(array[1], 2) 284 | self.assertEqual(array[2], 2) 285 | self.assertEqual(array[3], 3) 286 | 287 | array.delete(2) 288 | self.assertEqual(len(array), 3) 289 | 290 | array.delete(2) 291 | array.delete(0) 292 | self.assertEqual(len(array), 1) 293 | self.assertEqual(array[0], 3) 294 | 295 | array.delete(3) 296 | self.assertEqual(len(array), 0) 297 | 298 | def test_delete_not_found(self): 299 | """Test deleting a value that is not in the array.""" 300 | array = SortedArray(15, 'i') 301 | array.insert(1) 302 | array.insert(3) 303 | array.insert(2) 304 | with self.assertRaises(ValueError): 305 | array.delete(4) 306 | 307 | def test_delete_empty(self): 308 | """Test deleting from an empty array.""" 309 | array = SortedArray(5, 'i') 310 | with self.assertRaises(ValueError): 311 | array.delete(1) 312 | -------------------------------------------------------------------------------- /python/tests/test_sorted_singly_linked_list.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from linked_lists.sorted_singly_linked_list import SortedSinglyLinkedList 3 | 4 | class TestSortedSinglyLinkedList(unittest.TestCase): 5 | 6 | def test_insert(self): 7 | sorted_list = SortedSinglyLinkedList() 8 | 9 | # Insert into empty list 10 | sorted_list.insert(6) 11 | self.assertEqual(sorted_list._head.data(), 6) 12 | 13 | # Insert at head 14 | sorted_list.insert(3) 15 | self.assertEqual(sorted_list._head.data(), 3) 16 | 17 | # Insert in middle 18 | sorted_list.insert(5) 19 | self.assertEqual(sorted_list._head.next().data(), 5) 20 | sorted_list.insert(4) 21 | self.assertEqual(sorted_list._head.next().data(), 4) 22 | 23 | # Insert at tail 24 | self.assertEqual(sorted_list._head.next().next().next().data(), 6) 25 | sorted_list.insert(7) 26 | self.assertEqual(sorted_list._head.next().next().next().data(), 6) 27 | self.assertEqual(sorted_list._head.next().next().next().next().data(), 7) 28 | 29 | def test_insert_in_front(self): 30 | sorted_list = SortedSinglyLinkedList() 31 | with self.assertRaises(NotImplementedError): 32 | sorted_list.insert_in_front(10) 33 | 34 | 35 | def test_insert_to_back(self): 36 | sorted_list = SortedSinglyLinkedList() 37 | with self.assertRaises(NotImplementedError): 38 | sorted_list.insert_to_back(10) 39 | 40 | 41 | def test_search(self): 42 | linked_list = SortedSinglyLinkedList() 43 | 44 | # Search empty list 45 | self.assertIsNone(linked_list._search(1)) 46 | 47 | # Search when not found 48 | linked_list.insert(2) 49 | linked_list.insert(1) 50 | self.assertIsNone(linked_list._search(3)) 51 | 52 | # Search when found 53 | linked_list.insert(3) 54 | found = linked_list._search(3) 55 | self.assertEqual(found.data(), 3) 56 | 57 | 58 | found = linked_list._search(2) 59 | self.assertEqual(found.data(), 2) 60 | 61 | 62 | def test_delete(self): 63 | linked_list = SortedSinglyLinkedList() 64 | linked_list.insert(1) 65 | linked_list.insert(2) 66 | linked_list.insert(3) 67 | 68 | linked_list.delete(2) 69 | 70 | self.assertEqual(len(linked_list), 2) 71 | self.assertEqual(linked_list._head.data(), 1) 72 | self.assertEqual(linked_list._head.next().data(), 3) -------------------------------------------------------------------------------- /python/tests/test_stack.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from stacks.stack import Stack 3 | from stacks.stack_dynamic_array import Stack as StackWithArray 4 | 5 | class TestStackTemplate(): 6 | def new_stack(self): # pragma: no cover 7 | raise NotImplementedError() 8 | 9 | def test_init(self): 10 | stack = self.new_stack() 11 | self.assertEqual(len(stack), 0) 12 | 13 | def test_push_pop(self): 14 | stack = self.new_stack() 15 | self.assertEqual(len(stack), 0) 16 | 17 | # Pop from empty stack 18 | with self.assertRaises(ValueError): 19 | stack.pop() 20 | 21 | stack.push('A') 22 | self.assertEqual(len(stack), 1) 23 | self.assertEqual(stack.pop(), 'A') 24 | self.assertEqual(len(stack), 0) 25 | 26 | # Pop from empty stack 27 | with self.assertRaises(ValueError): 28 | stack.pop() 29 | 30 | stack.push('A') 31 | stack.push('BB') 32 | stack.push('CCC') 33 | self.assertEqual(len(stack), 3) 34 | self.assertEqual(stack.pop(), 'CCC') 35 | stack.push(4) 36 | self.assertEqual(stack.pop(), 4) 37 | self.assertEqual(stack.pop(), 'BB') 38 | self.assertEqual(stack.pop(), 'A') 39 | 40 | 41 | def test_peek(self): 42 | stack = self.new_stack() 43 | self.assertEqual(len(stack), 0) 44 | 45 | # Peek at empty stack 46 | with self.assertRaises(ValueError): 47 | stack.peek() 48 | 49 | stack.push('A') 50 | self.assertEqual(len(stack), 1) 51 | self.assertEqual(stack.peek(), 'A') 52 | self.assertEqual(len(stack), 1) 53 | 54 | stack.push('BB') 55 | stack.push('CCC') 56 | self.assertEqual(len(stack), 3) 57 | self.assertEqual(stack.peek(), 'CCC') 58 | self.assertEqual(stack.pop(), 'CCC') 59 | self.assertEqual(stack.peek(), 'BB') 60 | 61 | 62 | def test_len(self): 63 | stack = self.new_stack() 64 | self.assertEqual(len(stack), 0) 65 | 66 | stack.push(1) 67 | self.assertEqual(len(stack), 1) 68 | 69 | stack.push(2) 70 | self.assertEqual(len(stack), 2) 71 | 72 | stack.push(2) 73 | self.assertEqual(len(stack), 3) 74 | 75 | 76 | def test_repr(self): 77 | stack = self.new_stack() 78 | self.assertEqual(repr(stack), 'Stack()') 79 | 80 | stack.push(1) 81 | self.assertEqual(repr(stack), 'Stack(1)') 82 | 83 | stack.push(2) 84 | stack.push(3.14) 85 | # When more than one element is in the stack, we can't put constraints on the order 86 | self.assertEqual(repr(stack), 'Stack(3.14->2->1)') 87 | 88 | 89 | def test_str(self): 90 | stack = self.new_stack() 91 | self.assertEqual(str(stack), '') 92 | 93 | stack.push('a') 94 | self.assertEqual(str(stack), 'a') 95 | 96 | stack.push('b') 97 | stack.push('c') 98 | # When more than one element is in the stack, we can't put constraints on the order 99 | self.assertEqual(str(stack), 'c->b->a') 100 | 101 | 102 | def test_is_empty(self): 103 | stack = self.new_stack() 104 | 105 | # Check empty stack 106 | self.assertTrue(stack.is_empty()) 107 | 108 | # Check non-empty stack 109 | stack.push(1) 110 | self.assertFalse(stack.is_empty()) 111 | 112 | 113 | def test_iter(self): 114 | stack = self.new_stack() 115 | stack.push(1) 116 | stack.push(2) 117 | stack.push(3) 118 | stack.pop() 119 | stack.push(4) 120 | stack.push(5) 121 | stack.pop() 122 | stack.push(6) 123 | 124 | iterated = [x for x in stack] 125 | self.assertEqual(iterated, [6, 4, 2, 1]) 126 | self.assertTrue(stack.is_empty()) 127 | 128 | 129 | class TestStack(TestStackTemplate, unittest.TestCase): 130 | """Tests a stack implemented with linked lists.""" 131 | def new_stack(self): 132 | return Stack() 133 | 134 | 135 | class TestStackArray(TestStackTemplate, unittest.TestCase): 136 | """Runs the tests for a stack implemented with arrays (Python lists).""" 137 | def new_stack(self): 138 | return StackWithArray() 139 | 140 | # Additional tests specific to stack implemented with arrays 141 | def test_repr(self): 142 | stack = self.new_stack() 143 | self.assertEqual(repr(stack), 'Stack([])') 144 | 145 | stack.push(1) 146 | self.assertEqual(repr(stack), 'Stack([1])') 147 | 148 | stack.push(2) 149 | stack.push(3.14) 150 | self.assertEqual(repr(stack), 'Stack([3.14, 2, 1])') 151 | 152 | 153 | def test_str(self): 154 | stack = self.new_stack() 155 | self.assertEqual(str(stack), '[]') 156 | 157 | stack.push('a') 158 | self.assertEqual(str(stack), '[\'a\']') 159 | 160 | stack.push('b') 161 | stack.push('c') 162 | self.assertEqual(str(stack), '[\'c\', \'b\', \'a\']') 163 | -------------------------------------------------------------------------------- /python/tests/test_unsorted_array.py: -------------------------------------------------------------------------------- 1 | """Tests for class UnsortedArray""" 2 | import unittest 3 | from arrays.unsorted_array import UnsortedArray 4 | 5 | class TestArray(unittest.TestCase): 6 | """Tests for class UnsortedArray 7 | 8 | This test class contains unit tests for the UnsortedArray class. 9 | """ 10 | 11 | # __repr__ 12 | 13 | def test_repr(self): 14 | """Test string representation.""" 15 | array = UnsortedArray(5, 'i') 16 | array.insert(1) 17 | array.insert(2) 18 | self.assertEqual(repr(array), "UnsortedArray(array('i', [1, 2]))") 19 | 20 | # __get_item__ 21 | 22 | def test_getitem_valid_index(self): 23 | array = UnsortedArray(5, 'f') 24 | array.insert(1) 25 | array.insert(2) 26 | self.assertEqual(array[0], 1) 27 | self.assertEqual(array[1], 2) 28 | 29 | def test_getitem_invalid_index(self): 30 | array = UnsortedArray(3, 'i') 31 | with self.assertRaises(IndexError): 32 | array[3] 33 | with self.assertRaises(IndexError): 34 | array[-1] 35 | 36 | # max_size 37 | 38 | def test_max_size(self): 39 | array = UnsortedArray(3, 'i') 40 | self.assertEqual(array.max_size(), 3) 41 | array.insert(2) 42 | self.assertEqual(array.max_size(), 3) 43 | array = UnsortedArray(6, 'f') 44 | self.assertEqual(array.max_size(), 6) 45 | 46 | # __insert__ 47 | 48 | def test_insert_valid(self): 49 | """Test inserting into an array with space""" 50 | array = UnsortedArray(5) 51 | array.insert(1) 52 | self.assertEqual(len(array), 1) 53 | self.assertEqual(array[0], 1) 54 | 55 | def test_insert_in_full_array(self): 56 | """Test inserting into a full array""" 57 | array = UnsortedArray(1) 58 | array.insert(1) 59 | with self.assertRaises(ValueError): 60 | array.insert(2) 61 | 62 | # delete 63 | 64 | def test_delete_valid(self): 65 | """Test deleting from an array with elements""" 66 | array = UnsortedArray(5) 67 | array.insert(1) 68 | array.insert(2) 69 | array.insert(3) 70 | array.insert(4) 71 | array.delete(1) 72 | self.assertEqual(len(array), 3) 73 | self.assertEqual(array[1], 4) 74 | array.delete(2) 75 | self.assertEqual(len(array), 2) 76 | self.assertEqual(array[0], 1) 77 | self.assertEqual(array[1], 4) 78 | array.delete(0) 79 | self.assertEqual(len(array), 1) 80 | self.assertEqual(array[0], 4) 81 | array.delete(0) 82 | self.assertEqual(len(array), 0) 83 | 84 | def test_delete_invalid_empty(self): 85 | """Test deleting from an empty array""" 86 | array = UnsortedArray(3) 87 | with self.assertRaises(ValueError): 88 | array.delete(0) 89 | 90 | def test_delete_invalid_index(self): 91 | """Test deleting with an invalid index""" 92 | array = UnsortedArray(5) 93 | array.insert(1) 94 | with self.assertRaises(ValueError): 95 | array.delete(1) 96 | with self.assertRaises(ValueError): 97 | array.delete(-1) 98 | 99 | # find 100 | 101 | def test_find_present(self): 102 | """Test finding an entry that is present in the array""" 103 | array = UnsortedArray(7) 104 | array.insert(4) 105 | array.insert(-2) 106 | array.insert(55) 107 | self.assertEqual(array.find(-2), 1) 108 | self.assertEqual(array.find(55), 2) 109 | self.assertEqual(array.find(4), 0) 110 | 111 | def test_find_absent(self): 112 | """Test finding an entry that is absent from the array""" 113 | array = UnsortedArray(5) 114 | array.insert(1) 115 | array.insert(2) 116 | array.insert(3) 117 | index = array.find(4) 118 | self.assertEqual(index, None) 119 | 120 | # traverse 121 | 122 | def test_traverses_entire_array(self): 123 | result = [] 124 | def test_callback(i: int): 125 | result.append(i + 1 ) 126 | array = UnsortedArray(5) 127 | array.insert(1) 128 | array.insert(2) 129 | array.insert(3) 130 | array.insert(4) 131 | array.insert(5) 132 | array.traverse(test_callback) 133 | self.assertEqual(result, [2, 3, 4, 5, 6]) 134 | -------------------------------------------------------------------------------- /python/trees/bst.py: -------------------------------------------------------------------------------- 1 | """Binary Search Tree (BST) implementation.""" 2 | from __future__ import annotations 3 | from typing import Optional 4 | from stacks.stack import Stack 5 | 6 | class BinarySearchTree: 7 | """A class modeling the binary search tree data structure.""" 8 | 9 | class Node: 10 | """A class modeling the nodes of the binary search tree.""" 11 | 12 | @staticmethod 13 | def _node_str(node: type[BinarySearchTree.Node]) -> str: 14 | return str(node) if node is not None else '' 15 | 16 | def __init__(self, value: any, 17 | left: type[BinarySearchTree.Node] = None, 18 | right: type[BinarySearchTree.Node] = None) -> None: 19 | self._value = value 20 | self._left = left 21 | self._right = right 22 | 23 | def __str__(self) -> str: 24 | left_str = BinarySearchTree.Node._node_str(self._left) 25 | right_str = BinarySearchTree.Node._node_str(self._right) 26 | return f'{self._value} ({left_str})({right_str})' 27 | 28 | def value(self) -> any: 29 | """Return the value of the node.""" 30 | return self._value 31 | 32 | def left(self) -> type[BinarySearchTree.Node]: 33 | """Return a reference to the left child of the node.""" 34 | return self._left 35 | 36 | def right(self) -> type[BinarySearchTree.Node]: 37 | """Return a reference to the right child of the node.""" 38 | return self._right 39 | 40 | def set_left(self, node: type[BinarySearchTree.Node]) -> None: 41 | """Set the left child of the node. The old value will be lost.""" 42 | self._left = node 43 | 44 | def set_right(self, node: type[BinarySearchTree.Node]) -> None: 45 | """Set the right child of the node. The old value will be lost.""" 46 | self._right = node 47 | 48 | def find_min_in_subtree(self) -> tuple[type[BinarySearchTree.Node], type[BinarySearchTree.Node]]: 49 | """Return the node with the smallest value in the subtree 50 | rooted at the node, and its parent.""" 51 | parent = None 52 | node = self 53 | while node.left() is not None: 54 | parent = node 55 | node = node.left() 56 | return node, parent 57 | 58 | def find_max_in_subtree(self) -> tuple[type[BinarySearchTree.Node], type[BinarySearchTree.Node]]: 59 | """Return the node with the largest value in the subtree 60 | rooted at the node, and its parent.""" 61 | parent = None 62 | node = self 63 | while node.right() is not None: 64 | parent = node 65 | node = node.right() 66 | return node, parent 67 | 68 | 69 | def __init__(self) -> None: 70 | self._root = None 71 | 72 | 73 | def __repr__(self) -> str: 74 | return f'BinarySearchTree({str(self)})' 75 | 76 | 77 | def __str__(self) -> str: 78 | return BinarySearchTree.Node._node_str(self._root) 79 | 80 | 81 | def __len__(self) -> bool: 82 | """Return the number of values stored in the tree.""" 83 | # This version of the traversal algorithm uses an explicit stack, instead of recursion. 84 | stack = Stack() 85 | stack.push(self._root) 86 | size = 0 87 | while len(stack) > 0: 88 | node = stack.pop() 89 | if node is not None: 90 | size += 1 91 | stack.push(node.right()) 92 | stack.push(node.left()) 93 | return size 94 | 95 | 96 | def __iter__(self): 97 | """ 98 | Iterate over the values in the BST. 99 | 100 | Parameters: 101 | None 102 | 103 | Functionality: 104 | Iterates over the values in the BST. The iteration starts at the root of the BST and 105 | traverses the tree using inorder traversal. 106 | """ 107 | current = self._root 108 | stack = Stack() 109 | while current is not None or len(stack) > 0: 110 | if current is None: 111 | current = stack.pop() 112 | yield current.value() 113 | current = current.right() 114 | else: 115 | while current.left() is not None: 116 | stack.push(current) 117 | current = current.left() 118 | yield current.value() 119 | current = current.right() 120 | 121 | 122 | def _search(self, value: any) -> tuple[Optional[type[BinarySearchTree.Node]], type[BinarySearchTree.Node]]: 123 | """Returns a tuple. 124 | The first element in the tuple is the node containing the target value, 125 | or None if not found. If the tree contains duplicates, it returns the first 126 | node traversed that contains the target value. 127 | The second element in the tuple is the parent of the node in the first position. 128 | If the target wasn't found or if it was the root, the parent is set to None. 129 | """ 130 | parent = None 131 | node = self._root 132 | while node is not None: 133 | node_val = node.value() 134 | if node_val == value: 135 | return node, parent 136 | elif value < node_val: 137 | parent = node 138 | node = node.left() 139 | else: 140 | parent = node 141 | node = node.right() 142 | return None, None 143 | 144 | 145 | def contains(self, value: any) -> bool: 146 | """Return True if the tree contains the value, False otherwise. 147 | 148 | Args: 149 | value: The element to be searched in the tree. 150 | Returns: True if the value is found in the tree, False otherwise. 151 | """ 152 | return self._search(value)[0] is not None 153 | 154 | 155 | def insert(self, value: any) -> None: 156 | """Insert a new value into the tree. 157 | 158 | Args: 159 | value: The new element to be added to the tree. 160 | """ 161 | node = self._root 162 | if node is None: 163 | # Empty tree 164 | self._root = BinarySearchTree.Node(value) 165 | else: 166 | while True: # node can never be None here 167 | if value <= node.value(): 168 | if node.left() is None: 169 | # We have found the right spot for value 170 | node.set_left(BinarySearchTree.Node(value)) 171 | break 172 | else: 173 | # We keep traversing the left branch 174 | node = node.left() 175 | elif node.right() is None: 176 | # We have found the right spot for value 177 | node.set_right(BinarySearchTree.Node(value)) 178 | break 179 | else: 180 | # We keep traversing the right branch 181 | node = node.right() 182 | 183 | def delete(self, value: any) -> None: 184 | """ Delete a value from the tree. 185 | If the value is not found, raise a ValueError. 186 | If the tree is empty, raise a ValueError. 187 | If the tree contains duplicates, delete the first node found. 188 | Args: 189 | value: The element to be deleted from the tree. 190 | """ 191 | if self._root is None: 192 | raise ValueError('Delete on an empty tree') 193 | node, parent = self._search(value) 194 | if node is None: 195 | raise ValueError('Value not found') 196 | 197 | if node.left() is None or node.right() is None: 198 | maybe_child = node.right() if node.left() is None else node.left() 199 | # The node has at most only one child 200 | if parent is None: 201 | # The node is the root 202 | self._root = maybe_child 203 | elif value <= parent.value(): 204 | parent.set_left(maybe_child) 205 | else: 206 | parent.set_right(maybe_child) 207 | else: # The node N has two children. 208 | # Find and remove the node M with the largest value in the left subtree of N. 209 | max_node, max_node_parent = node.left().find_max_in_subtree() 210 | if max_node_parent is None: # M is the left child of N. 211 | new_node = BinarySearchTree.Node(max_node.value(), None, node.right()) 212 | else: 213 | new_node = BinarySearchTree.Node(max_node.value(), node.left(), node.right()) 214 | max_node_parent.set_right(max_node.left()) 215 | # Then replace the node to be deleted with a new node with M.value(), 216 | # and the same subtrees as N. 217 | if parent is None: 218 | # The node is the root 219 | self._root = new_node 220 | elif value <= parent.value(): 221 | parent.set_left(new_node) 222 | else: 223 | parent.set_right(new_node) 224 | -------------------------------------------------------------------------------- /readme/thumbs/CH01_UN01_La_Rocca3.md.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlarocca/grokking_data_structures/9c78516f70e44ddc910778a059e6a5d242fcc447/readme/thumbs/CH01_UN01_La_Rocca3.md.jpg -------------------------------------------------------------------------------- /readme/thumbs/CH02_UN06_La_Rocca3.md.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlarocca/grokking_data_structures/9c78516f70e44ddc910778a059e6a5d242fcc447/readme/thumbs/CH02_UN06_La_Rocca3.md.jpg -------------------------------------------------------------------------------- /readme/thumbs/CH03_UN08_La_Rocca3.md.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlarocca/grokking_data_structures/9c78516f70e44ddc910778a059e6a5d242fcc447/readme/thumbs/CH03_UN08_La_Rocca3.md.jpg -------------------------------------------------------------------------------- /readme/thumbs/CH04_UN04_La_Rocca3.md.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlarocca/grokking_data_structures/9c78516f70e44ddc910778a059e6a5d242fcc447/readme/thumbs/CH04_UN04_La_Rocca3.md.jpg -------------------------------------------------------------------------------- /readme/thumbs/CH05_UN05_La_Rocca3.md.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlarocca/grokking_data_structures/9c78516f70e44ddc910778a059e6a5d242fcc447/readme/thumbs/CH05_UN05_La_Rocca3.md.jpg -------------------------------------------------------------------------------- /readme/thumbs/CH06_UN04_La_Rocca3.md.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlarocca/grokking_data_structures/9c78516f70e44ddc910778a059e6a5d242fcc447/readme/thumbs/CH06_UN04_La_Rocca3.md.jpg -------------------------------------------------------------------------------- /readme/thumbs/CH06_UN11_La_Rocca3.md.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlarocca/grokking_data_structures/9c78516f70e44ddc910778a059e6a5d242fcc447/readme/thumbs/CH06_UN11_La_Rocca3.md.jpg -------------------------------------------------------------------------------- /readme/thumbs/CH06_UN12_La_Rocca3.md.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlarocca/grokking_data_structures/9c78516f70e44ddc910778a059e6a5d242fcc447/readme/thumbs/CH06_UN12_La_Rocca3.md.jpg -------------------------------------------------------------------------------- /readme/thumbs/CH07_UN07_La_Rocca3.md.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlarocca/grokking_data_structures/9c78516f70e44ddc910778a059e6a5d242fcc447/readme/thumbs/CH07_UN07_La_Rocca3.md.jpg -------------------------------------------------------------------------------- /readme/thumbs/CH08_UN03_La_Rocca3.md.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlarocca/grokking_data_structures/9c78516f70e44ddc910778a059e6a5d242fcc447/readme/thumbs/CH08_UN03_La_Rocca3.md.jpg -------------------------------------------------------------------------------- /readme/thumbs/CH09_UN04_La_Rocca3.md.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlarocca/grokking_data_structures/9c78516f70e44ddc910778a059e6a5d242fcc447/readme/thumbs/CH09_UN04_La_Rocca3.md.jpg -------------------------------------------------------------------------------- /readme/thumbs/CH10_UN14_La_Rocca3.md.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlarocca/grokking_data_structures/9c78516f70e44ddc910778a059e6a5d242fcc447/readme/thumbs/CH10_UN14_La_Rocca3.md.jpg -------------------------------------------------------------------------------- /readme/thumbs/CH11_UN01_La_Rocca3.md.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlarocca/grokking_data_structures/9c78516f70e44ddc910778a059e6a5d242fcc447/readme/thumbs/CH11_UN01_La_Rocca3.md.jpg -------------------------------------------------------------------------------- /readme/thumbs/CH12_UN08_La_Rocca3.md.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlarocca/grokking_data_structures/9c78516f70e44ddc910778a059e6a5d242fcc447/readme/thumbs/CH12_UN08_La_Rocca3.md.jpg -------------------------------------------------------------------------------- /readme/thumbs/CH13_UN06_La_Rocca3.md.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlarocca/grokking_data_structures/9c78516f70e44ddc910778a059e6a5d242fcc447/readme/thumbs/CH13_UN06_La_Rocca3.md.jpg -------------------------------------------------------------------------------- /readme/thumbs/LaRocca-MEAP-HI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlarocca/grokking_data_structures/9c78516f70e44ddc910778a059e6a5d242fcc447/readme/thumbs/LaRocca-MEAP-HI.png --------------------------------------------------------------------------------