├── .gitignore ├── README.md ├── assignments ├── GapBuffer │ ├── CMakeLists.txt │ ├── README.md │ ├── doc │ │ └── Short Assignment 3 - Gap Buffer.pdf │ ├── gap_buffer.h │ └── gap_buffer_test.cc ├── HashMap │ ├── CMakeLists.txt │ ├── README.md │ ├── doc │ │ ├── HashMap_Starter │ │ │ ├── hashmap.cpp │ │ │ ├── hashmap.h │ │ │ └── hashmap_iterator.h │ │ └── HashMap_doc.pdf │ ├── hashmap.cpp │ ├── hashmap.h │ ├── hashmap_iterator.h │ ├── hashmap_perf.cpp │ ├── hashmap_test.cpp │ └── test_settings.h └── KDTree │ ├── CMakeLists.txt │ ├── README.md │ ├── bounded_priority_queue.h │ ├── doc │ └── 005_assignment_3_kdtree.pdf │ ├── kd_tree.h │ ├── kd_tree_test.cc │ └── point.h ├── slides ├── Spring2023 │ ├── lec12_special_member_functions_s23.pdf │ ├── lecture10_functions_and_lambdas_s23.pdf │ ├── lecture11_operators_s23.pdf │ ├── lecture13_move_semantics_s23.pdf │ ├── lecture14_typesafety_s23.pdf │ ├── lecture16_special_topics_s23.pdf │ ├── lecture1_welcome_s23.pdf │ ├── lecture2_typesandstructs_s23.pdf │ ├── lecture3_initandref_s23.pdf │ ├── lecture4_streams_s23.pdf │ ├── lecture5_containers_s23.pdf │ ├── lecture6_iterators_and_pointers_s23.pdf │ ├── lecture7_classes_s23.pdf │ ├── lecture8_templateclassesconst_s23.pdf │ └── lecture9_template_functions_s23.pdf └── Winter2021 │ ├── WL10_Temp_classes.pdf │ ├── WL11_Const.pdf │ ├── WL12_Operators.pdf │ ├── WL13_SMF.pdf │ ├── WL14-Move.pdf │ ├── WL15_RAII.pdf │ ├── WL16-Wrapup.pdf │ ├── WL2-Structures.pdf │ ├── WL4_Streams.pdf │ ├── WL5_Containers.pdf │ ├── WL6_Iterators.pdf │ ├── WL7_Templates.pdf │ ├── WL8_Functions.pdf │ ├── WL9-STL-Summary.pdf │ ├── WLecture1_intro.pdf │ ├── WLecture_3_Init_and_Ref.pdf │ └── Welcome to C++!.pdf ├── solutions ├── GapBuffer │ ├── CMakeLists.txt │ ├── gap_buffer.h │ └── gap_buffer_test.cc ├── HashMap │ ├── CMakeLists.txt │ ├── answers.md │ ├── hashmap.cpp │ ├── hashmap.h │ ├── hashmap_iterator.h │ ├── hashmap_perf.cpp │ ├── hashmap_test.cpp │ └── test_settings.h └── KDTree │ ├── CMakeLists.txt │ ├── bounded_priority_queue.h │ ├── kd_tree.h │ ├── kd_tree_test.cc │ └── point.h └── textbooks └── full_course_reader.pdf /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .vscode 3 | *.zip -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Self-Learning Materials of CS106L 2 | 3 | This repository collects and organizes learning materials of CS106L, a C++ programming course taught at Stanford University. The course delves into many useful and essential features of modern C++ and will help you understand C++ more in depth. In specific, this repo contains lecture slides, textbook, starter codes and testbenches of programming assignments. 4 | 5 | ```shell 6 | . 7 | ├── assignments # documents and starter codes of programming assigments 8 | │ ├── GapBuffer 9 | │ ├── HashMap 10 | │ └── KDTree 11 | ├── README.md 12 | ├── slides # lecture slides of Sp23 and Win21 semesters 13 | │ ├── Spring2023 14 | │ └── Winter2021 15 | ├── solutions # solution codes of three programming assignments 16 | │ ├── GapBuffer 17 | │ ├── HashMap 18 | │ └── KDTree 19 | └── textbooks # textbook for this course 20 | └── full_course_reader.pdf 21 | ``` 22 | 23 | # Course Overview 24 | This course discusses the following topics of C++ programming: 25 | - Type and Struct 26 | - Uniform Initialization 27 | - Reference 28 | - Streams 29 | - Containers 30 | - Iterators 31 | - Function Templates 32 | - Lambda Function 33 | - Template Class 34 | - Const-Correctness 35 | - Operator Overloading 36 | - Special Member Function 37 | - Move Semantics 38 | - Resource Acquisition Is Initialization 39 | 40 | For each topic, this repo includes two versions of lecture slides: one from Spring 2023 semester and the other comes from Winter 2022 semester. These two versions cover similar aspects of each topic but may illustrate them in different ways. And you can refer to both of them when learning a specific topic. 41 | 42 | # Programing Assignments 43 | Programming assignments are the most important and valuable part of this course, which guide you to create STL-compliant container classes from scratch. The implementation of these classes refers to most C++ grammars and features introduced in the lecture. By completing these assignments, you will better understand and master what taught in this class and improve your C++ programming skills. 44 | 45 | This repo has collected programming assignments released from three different semesters of this course, including HashMap, KDTree and GapBuffer. Each assignment is accompanied by comprehensive materials, including documents, starter codes, benchmarks, build scripts, and solutions. 46 | 47 | - [GapBuffer](./assignments/GapBuffer/): sequential container optimized for fast inserttion and deletion at any position; 48 | - [KDTree](./assignments/KDTree/): k-dimension binary search tree for efficient storage and query of multi-dimension data; 49 | - [HashMap](./assignments/HashMap): associative container similiar to the [unordered_map](https://en.cppreference.com/w/cpp/container/unordered_map) provided in STL; 50 | 51 | The original versions of these programming assignments were built based on [QtCreator](https://www.qt.io/product/development-tools), which requires tedious and cumbersome process to setup development environment for self-learners. This repo has refactored the build and test process of each assignment using [CMake](https://cmake.org/) and [GoogleTest](https://github.com/google/googletest) for the convenience of development on Linux machines. And you only need to ensure that your Linux machine has installed CMake and C++ compiler before getting started with these assignments. To setup development environemnt on Ubuntu systems, you may refer to following commands: 52 | 53 | ```shell 54 | sudo apt-get update 55 | sudo apt-get install build-essential libssl-dev cmake -y 56 | ``` 57 | 58 | # Related Resources 59 | All contents in this repo are sourced from online resources inclduing: 60 | - CS106L Winter 2018: https://web.stanford.edu/class/archive/cs/cs106l/cs106l.1184/assignments.html 61 | - CS106L Winter 2020: https://web.stanford.edu/class/archive/cs/cs106l/cs106l.1204/index.html 62 | - CS106L Spring 2022: https://web.stanford.edu/class/archive/cs/cs106l/cs106l.1226/ 63 | - CS106L Spring 2023: https://web.stanford.edu/class/cs106l/ 64 | - Other GitHub Repos: https://github.com/PKUFlyingPig/CS106L 65 | -------------------------------------------------------------------------------- /assignments/GapBuffer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | cmake_minimum_required(VERSION 3.14) 3 | project(gap_buffer) 4 | 5 | # GoogleTest requires at least C++14 6 | set(CMAKE_CXX_STANDARD 14) 7 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 8 | 9 | include(FetchContent) 10 | FetchContent_Declare( 11 | googletest 12 | URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip 13 | ) 14 | # For Windows: Prevent overriding the parent project's compiler/linker settings 15 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 16 | FetchContent_MakeAvailable(googletest) 17 | 18 | enable_testing() 19 | 20 | add_executable( 21 | gap_buffer_test 22 | gap_buffer_test.cc 23 | ) 24 | target_link_libraries( 25 | gap_buffer_test 26 | GTest::gtest_main 27 | ) 28 | 29 | include(GoogleTest) 30 | gtest_discover_tests(gap_buffer_test) -------------------------------------------------------------------------------- /assignments/GapBuffer/README.md: -------------------------------------------------------------------------------- 1 | # GapBuffer 2 | In this programming assignment, you will implement a sequential container class, which is optimized for fast inserttion and deletion operations at any position. For detailed description of HashMap and guidance on this assignment, you can refer to this [document](./doc/Short%20Assignment%203%20-%20Gap%20Buffer.pdf). To build and test your codes, you can refer to the following commands. You can selectively enable testcases corresponding to the functionalities you have already implemented by changing macro definitions in the header of [gap_buffer_test.cc](./gap_buffer_test.cc). 3 | 4 | ```shell 5 | mkdir -p build && cd build 6 | cmake .. 7 | make 8 | ./gap_buffer_test 9 | ``` -------------------------------------------------------------------------------- /assignments/GapBuffer/doc/Short Assignment 3 - Gap Buffer.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/assignments/GapBuffer/doc/Short Assignment 3 - Gap Buffer.pdf -------------------------------------------------------------------------------- /assignments/GapBuffer/gap_buffer.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef GAPBUFFER_H 3 | #define GAPBUFFER_H 4 | #include 5 | #include // for cout in debug 6 | #include 7 | #include // for stringstreams 8 | #include // for unique_ptr 9 | 10 | const int kDefaultSize = 10; 11 | 12 | // forward declaration for the GapBufferIterator class 13 | template 14 | class GapBufferIterator; 15 | 16 | // declaration for the GapBuffer class 17 | template 18 | class GapBuffer { 19 | public: 20 | friend class GapBufferIterator; 21 | 22 | using value_type = T; 23 | using size_type = size_t; 24 | using difference_type = ptrdiff_t; 25 | using reference = value_type&; 26 | using const_reference = const value_type&; 27 | using pointer = value_type*; 28 | using iterator = GapBufferIterator; 29 | 30 | explicit GapBuffer(); 31 | explicit GapBuffer(size_type count, const value_type& val = value_type()); 32 | ~GapBuffer(); 33 | GapBuffer(std::initializer_list init); 34 | GapBuffer(const GapBuffer& other); 35 | GapBuffer(GapBuffer&& other); 36 | GapBuffer& operator=(const GapBuffer& rhs); 37 | GapBuffer& operator=(GapBuffer&& rhs); 38 | 39 | void insert_at_cursor(const_reference element); 40 | void insert_at_cursor(value_type&& element); 41 | template 42 | void emplace_at_cursor(Args&&... args); // optional 43 | void delete_at_cursor(); 44 | reference get_at_cursor(); 45 | const_reference get_at_cursor() const; 46 | reference operator[](size_type pos); 47 | const_reference operator[](size_type pos) const; 48 | reference at(size_type pos); 49 | const_reference at(size_type pos) const; 50 | void move_cursor(int num); 51 | void reserve(size_type new_size); 52 | size_type size() const; 53 | size_type cursor_index() const; 54 | bool empty() const; 55 | void debug() const; 56 | 57 | iterator begin(); 58 | iterator end(); 59 | iterator cursor(); 60 | 61 | private: 62 | size_type _logical_size; // uses external_index 63 | size_type _buffer_size; // uses array_index 64 | size_type _cursor_index; // uses array_index 65 | size_type _gap_size; 66 | pointer _elems; // uses array_index 67 | 68 | size_type to_external_index(size_type array_index) const; 69 | size_type to_array_index(size_type external_index) const; 70 | void move_to_left_of_buffer(size_type num); 71 | }; 72 | 73 | // Class declaration of the GapBufferIterator class 74 | template 75 | class GapBufferIterator : public std::iterator { 76 | public: 77 | friend class GapBuffer; 78 | using value_type = T; 79 | using size_type = size_t; 80 | using difference_type = ptrdiff_t; 81 | using reference = value_type&; 82 | using iterator = GapBufferIterator; 83 | 84 | reference operator*(); 85 | iterator& operator++(); 86 | iterator operator++(int); 87 | iterator& operator--(); 88 | iterator operator--(int); 89 | 90 | // the operators below are implemented as non-friend non-members 91 | // iterator operator+(const iterator& lhs, size_type diff); 92 | // iterator operator-(const iterator& lhs, size_type diff); 93 | // iterator operator+(size_type diff, const iterator& rhs); 94 | 95 | /* we implemented these for you. don't change these */ 96 | friend bool operator==(const iterator& lhs, const iterator& rhs) { return lhs._index == rhs._index; } 97 | friend bool operator!=(const iterator& lhs, const iterator& rhs) { return !(lhs == rhs); } 98 | friend difference_type operator-(const iterator& lhs, const iterator& rhs) { return lhs._index - rhs._index; } 99 | iterator& operator+=(size_type diff) { _index += diff; return *this; } 100 | iterator& operator-=(size_type diff) { _index -= diff; return *this; } 101 | friend bool operator>(const iterator& lhs, const iterator& rhs) { return lhs._index < rhs._index; } 102 | friend bool operator<(const iterator& lhs, const iterator& rhs) { return lhs._index > rhs._index; } 103 | friend bool operator<=(const iterator& lhs, const iterator& rhs) { return !(lhs > rhs); } 104 | friend bool operator>=(const iterator& lhs, const iterator& rhs) { return !(lhs < rhs); } 105 | reference operator[](size_t index) { return *(*this + index); } 106 | 107 | private: 108 | GapBuffer* _pointee; 109 | size_t _index; 110 | GapBufferIterator(GapBuffer* pointee, size_t index) : _pointee(pointee), _index(index) {} 111 | }; 112 | 113 | // Part 1: basic functions 114 | template 115 | GapBuffer::GapBuffer(){ 116 | // TODO: Implement the default constructor (~5 lines long) 117 | // use member initialization list 118 | } 119 | 120 | template 121 | GapBuffer::GapBuffer(size_type count, const value_type& val) { 122 | // TODO: Implement the fill constructor (~6 lines long) 123 | // use member initialization list 124 | } 125 | 126 | template 127 | void GapBuffer::insert_at_cursor(const_reference element) { 128 | // TODO: implement this function (~7 lines long) 129 | // Hint: call reserve() to resize 130 | } 131 | 132 | template 133 | void GapBuffer::delete_at_cursor() { 134 | // TODO: implement this function (~4 lines long) 135 | } 136 | 137 | template 138 | typename GapBuffer::reference GapBuffer::get_at_cursor() { 139 | // TODO: implement this function (~1 line long) 140 | // Hint: check out the indexing helper functions we provide 141 | // Be sure to use the static_cast/const_cast trick here after implementing the const-version. 142 | } 143 | 144 | template 145 | typename GapBuffer::reference GapBuffer::at(size_type pos) { 146 | // TODO: implement this function (~1 line long) 147 | // Hint: at should do error-checking! 148 | } 149 | 150 | template 151 | typename GapBuffer::size_type GapBuffer::size() const { 152 | // TODO: implement this function (~1 line long) 153 | } 154 | 155 | template 156 | typename GapBuffer::size_type GapBuffer::cursor_index() const { 157 | // TODO: implement this function (~1 line long) 158 | // Hint: check out the indexing helper functions we provide 159 | } 160 | 161 | template 162 | bool GapBuffer::empty() const { 163 | // TODO: implement this function (~1 line long) 164 | } 165 | 166 | // Part 2: const-correctness 167 | 168 | template 169 | typename GapBuffer::const_reference GapBuffer::get_at_cursor() const { 170 | // TODO: implement this function (~1 line long) 171 | // Hint: check out the indexing helper functions we provide 172 | // Be sure to use the static_cast/const_cast trick in the non-const version. 173 | } 174 | 175 | template 176 | typename GapBuffer ::const_reference GapBuffer::at(size_type pos) const { 177 | // TODO: implement this function (~1 line long) 178 | // Hint: check out the indexing helper functions we provide 179 | // Be sure to use the static_cast/const_cast trick in the non-const version. 180 | } 181 | 182 | // Part 3: operator overloading 183 | template 184 | typename GapBuffer::reference GapBuffer::operator[](size_type pos) { 185 | // TODO: implement this function (~1 line long) 186 | // Hint: check out the indexing helper functions we provide 187 | // Be sure to use the static_cast/const_cast trick here after implementing the const-version. 188 | } 189 | 190 | template 191 | typename GapBuffer::const_reference GapBuffer::operator[](size_type pos) const { 192 | // TODO: implement this function (~1 line long) 193 | // Hint: check out the indexing helper functions we provide 194 | // Be sure to use the static_cast/const_cast trick in the non-const version. 195 | } 196 | 197 | template 198 | std::ostream& operator<<(std::ostream& os, const GapBuffer& buf) { 199 | // TODO: implement this operator (~18 lines long) 200 | } 201 | 202 | template 203 | bool operator==(const GapBuffer& lhs, const GapBuffer& rhs) { 204 | // TODO: implement this operator (~1 line long) 205 | // Hint: std::equal can be used after you implement iterators 206 | } 207 | 208 | template 209 | bool operator!=(const GapBuffer& lhs, const GapBuffer& rhs) { 210 | // TODO: implement this operator (~1 line long) 211 | // Hint: how are == and != related? 212 | } 213 | 214 | template 215 | bool operator<(const GapBuffer& lhs, const GapBuffer& rhs) { 216 | // TODO: implement this operator (~3 lines long) 217 | // Hint: std::lexicographical_compare can be used after you implement iterators. 218 | 219 | // Hint: if you get warnings about const_iterator, you can try using const_cast 220 | // as a hack to cast away the const-ness of your parameter. 221 | // auto& lhs_nonconst = const_cast&>(lhs); 222 | // auto& rhs_nonconst = const_cast&>(lhs); 223 | // use lhs_nonconst.begin(), etc. 224 | } 225 | 226 | template 227 | bool operator>(const GapBuffer& lhs, const GapBuffer& rhs) { 228 | // TODO: implement this operator (~1 line long) 229 | } 230 | 231 | template 232 | bool operator<=(const GapBuffer& lhs, const GapBuffer& rhs) { 233 | // TODO: implement this operator (~1 line long) 234 | } 235 | 236 | template 237 | bool operator>=(const GapBuffer& lhs, const GapBuffer& rhs) { 238 | // TODO: implement this operator (~1 line long) 239 | } 240 | 241 | // Part 4: turn everything into a template! 242 | 243 | 244 | // Part 5: Implement iterators 245 | template 246 | typename GapBufferIterator::reference GapBufferIterator::operator*() { 247 | // TODO: implement this operator (~1 line long) 248 | } 249 | 250 | template 251 | GapBufferIterator& GapBufferIterator::operator++() { 252 | // TODO: implement this prefix operator (~2 lines long) 253 | } 254 | 255 | template 256 | GapBufferIterator GapBufferIterator::operator++(int) { 257 | // TODO: implement this postfix operator (~3 lines long) 258 | } 259 | 260 | template 261 | GapBufferIterator& GapBufferIterator::operator--() { 262 | // TODO: implement this prefix operator (~2 lines long) 263 | } 264 | 265 | template 266 | GapBufferIterator GapBufferIterator::operator--(int) { 267 | // TODO: implement this postfix operator (~3 lines long) 268 | } 269 | 270 | template 271 | GapBufferIterator operator+(const GapBufferIterator& lhs, 272 | typename GapBufferIterator::size_type diff) { 273 | // TODO: implement this operator (~3 lines long) 274 | // Note: this operator is not a friend of the GapBufferIterator class 275 | // Hint: write the operator in terms of += 276 | } 277 | 278 | template 279 | GapBufferIterator operator+(typename GapBufferIterator::size_type diff, 280 | const GapBufferIterator& rhs) { 281 | // TODO: implement this operator (~1 line long) 282 | // Note: this operator is not a friend of the GapBufferIterator class 283 | // Hint: write the operator in terms of the operator+ you wrote above. 284 | } 285 | 286 | template 287 | GapBufferIterator operator-(const GapBufferIterator& lhs, 288 | typename GapBufferIterator::size_type diff) { 289 | // TODO: implement this operator (~3 lines long) 290 | // Note: this operator is not a friend of the GapBufferIterator class 291 | // Hint: write the operator in terms of -= 292 | } 293 | 294 | // The functions that are part of the GapBuffer class is provided for you! 295 | template 296 | typename GapBuffer::iterator GapBuffer::begin() { 297 | return iterator(this, 0); 298 | } 299 | 300 | template 301 | typename GapBuffer::iterator GapBuffer::end() { 302 | return iterator(this, _logical_size); 303 | } 304 | 305 | template 306 | typename GapBuffer::iterator GapBuffer::cursor() { 307 | return iterator(this, _cursor_index); 308 | } 309 | 310 | // Part 6: Constructors and assignment 311 | 312 | template 313 | GapBuffer::~GapBuffer() { 314 | // TODO: implement this destructor (~1 line long) 315 | } 316 | template 317 | GapBuffer::GapBuffer(std::initializer_list init) { 318 | // TODO: implement this initializer list constructor (~2 lines long) 319 | } 320 | 321 | template 322 | GapBuffer::GapBuffer(const GapBuffer& other) { 323 | // TODO: implement this copy constructor (~4 lines long) 324 | // use member initialization list! 325 | } 326 | 327 | template 328 | GapBuffer& GapBuffer::operator=(const GapBuffer& rhs) { 329 | // TODO: implement this copy assignment operator (~8 lines long) 330 | } 331 | 332 | // Part 7: Move semantics 333 | template 334 | GapBuffer::GapBuffer(GapBuffer&& other) { 335 | // TODO: implement this move constructor (~4 lines long) 336 | // use initializer list! 337 | 338 | // Hint: if you get warnings about const_iterator, you can try using const_cast 339 | // as a hack to cast away the const-ness of your parameter. 340 | // auto& other_nonconst = const_cast&>(other); 341 | // use other.begin(), etc. 342 | } 343 | 344 | template 345 | GapBuffer& GapBuffer::operator=(GapBuffer&& rhs) { 346 | // TODO: implement this move assignment operator (~7 lines long) 347 | 348 | // Hint: if you get warnings about const_iterator, you can try using const_cast 349 | // as a hack to cast away the const-ness of your parameter. 350 | // auto& rhs_nonconst = const_cast&>(rhs); 351 | // use rhs.begin(), etc. 352 | } 353 | 354 | template 355 | void GapBuffer::insert_at_cursor(value_type&& element) { 356 | // TODO: implement this insert function (takes in an r-value) (~7 lines long) 357 | insert_at_cursor(element); // by default, calls the l-value version above 358 | // when you are ready to implement, remove the insert_at_cursor call. 359 | } 360 | 361 | // Part 8: Make your code RAII-compliant - change the code throughout 362 | 363 | // optional: 364 | template 365 | template 366 | void GapBuffer::emplace_at_cursor(Args&&... args) { 367 | // TODO: optional: implement function 368 | // remember to perfectly forward the arguments to the constructor of T. 369 | } 370 | 371 | 372 | // We've implemented the following functions for you. 373 | // However...they do use raw pointers, so you might want to turn them into smart pointers! 374 | template 375 | void GapBuffer::move_cursor(int delta) { 376 | int new_index = _cursor_index + delta; 377 | if (new_index < 0 || new_index > static_cast(_buffer_size)) { 378 | throw std::string("move_cursor: delta moves cursor out of bounds"); 379 | } 380 | if (delta > 0) { 381 | auto begin_move = _elems + _cursor_index + _gap_size; 382 | auto end_move = begin_move + delta; 383 | auto destination = _elems + _cursor_index; 384 | std::move(begin_move, end_move, destination); 385 | } else { 386 | auto end_move = _elems + _cursor_index; 387 | auto begin_move = end_move + delta; 388 | auto* destination = _elems + _cursor_index + _gap_size + delta; 389 | std::move(begin_move, end_move, destination); 390 | } 391 | _cursor_index += delta; 392 | } 393 | 394 | template 395 | void GapBuffer::reserve(size_type new_size) { 396 | if (_logical_size >= new_size) return; 397 | auto new_elems = new T[new_size]; 398 | std::move(_elems, _elems + _cursor_index, new_elems); 399 | size_t new_gap_size = new_size - _logical_size; 400 | std::move(_elems + _buffer_size - _logical_size + _cursor_index, 401 | _elems + _buffer_size, 402 | new_elems + _cursor_index + new_gap_size); 403 | _buffer_size = new_size; 404 | delete [] _elems; 405 | _elems = std::move(new_elems); 406 | _gap_size = new_gap_size; 407 | } 408 | 409 | template 410 | void GapBuffer::debug() const { 411 | std::cout << "["; 412 | for (size_t i = 0; i < _buffer_size; ++i) { 413 | if (i == _cursor_index) { 414 | std::cout << "|"; 415 | } else { 416 | std::cout << " "; 417 | } 418 | if (i >= _cursor_index && i < _cursor_index + _gap_size) { 419 | std::cout << "*"; 420 | } else { 421 | std::cout << _elems[i]; 422 | } 423 | } 424 | std::cout << (_cursor_index == _buffer_size ? "|" : " "); 425 | std::cout << "]" << std::endl; 426 | } 427 | 428 | template 429 | typename GapBuffer::size_type GapBuffer::to_external_index(size_type array_index) const { 430 | if (array_index < _cursor_index) { 431 | return array_index; 432 | } else if (array_index >= _cursor_index + _gap_size){ 433 | return array_index - _cursor_index; 434 | } else { 435 | throw ("to_external_index: array_index is out of bounds!"); 436 | } 437 | } 438 | 439 | template 440 | typename GapBuffer::size_type GapBuffer::to_array_index(size_type external_index) const { 441 | if (external_index < _cursor_index) { 442 | return external_index; 443 | } else { 444 | return external_index + _gap_size; 445 | } 446 | } 447 | 448 | 449 | #endif // GAPBUFFER_H 450 | -------------------------------------------------------------------------------- /assignments/HashMap/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | cmake_minimum_required(VERSION 3.14) 3 | project(hashmap) 4 | 5 | 6 | set(CMAKE_CXX_STANDARD 17) 7 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 8 | SET(CMAKE_BUILD_TYPE "Debug") 9 | 10 | SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g2 -ggdb") 11 | SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -Wall") 12 | 13 | include(FetchContent) 14 | FetchContent_Declare( 15 | googletest 16 | URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip 17 | ) 18 | # For Windows: Prevent overriding the parent project's compiler/linker settings 19 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 20 | FetchContent_MakeAvailable(googletest) 21 | 22 | enable_testing() 23 | 24 | add_executable( 25 | hashmap_test 26 | hashmap_test.cpp 27 | ) 28 | target_link_libraries( 29 | hashmap_test 30 | GTest::gtest_main 31 | ) 32 | 33 | add_executable( 34 | hashmap_perf 35 | hashmap_perf.cpp 36 | ) 37 | 38 | target_link_libraries( 39 | hashmap_perf 40 | GTest::gtest_main 41 | ) 42 | 43 | include(GoogleTest) 44 | gtest_discover_tests(hashmap_test) -------------------------------------------------------------------------------- /assignments/HashMap/README.md: -------------------------------------------------------------------------------- 1 | # HashMap 2 | In this programming assignment, you will implement an associative container class which has similar functionalities as [unordered_map](https://en.cppreference.com/w/cpp/container/unordered_map) provided in the STL. For detailed description of HashMap and guidance on this assignment, you can refer to this [document](./doc/HashMap_doc.pdf). As described in the document, for the original version of this assignment, you are given an almost complete implementation of HashMap and only need to do some modifications and add few methods. But it's highly recommended that you need to implement each method in the empty [source file](./hashmap.cpp) from scratch after reading and understanding HashMap implementation provided in the original [starter codes](./doc/HashMap_Starter/). 3 | 4 | To build and test your codes, you can refer to following commands. And you can selectively enable testcases corresponding to the functionalities you have already implemented by changing macro definitions in the [test_settings.h](./test_settings.h). 5 | ```shell 6 | mkdir -p build && cd build 7 | cmake .. 8 | make 9 | # functionality test 10 | ./hashmap_test 11 | # performance test 12 | ./hashmap_perf 13 | ``` 14 | -------------------------------------------------------------------------------- /assignments/HashMap/doc/HashMap_Starter/hashmap.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Assignment 2: HashMap template implementation (STARTER CODE) 3 | * TODO: write a comment here. 4 | */ 5 | 6 | #include "hashmap.h" 7 | 8 | template 9 | HashMap::HashMap() : 10 | HashMap{kDefaultBuckets} { } 11 | 12 | template 13 | HashMap::HashMap(size_t bucket_count, const H& hash) : 14 | _size{0}, 15 | _hash_function{hash}, 16 | _buckets_array(bucket_count, nullptr) { } 17 | 18 | template 19 | HashMap::~HashMap() { 20 | clear(); 21 | } 22 | 23 | template 24 | inline size_t HashMap::size() { 25 | return _size; 26 | } 27 | 28 | template 29 | inline bool HashMap::empty() { 30 | return size() == 0; 31 | } 32 | 33 | template 34 | inline float HashMap::load_factor() { 35 | return static_cast(size())/bucket_count(); 36 | }; 37 | 38 | template 39 | inline size_t HashMap::bucket_count() const{ 40 | return _buckets_array.size(); 41 | }; 42 | 43 | template 44 | M& HashMap::at(const K& key) { 45 | auto [prev, node_found] = find_node(key); 46 | if (node_found == nullptr) { 47 | throw std::out_of_range("HashMap::at: key not found"); 48 | } 49 | return node_found->value.second; 50 | } 51 | 52 | template 53 | bool HashMap::contains(const K& key) { 54 | return find_node(key).second != nullptr; 55 | } 56 | 57 | template 58 | void HashMap::clear() { 59 | for (auto& curr : _buckets_array) { 60 | while (curr != nullptr) { 61 | curr = curr->next; 62 | } 63 | } 64 | _size = 0; 65 | } 66 | 67 | template 68 | typename HashMap::iterator HashMap::find(const K& key) { 69 | return make_iterator(find_node(key).second); 70 | } 71 | 72 | template 73 | std::pair::iterator, bool> HashMap::insert(const value_type& value) { 74 | const auto& [key, mapped] = value; 75 | auto [prev, node_to_edit] = find_node(key); 76 | size_t index = _hash_function(key) % bucket_count(); 77 | 78 | if (node_to_edit != nullptr) { 79 | return {make_iterator(node_to_edit), false}; 80 | } 81 | 82 | auto temp = new node(value, _buckets_array[index]); 83 | _buckets_array[index] = temp; 84 | 85 | ++_size; 86 | return {make_iterator(temp), true}; 87 | } 88 | 89 | template 90 | typename HashMap::node_pair HashMap::find_node(const K& key) const { 91 | size_t index = _hash_function(key) % bucket_count(); 92 | node* curr = _buckets_array[index]; 93 | node* prev = nullptr; // if first node is the key, return {nullptr, front} 94 | while (curr != nullptr) { 95 | const auto& [found_key, found_mapped] = curr->value; 96 | if (found_key == key) { 97 | return {prev, curr}; 98 | } 99 | prev = curr; 100 | curr = curr->next; 101 | } 102 | return {nullptr, nullptr}; // key not found at all. 103 | } 104 | 105 | template 106 | typename HashMap::iterator HashMap::begin() { 107 | size_t index = first_not_empty_bucket(); 108 | if (index == bucket_count()) { 109 | return end(); 110 | } 111 | return make_iterator(_buckets_array[index]); 112 | } 113 | 114 | template 115 | typename HashMap::const_iterator HashMap::begin() const { 116 | // This is called the static_cast/const_cast trick, which allows us to reuse 117 | // the non-const version of find to implement the const version. 118 | // The idea is to cast this so it's pointing to a non-const HashMap, which 119 | // calls the overload above (and prevent infinite recursion). 120 | // Also note that we are calling the conversion operator in the iterator class! 121 | return static_cast(const_cast*>(this)->begin()); 122 | } 123 | 124 | template 125 | typename HashMap::iterator HashMap::end() { 126 | return make_iterator(nullptr); 127 | } 128 | 129 | template 130 | size_t HashMap::first_not_empty_bucket() const { 131 | auto isNotNullptr = [ ](const auto& v){ 132 | return v != nullptr; 133 | }; 134 | 135 | auto found = std::find_if(_buckets_array.begin(), _buckets_array.end(), isNotNullptr); 136 | return found - _buckets_array.begin(); 137 | } 138 | 139 | template 140 | typename HashMap::iterator HashMap::make_iterator(node* curr) { 141 | if (curr == nullptr) { 142 | return {&_buckets_array, curr, bucket_count()}; 143 | } 144 | size_t index = _hash_function(curr->value.first) % bucket_count(); 145 | return {&_buckets_array, curr, index}; 146 | } 147 | 148 | template 149 | bool HashMap::erase(const K& key) { 150 | auto [prev, node_to_erase] = find_node(key); 151 | if (node_to_erase == nullptr) { 152 | return false; 153 | } 154 | size_t index = _hash_function(key) % bucket_count(); 155 | (prev ? prev->next : _buckets_array[index]) = node_to_erase->next; 156 | --_size; 157 | return true; 158 | } 159 | 160 | template 161 | typename HashMap::iterator HashMap::erase(typename HashMap::const_iterator pos) { 162 | erase(pos++->first); 163 | return make_iterator(pos._node); // unfortunately we need a regular iterator, not a const_iterator 164 | } 165 | 166 | template 167 | void HashMap::debug() { 168 | std::cout << std::setw(30) << std::setfill('-') << '\n' << std::setfill(' ') 169 | << "Printing debug information for your HashMap implementation\n" 170 | << "Size: " << size() << std::setw(15) << std::right 171 | << "Buckets: " << bucket_count() << std::setw(20) << std::right 172 | << "(load factor: " << std::setprecision(2) << load_factor() << ") \n\n"; 173 | 174 | for (size_t i = 0; i < bucket_count(); ++i) { 175 | std::cout << "[" << std::setw(3) << i << "]:"; 176 | node* curr = _buckets_array[i]; 177 | while (curr != nullptr) { 178 | const auto& [key, mapped] = curr->value; 179 | // next line will not compile if << not supported for K or M 180 | std::cout << " -> " << key << ":" << mapped; 181 | curr = curr->next; 182 | } 183 | std::cout << " /" << '\n'; 184 | } 185 | std::cout << std::setw(30) << std::setfill('-') << '\n' << std::setfill(' '); 186 | } 187 | 188 | template 189 | void HashMap::rehash(size_t new_bucket_count) { 190 | if (new_bucket_count == 0) { 191 | throw std::out_of_range("HashMap::rehash: new_bucket_count must be positive."); 192 | } 193 | 194 | std::vector new_buckets_array(new_bucket_count, nullptr); 195 | for (auto& curr : _buckets_array) { // short answer question is asking about this 'curr' 196 | while (curr != nullptr) { 197 | const auto& [key, mapped] = curr->value; 198 | size_t index = _hash_function(key) % new_bucket_count; 199 | 200 | auto temp = curr; 201 | curr = temp->next; 202 | temp->next = new_buckets_array[index]; 203 | new_buckets_array[index] = temp; 204 | } 205 | } 206 | _buckets_array = std::move(new_buckets_array); 207 | } 208 | 209 | template 210 | template 211 | HashMap::HashMap(InputIt first, InputIt last, size_t bucket_count, const H& hash) : 212 | HashMap(bucket_count, hash) { 213 | for (auto iter = first; iter != last; ++iter) { 214 | insert(*iter); 215 | } 216 | } 217 | 218 | template 219 | HashMap::HashMap(std::initializer_list init, size_t bucket_count, const H& hash) : 220 | HashMap{init.begin(), init.end(), bucket_count, hash} { 221 | } 222 | 223 | template 224 | M& HashMap::operator[](const K& key) { 225 | return insert({key, {}}).first->second; 226 | } 227 | 228 | template 229 | bool operator==(const HashMap& lhs, const HashMap& rhs) { 230 | return lhs.size() == rhs.size() && std::is_permutation(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); 231 | } 232 | 233 | template 234 | bool operator!=(const HashMap& lhs, const HashMap& rhs) { 235 | return !(lhs == rhs); 236 | } 237 | 238 | template 239 | std::ostream& operator<<(std::ostream& os, const HashMap& rhs) { 240 | std::ostringstream oss("{", std::ostringstream::ate); 241 | for (const auto& [key, value] : rhs) { 242 | oss << key << ":" << value << ", "; 243 | } 244 | std::string s = oss.str(); 245 | os << s.substr(0, s.length()-2) << "}"; 246 | return os; 247 | } 248 | 249 | /* Begin Milestone 2: Special Member Functions */ 250 | 251 | /* end student code */ 252 | -------------------------------------------------------------------------------- /assignments/HashMap/doc/HashMap_Starter/hashmap_iterator.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Assignment 2: HashMapIterator template interface and implementation 3 | * Created by Avery Wang (lecturer for 2019-2020 - awvry952@stanford.edu) 4 | * 5 | * DO NOT EDIT THIS FILE 6 | * For bug reports please email your current CS 106L lecturer. 7 | */ 8 | 9 | #ifndef HASHMAPITERATOR_H 10 | #define HASHMAPITERATOR_H 11 | 12 | #include // for std::forward_iterator_tag 13 | #include // for std::conditional_t 14 | 15 | // forward declaration for the HashMap class 16 | template class HashMap; 17 | 18 | /* 19 | * Template class for a HashMapIterator 20 | * 21 | * Map = the type of HashMap this class is an iterator for. 22 | * IsConst = whether this is a const_iterator class. 23 | * 24 | * Concept requirements: 25 | * - Map must be a valid class HashMap 26 | */ 27 | template 28 | class HashMapIterator { 29 | 30 | public: 31 | /* 32 | * This alias is very important. When dealing with const_iterator, the value_type is always const, and 33 | * that prevents the client from modifying the elements via a const_iterator. The meta-function 34 | * std::conditional_t changes the value_type (at compile-time) to a const one if IsConst is true. 35 | */ 36 | using value_type = std::conditional_t; 37 | 38 | /* 39 | * Public aliases for this iterator class. Important so STL functions like std::iterator_traits 40 | * can parse this class for important information, like its iterator category. 41 | */ 42 | using iterator_category = std::forward_iterator_tag; 43 | using difference_type = std::ptrdiff_t; 44 | using pointer = value_type*; 45 | using reference = value_type&; 46 | 47 | /* 48 | * Friend declarations so the HashMap class this iterator is for can access the attributes of its iterators. 49 | * Also, to make conversions between iterator and const_iterator easy, we declare the corresponding 50 | * iterator and const_iterators as friends. 51 | */ 52 | friend Map; 53 | friend HashMapIterator; 54 | friend HashMapIterator; 55 | 56 | /* 57 | * Conversion operator: converts any iterator (iterator or const_iterator) to a const_iterator. 58 | * 59 | * Usage: 60 | * iterator iter = map.begin(); 61 | * const_iterator c_iter = iter; // implicit conversion 62 | * 63 | * Implicit conversion operators are usually frowned upon, because they can cause 64 | * some unexpected behavior. This is a rare case where a conversion operator is 65 | * very convenient. Many of the iterator functions in the HashMap class are 66 | * secretly using this conversion. 67 | * 68 | * Note: conversion in the opposite direction (const to non-const) is not safe 69 | * because that gives the client write access the map itself is const. 70 | */ 71 | operator HashMapIterator() const { 72 | return HashMapIterator(_buckets_array, _node, _bucket); 73 | } 74 | 75 | /* 76 | * Dereference operators: defines the behavior of dereferencing an iterator. 77 | * 78 | * Usage: 79 | * auto [key, value] = *iter; 80 | * auto value = iter->second; 81 | * iter->second = 3; // if iter is a regular iterator (not a const_iterator) 82 | * 83 | * Note that dereferencing an invalid or end() iterator is undefined behavior. 84 | */ 85 | reference operator*() const; 86 | pointer operator->() const; 87 | 88 | /* 89 | * Increment operators: moves the iterator to point to the next element, or end(). 90 | * 91 | * Usage: 92 | * ++iter; // prefix 93 | * iter++; // postfix 94 | * 95 | * Note: the prefix operator first increments, and the returns a reference to itself (which is incremented). 96 | * The postfix operator returns a copy of the original iterator, while the iterator itself is incremented. 97 | * 98 | * Note that incrementing an invalid or end() iterator is undefined behavior. 99 | */ 100 | HashMapIterator& operator++(); 101 | HashMapIterator operator++(int); 102 | 103 | /* 104 | * Equality operator: decides if two iterators are pointing to the same element. 105 | * 106 | * Usage: 107 | * if (iter == map.end()) {...}; 108 | */ 109 | template 110 | friend bool operator==(const HashMapIterator& lhs, const HashMapIterator& rhs); 111 | 112 | /* 113 | * Inequality operator: decides if two iterators are pointing to different elements. 114 | * 115 | * Usage: 116 | * if (iter != map.end()) {...}; 117 | */ 118 | template 119 | friend bool operator!=(const HashMapIterator& lhs, const HashMapIterator& rhs); 120 | 121 | /* 122 | * Special member functions: we explicitly state that we want the default compiler-generated functions. 123 | * Here we are following the rule of zero. You should think about why that is correct. 124 | */ 125 | HashMapIterator(const HashMapIterator& rhs) = default; 126 | HashMapIterator& operator=(const HashMapIterator& rhs) = default; 127 | 128 | HashMapIterator(HashMapIterator&& rhs) = default; 129 | HashMapIterator& operator=(HashMapIterator&& rhs) = default; 130 | 131 | 132 | private: 133 | /* 134 | * Determines what is the type of the nodes that the HashMap is using. 135 | */ 136 | using node = typename Map::node; 137 | 138 | /* 139 | * Determines what is the type of the _buckets_array that the HashMap is using. 140 | */ 141 | using bucket_array_type = typename Map::bucket_array_type; 142 | 143 | /* 144 | * Instance variable: a pointer to the _buckets_array of the HashMap this iterator is for. 145 | */ 146 | bucket_array_type* _buckets_array; 147 | 148 | /* 149 | * Instance variable: pointer to the node that stores the element this iterator is currently pointing to. 150 | */ 151 | node* _node; 152 | 153 | /* 154 | * Instance variable: the index of the bucket that _node is in. 155 | */ 156 | size_t _bucket; 157 | 158 | /* 159 | * Private constructor for a HashMapIterator. 160 | * Friend classes can access the private members of class it is friends with, 161 | * so HashMap is able to call HashMapIterator's private constructor 162 | * (e.g, in begin()). We want the HashMapIterator constructor to be private 163 | * so a client can't randomly construct a HashMapIterator without asking for one 164 | * through the HashMap's interface. 165 | */ 166 | HashMapIterator(bucket_array_type* buckets_array, node* node, size_t bucket); 167 | 168 | }; 169 | 170 | 171 | template 172 | HashMapIterator::HashMapIterator(bucket_array_type* buckets_array, node* node, 173 | size_t bucket) : 174 | _buckets_array(buckets_array), 175 | _node(node), 176 | _bucket(bucket) { } 177 | 178 | template 179 | typename HashMapIterator::reference HashMapIterator::operator*() const { 180 | return _node->value; // _node can't be nullptr - that would be dereferencing end() 181 | } 182 | 183 | /* 184 | * operator-> is a bit of an odd-ball. When you write p->m, it is interpreted as (p.operator->())->m. 185 | * This means operator-> should return a pointer to the object that has the field m. 186 | * 187 | * For our usage, the client will write something like: iter->first, which is interpreted 188 | * as (iter.operator->())->first. This means operator-> should return a pointer to the 189 | * object that has the 'first' and 'second' field, i.e. a pointer to the std::pair/the value of that node. 190 | */ 191 | template 192 | typename HashMapIterator::pointer HashMapIterator::operator->() const { 193 | return &(_node->value); // _node can't be nullptr - that would be dereferencing end() 194 | } 195 | 196 | template 197 | HashMapIterator& HashMapIterator::operator++() { 198 | _node = _node->next; // _node can't be nullptr - that would be incrementing end() 199 | if (_node == nullptr) { // if you reach the end of the bucket, find the next bucket 200 | for (++_bucket; _bucket < _buckets_array->size(); ++_bucket) { 201 | _node = (*_buckets_array)[_bucket]; 202 | if (_node != nullptr) { 203 | return *this; 204 | } 205 | } 206 | } 207 | return *this; 208 | } 209 | 210 | template 211 | HashMapIterator HashMapIterator::operator++(int) { 212 | auto copy = *this; // calls the copy constructor to create copy 213 | ++(*this); 214 | return copy; 215 | } 216 | 217 | template 218 | bool operator==(const HashMapIterator& lhs, const HashMapIterator& rhs) { 219 | return lhs._node == rhs._node; 220 | } 221 | 222 | template 223 | bool operator!=(const HashMapIterator& lhs, const HashMapIterator& rhs) { 224 | return !(lhs == rhs); 225 | } 226 | 227 | #endif // HASHMAPITERATOR_H 228 | -------------------------------------------------------------------------------- /assignments/HashMap/doc/HashMap_doc.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/assignments/HashMap/doc/HashMap_doc.pdf -------------------------------------------------------------------------------- /assignments/HashMap/hashmap.cpp: -------------------------------------------------------------------------------- 1 | #include "hashmap.h" 2 | 3 | template 4 | HashMap::HashMap() : _size(0), _hash_function(H()), _buckets_array(kDefaultBuckets, nullptr) {}; 5 | 6 | template 7 | HashMap::HashMap(size_t bucket_count, const H& hash): 8 | _size(0), 9 | _hash_function(hash), 10 | _buckets_array(bucket_count, nullptr) {}; 11 | 12 | template 13 | HashMap::~HashMap() { 14 | // TODO: The implementation of contains should be modified 15 | } 16 | 17 | template 18 | inline size_t HashMap::size(){ 19 | return _size; 20 | } 21 | 22 | template 23 | inline bool HashMap::empty(){ 24 | return _size == 0; 25 | } 26 | 27 | template 28 | inline size_t HashMap::bucket_count() { 29 | return _buckets_array.size(); 30 | } 31 | 32 | template 33 | bool HashMap::contains(const K& key) { 34 | // TODO: The implementation of contains should be modified 35 | return false; 36 | } 37 | 38 | template 39 | M& HashMap::at(const K& key) { 40 | // TODO: The implementation of at should be modified 41 | throw std::runtime_error("at() function not implemented yet"); 42 | } -------------------------------------------------------------------------------- /assignments/HashMap/hashmap.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef HASHMAP_H 3 | #define HASHMAP_H 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "hashmap_iterator.h" 11 | 12 | /* 13 | * Template class for a HashMap 14 | * 15 | * K = key type 16 | * M = mapped type 17 | * H = hash function type used to hash a key; if not provided, defaults to std::hash 18 | * 19 | * Notes: When dealing with the Stanford libraries, we often call M the value 20 | * (and maps store key/value pairs). 21 | * 22 | * However, we name it M for mapped type to avoid confusion with value_type. 23 | * value_type is what the container is storing, which is a std::pair. 24 | * 25 | * All STL containers have a value_type and STL algorithms may use the value_type alias, so 26 | * we try our best to follow that convention. 27 | * 28 | * Example: 29 | * HashMap 30 | * This means K = key = std::string, 31 | * M = mapped = int, 32 | * value_type = std::pair. 33 | * 34 | * Concept requirements: 35 | * - H is function type that with function prototype size_t hash(const K& key). 36 | * The const and reference are not required, but key cannot be modified in function. 37 | * - K and M must be regular (copyable, default constructible, and equality comparable). 38 | */ 39 | template> 40 | class HashMap { 41 | public: 42 | /* 43 | * Alias for std::pair, used by the STL (such as in std::inserter) 44 | * As noted above, value_type is not the same as the mapped_type! 45 | * 46 | * Usage: 47 | * HashMap::value_type val = {3, "Avery"}; 48 | * map.insert(val); 49 | */ 50 | using value_type = std::pair; 51 | 52 | /* 53 | * Alias for the iterator type. Recall that it's impossible for an external client 54 | * to figure out the type of this iterator (you would've never guessed what the template 55 | * parameters are here), which is why the aliases are crucial. 56 | * 57 | * Usage: 58 | * HashMap::iterator iter = map.begin(); 59 | */ 60 | using iterator = HashMapIterator; 61 | 62 | /* 63 | * Alias for the const_iterator type. Recall that it's impossible for an external client 64 | * to figure out the type of this iterator (you would've never guessed what the template 65 | * parameters are here), which is why the aliases are crucial. 66 | * 67 | * Usage: 68 | * const auto& cmap = map; 69 | * HashMap::iterator iter = cmap.begin(); 70 | * 71 | * Notes: recall that you cannot modify the element a const_iterator is pointing to. 72 | * Also, a const_iterator is not a const iterator! 73 | */ 74 | using const_iterator = HashMapIterator; 75 | 76 | /* 77 | * Declares that the HashMapIterator class are friends of the HashMap class. 78 | * This allows the HashMapIterators to see the private members, which is 79 | * important because the iterator needs to know what element it is pointing to. 80 | */ 81 | friend class HashMapIterator; 82 | friend class HashMapIterator; 83 | 84 | /* 85 | * Default constructor 86 | * Creates an empty HashMap with default number of buckets and hash function. 87 | * 88 | * Usage: 89 | * HashMap map; 90 | * HashMap map{}; 91 | * 92 | * Complexity: O(B), B = number of buckets 93 | */ 94 | HashMap(); 95 | 96 | /* 97 | * Constructor with bucket_count and hash function as parameters. 98 | * 99 | * Creates an empty HashMap with a specified initial bucket_count and hash funciton. 100 | * If no hash function provided, default value of H is used. 101 | * 102 | * Usage: 103 | * HashMap(10) map; 104 | * HashMap map(10, [](const K& key) {return key % 10; }); 105 | * HashMap map{10, [](const K& key) {return key % 10; }}; 106 | * 107 | * Complexity: O(B), B = number of buckets 108 | * 109 | * Notes : what is explicit? Explicit specifies that a constructor 110 | * cannot perform implicit conversion on the parameters, or use copy-initialization. 111 | * That's good, as nonsense like the following won't compile: 112 | * 113 | * HashMap map(1.0); // double -> int conversion not allowed. 114 | * HashMap map = 1; // copy-initialization, does not compile. 115 | */ 116 | explicit HashMap(size_t bucket_count, const H& hash = H()); 117 | 118 | /* 119 | * Destructor. 120 | * 121 | * Usage: (implicitly called when HashMap goes out of scope) 122 | * 123 | * Complexity: O(N), N = number of elements 124 | */ 125 | ~HashMap(); 126 | 127 | /* 128 | * Returns the number of (K, M) pairs in the map. 129 | * 130 | * We declare this function inline since it is short and 131 | * the compiler can optimize by doing a direct inline substitution. 132 | * 133 | * Parameters: none 134 | * Return value: size_t 135 | * 136 | * Usage: 137 | * if (map.size() < 3) { ... } 138 | * 139 | * Complexity: O(1) (inlined because function is short) 140 | */ 141 | inline size_t size(); 142 | 143 | /* 144 | * Returns whether the HashMap is empty. 145 | * 146 | * Parameters: none 147 | * Return value: bool 148 | * 149 | * Usage: 150 | * if (map.empty()) { ... } 151 | * 152 | * Complexity: O(1) (inlined because function is short) 153 | */ 154 | inline bool empty(); 155 | 156 | /* 157 | * Returns the load_factor, defined as size/bucket_count. 158 | * 159 | * Parameters: none 160 | * Return value: float 161 | * 162 | * Usage: 163 | * float load_factor = map.load_factor(); 164 | * 165 | * Complexity: O(1) (inlined because function is short) 166 | * 167 | * Notes: our minimal implementation does not automatically rehash when the load 168 | * factor is too high. If you want as an extension, you can implement automatic rehashing. 169 | */ 170 | inline float load_factor(); 171 | 172 | /* 173 | * Returns the number of buckets. 174 | * 175 | * Parameters: none 176 | * Return value: size_t - number of buckets 177 | * 178 | * Usage: 179 | * size_t buckets = map.bucket_count(); 180 | * 181 | * Complexity: O(1) (inlined because function is short) 182 | * 183 | * Notes: our minimal implementation does not automatically rehash when the load 184 | * factor is too high. If you want, you can implement automatic rehashing. 185 | * 186 | * What is noexcept? It's a guarantee that this function does not throw 187 | * exceptions, allowing the compiler to optimize this function further. 188 | * A noexcept function that throws an exception will automatically 189 | * terminate the program. 190 | */ 191 | inline size_t bucket_count(); 192 | 193 | /* 194 | * Returns whether or not the HashMap contains the given key. 195 | * 196 | * Parameters: const l-value reference to type K, the given key 197 | * Return value: bool 198 | * 199 | * Usage: 200 | * if (map.contains("Avery")) { map.at("Avery"); ... } 201 | * 202 | * Complexity: O(1) amortized average case, O(N) worst case, N = number of elements 203 | * 204 | * Notes: Recall that when using a std::map, you use the map.count(key) function 205 | * (returns 0 or 1) to check if key exists. In C++20, map.contains(key) will be available. 206 | * Since contains feels more natural to students who've used the Stanford libraries 207 | * and will be available in the future, we will implement map.contains(key). 208 | */ 209 | bool contains(const K& key); 210 | 211 | /* 212 | * Returns a l-value reference to the mapped value given a key. 213 | * If no such element exists, throws exception of type std::out_of_range. 214 | * 215 | * Parameters: key of type K. 216 | * Return value: l-value reference to type V, the mapped value of key. 217 | * 218 | * Usage: 219 | * map.at(3) = "Avery"; // assuming {3, "Avery"} is in the map. 220 | * std::string s = map.at(3); // s = "Avery" 221 | * 222 | * Exceptions: std::out_of_range if key is not in the map. 223 | * 224 | * Complexity: O(1) amortized average case, O(N) worst case, N = number of elements 225 | * 226 | * Notes: recall that operator[], which you will implement, does not throw exceptions, 227 | * if a key is not found. Instead, it will create a K/M pair for that key with a default 228 | * mapped value. This function is also not const-correct, which you will fix in milestone 2. 229 | */ 230 | M& at(const K& key); 231 | 232 | /* 233 | * Removes all K/M pairs in the HashMap. 234 | * 235 | * Parameters: none 236 | * Return value: none 237 | * 238 | * Usage: 239 | * map.clear(); 240 | * 241 | * Complexity: O(N), N = number of elements 242 | * 243 | * Notes: clear removes all the elements in the HashMap and frees the memory associated 244 | * with those elements, but the HashMap should still be in a valid state and is 245 | * ready to be inserted again, as if it were a newly constructed HashMap with no elements. 246 | * The number of buckets should stay the same. 247 | */ 248 | void clear(); 249 | 250 | /* 251 | * Inserts the K/M pair into the HashMap, if the key does not already exist. 252 | * If the key exists, then the operation is a no-op. 253 | * 254 | * Parameters: const l-value reference to value_type (K/M pair) 255 | * Return value: 256 | * pair, where: 257 | * iterator - iterator to the value_type element with the given key 258 | * this element may have been just added, or may have already existed. 259 | * bool - true if the element was successfully added, 260 | * false if the element already existed. 261 | * 262 | * Usage: 263 | * HashMap map; 264 | * auto [iter1, insert1] = map.insert({3, "Avery"}); // inserts {3, "Avery"}, iter1 points to that element, insert1 = true 265 | * auto [iter2, insert2] = map.insert({3, "Anna"}); // no-op, iter2 points to {3, "Avery"}, insert2 = false 266 | * 267 | * Complexity: O(1) amortized average case 268 | */ 269 | std::pair insert(const value_type& val); 270 | 271 | /* 272 | * Erases a K/M pair (if one exists) corresponding to given key from the HashMap. 273 | * This is a no-op if the key does not exist. 274 | * 275 | * Parameters: const l-value reference to K, key to be removed. 276 | * Return value: true if K/M pair was found and removed, false if key was not found. 277 | * 278 | * Usage: 279 | * map.erase(3); // assuming K = int, erases element with key 3, returns true 280 | * 281 | * Complexity: O(1) amortized average case, O(N) worst case, N = number of elements 282 | * 283 | * Notes: a call to erase should maintain the order of existing iterators, 284 | * other than iterators to the erased K/M element. 285 | */ 286 | bool erase(const K& key); 287 | 288 | /* 289 | * Erases the K/M pair that pos points to. 290 | * Behavior is undefined if pos is not a valid and dereferencable iterator. 291 | * 292 | * Parameters: const_iterator pos, iterator to element to be removed 293 | * Return value: the iterator immediately following pos, which may be end(). 294 | * 295 | * Usage: 296 | * auto iter = map.find(3); 297 | * auto next = map.erase(iter); // erases element that iter is pointing to 298 | * 299 | * Complexity: O(1) amortized average case, O(N) worst case, N = number of elements 300 | * 301 | * Notes: a call to erase should maintain the order of existing iterators, 302 | * other than iterators to the erased K/M element. 303 | */ 304 | iterator erase(const_iterator pos); 305 | 306 | /* 307 | * Resizes the array of buckets, and rehashes all elements. new_buckets could 308 | * be larger than, smaller than, or equal to the original number of buckets. 309 | * 310 | * Parameters: new_buckets - the new number of buckets. Must be greater than 0. 311 | * Return value: none 312 | * 313 | * Usage: 314 | * map.rehash(30) 315 | * 316 | * Exceptions: std::out_of_range if new_buckets = 0. 317 | * 318 | * Complexity: O(N) amortized average case, O(N^2) worst case, N = number of elements 319 | * 320 | * Notes: our minimal HashMap implementation does not support automatic rehashing, but 321 | * std::unordered_map will automatically rehash, even if you rehash to 322 | * a very small number of buckets. For this reason, std::unordered_map.rehash(0) 323 | * is allowed and forces an unconditional rehash. We will not require this behavior. 324 | * If you want, you could implement this. 325 | * 326 | * Previously, this function was part of the assignment. However, it's a fairly challenging 327 | * linked list problem, and students had a difficult time finding an elegant solution. 328 | * Instead, we will ask short answer questions on this function instead. 329 | */ 330 | void rehash(size_t new_bucket); 331 | 332 | /* 333 | * Returns an iterator to the first element. 334 | * This overload is used when the HashMap is non-const. 335 | * 336 | * Usage: 337 | * auto iter = map.begin(); 338 | */ 339 | iterator begin(); 340 | 341 | /* 342 | * Returns a const_iterator to the first element. 343 | * This overload is used when the HashMap is const. 344 | * 345 | * Usage: 346 | * auto iter = cmap.begin(); 347 | */ 348 | const_iterator begin() const; 349 | 350 | /* 351 | * Returns an iterator to one past the last element. 352 | * This overload is used when the HashMap is non-const. 353 | * 354 | * Usage: 355 | * while (iter != map.end()) {...} 356 | */ 357 | iterator end(); 358 | 359 | /* 360 | * Finds the element with the given key, and returns an iterator to that element. 361 | * If an element is not found, an iterator to end() is returned. 362 | * 363 | * Parameters: const l-value reference to type K, the key we are looking for. 364 | * Return value: iterator to the K/M element with given key. 365 | * 366 | * Usage: 367 | * auto iter = map.find(4); 368 | * iter->second = "Hello"; // sets whatever 4 was mapped to to "Hello". 369 | * 370 | * Complexity: O(1) amortized average case, O(N) worst case, N = number of elements 371 | */ 372 | iterator find(const K& key); 373 | 374 | /* 375 | * Function that will print to std::cout the contents of the hash table as 376 | * linked lists, and also displays the size, number of buckets, and load factor. 377 | * 378 | * Parameters: none 379 | * Return value: none 380 | * 381 | * Usage: 382 | * map.debug(); 383 | * 384 | * Complexity: O(N), N = number of elements. 385 | * 386 | * Notes: debug will not compile if either K or V does not support operator<< for std::ostream. 387 | * this function will crash if your linked list logic is incorrect (eg. forgot to reset the 388 | * last node's next to nullptr). Check where the source of the compiler error comes from 389 | * before complaining to us that our starter code doesn't work! 390 | * 391 | * Tip: place map.debug() in various places in the test cases to figure out which operation 392 | * is failing. Super useful when we debugged our code. 393 | */ 394 | void debug(); 395 | 396 | /* EXTRA CONSTURCTORS */ 397 | 398 | /* 399 | * Range constructor 400 | * Creates a HashMap with the elements in the range [first, last). 401 | * 402 | * Requirements: InputIt must be iterators to a container whose elements are pair. 403 | * 404 | * Usage: 405 | * std::vector> vec {{'a', 3}, {'b', 5}, {'c', 7}}; 406 | * HashMap map{vec.begin(), vec.end()}; 407 | * 408 | * Complexity: O(N), where N = std::distance(first, last); 409 | */ 410 | template 411 | HashMap(InputIter begin, InputIter end, size_t bucket_count = kDefaultBuckets, const H& hash = H()); 412 | 413 | /* 414 | * Initializer list constructor 415 | * Creates a HashMap with the elements in the initializer list init 416 | * 417 | * Requirements: init must be an initializer_list whose elements are pair. 418 | * 419 | * Usage: 420 | * HashMap map{{'a', 3}, {'b', 5}, {'c', 7}}; 421 | * 422 | * Complexity: O(N), where N = init.size(); 423 | * 424 | * Notes: you may want to do some research on initializer_lists. The most important detail you need 425 | * to know is that they are very limited, and have three functions: init.begin(), init.end(), and init.size(). 426 | * There are no other ways to access the elements in an initializer_list. 427 | * As a result, you probably want to leverage the range constructor you wrote in the previous function! 428 | * 429 | * Also, you should check out the delegating constructor note in the .cpp file. 430 | */ 431 | HashMap(std::initializer_list init, size_t bucket_count = kDefaultBuckets, const H& hash = H()); 432 | 433 | /* 434 | * Indexing operator 435 | * Retrieves a reference to the mapped value corresponding to this key. 436 | * If no such key exists, a key/mapped value pair will be added to the HashMap. 437 | * The mapped value will have the default value for type M. 438 | * 439 | * Usage: 440 | * HashMap map; 441 | * map[3] = "Avery"; // creates the pair {3, "Avery"} 442 | * auto name = map[3]; // name is now "Avery" 443 | * auto name2 = map[4]; // creates the pair {4, ""}, name2 is now "" 444 | * 445 | * Complexity: O(1) average case amortized plus complexity of K and M's constructor 446 | */ 447 | M& operator[](const K& key); 448 | 449 | 450 | // TODO: declare headers for copy constructor/assignment, move constructor/assignment 451 | 452 | 453 | private: 454 | /* 455 | * node structure represented a node in a linked list. 456 | * Each node consists of a value_type (K/M pair) and a next pointer. 457 | * 458 | * This is implemented in the private section as clients should not be dealing 459 | * with anything related to the node struct. 460 | * 461 | * Usage; 462 | * HashMap::node n; 463 | * n->value = {3, 4}; 464 | * n->next = nullptr; 465 | */ 466 | struct Node 467 | { 468 | value_type value; 469 | Node* next; 470 | /* 471 | * Default constructor, so even if you forget to set next to nullptr it'll be fine. 472 | * 473 | */ 474 | Node() : value(value_type()), next(nullptr) {}; 475 | }; 476 | 477 | /* 478 | * Type alias for a pair of node*'s. 479 | * 480 | * This is used in find_node. 481 | * 482 | * Usage: 483 | * auto& [prev, curr] = node_pair{nullptr, new node()}; 484 | */ 485 | using node_pair = std::pair; 486 | 487 | /* 488 | * Finds the node N with given key, and returns a node_pair consisting of 489 | * the node whose's next is N, and N. If node is not found, {nullptr, nullptr} 490 | * is returned. If node found is the first in the list, {nullptr, node} is returned. 491 | * 492 | * Example given list: front -> [A] -> [B] -> [C] -> / 493 | * where A, B, C, D are pointers, then 494 | * 495 | * find_node(A_key) = {nullptr, A} 496 | * find_node(B_key) = {A, B} 497 | * find_node(C_key) = {B, C} 498 | * find_node(D_key) = {nullptr, nullptr} 499 | * 500 | * Usage: 501 | * auto& [prev, curr] = find_node(3); 502 | * if (prev == nullptr) { ... } 503 | * 504 | * Complexity: O(1) amortized average case, O(N) worst case, N = number of elements 505 | * 506 | * Notes: this function is necessary because when erasing, we need to change the 507 | * next pointer of the node before the one we are erasing. 508 | * 509 | * Hint: on the assignment, you should NOT need to call this function. 510 | */ 511 | node_pair find_node(const K& key) const; 512 | 513 | /* 514 | * Finds the first bucket in _buckets_array that is non-empty. 515 | * 516 | * Hint: on the assignment, you should NOT need to call this function. 517 | */ 518 | size_t first_not_empty_bucket() const; 519 | 520 | /* 521 | * Creates an iterator that points to the element curr->value. 522 | * 523 | * Hint: on the assignment, you should NOT need to call this function. 524 | */ 525 | iterator make_iterator(Node* curr); 526 | 527 | /* Private member variables */ 528 | 529 | /* 530 | * instance variable: _size, the number of elements, which are K/M pairs. 531 | * Don't confuse this with the number of buckets! 532 | */ 533 | size_t _size; 534 | 535 | /* 536 | * instance variable: _hash_function, a function (K -> size_t) that is used 537 | * to hash K's to determine which bucket they should be inserted/found. 538 | * 539 | * Remember to mod the output of _hash_function by _bucket_count! 540 | * 541 | * Usage: 542 | * K element = // something; 543 | * size_t index = _hash_function(element) % _bucket_count; 544 | * 545 | */ 546 | H _hash_function; 547 | 548 | /* 549 | * The array (vector) of buckets. Each bucket is a linked list, 550 | * and the item stored in the bucket is the front pointer of that linked list. 551 | * 552 | * Usage: 553 | * node* ptr = _buckets_array[index]; // _buckets_array is array of node* 554 | * const auto& [key, mapped] = ptr->value; // each node* contains a value that is a pair 555 | */ 556 | std::vector _buckets_array; 557 | 558 | /* 559 | * A constant for the default number of buckets for the default constructor. 560 | */ 561 | static const size_t kDefaultBuckets = 10; 562 | 563 | /* 564 | * A private type alias used by the iterator class so it can traverse 565 | * the buckets. 566 | */ 567 | using bucket_array_type = decltype(_buckets_array); 568 | }; 569 | 570 | #include "hashmap.cpp" 571 | #endif -------------------------------------------------------------------------------- /assignments/HashMap/hashmap_iterator.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef HASHMAP_ITERATOR_H 3 | #define HASHMAP_ITERATOR_H 4 | 5 | #include // for std::forward_iterator_tag 6 | #include // for std::conditional_t 7 | 8 | // forward declaration for the HashMap class 9 | template class HashMap; 10 | 11 | /* 12 | * Template class for a HashMapIterator 13 | * 14 | * Map = the type of HashMap this class is an iterator for. 15 | * IsConst = whether this is a const_iterator class. 16 | * 17 | * Concept requirements: 18 | * - Map must be a valid class HashMap 19 | */ 20 | template 21 | class HashMapIterator { 22 | public: 23 | 24 | /* 25 | * This alias is very important. When dealing with const_iterator, the value_type is always const, and 26 | * that prevents the client from modifying the elements via a const_iterator. The meta-function 27 | * std::conditional_t changes the value_type (at compile-time) to a const one if IsConst is true. 28 | */ 29 | using value_type = std::conditional_t; 30 | 31 | /* 32 | * Public aliases for this iterator class. Important so STL functions like std::iterator_traits 33 | * can parse this class for important information, like its iterator category. 34 | */ 35 | using iterator_category = std::forward_iterator_tag; 36 | using difference_type = std::ptrdiff_t; 37 | using pointer = value_type*; 38 | using reference = value_type&; 39 | 40 | /* 41 | * Friend declarations so the HashMap class this iterator is for can access the attributes of its iterators. 42 | * Also, to make conversions between iterator and const_iterator easy, we declare the corresponding 43 | * iterator and const_iterators as friends. 44 | */ 45 | friend Map; 46 | friend HashMapIterator; 47 | friend HashMapIterator; 48 | 49 | /* 50 | * Conversion operator: converts any iterator (iterator or const_iterator) to a const_iterator. 51 | * 52 | * Usage: 53 | * iterator iter = map.begin(); 54 | * const_iterator c_iter = iter; // implicit conversion 55 | * 56 | * Implicit conversion operators are usually frowned upon, because they can cause 57 | * some unexpected behavior. This is a rare case where a conversion operator is 58 | * very convenient. Many of the iterator functions in the HashMap class are 59 | * secretly using this conversion. 60 | * 61 | * Note: conversion in the opposite direction (const to non-const) is not safe 62 | * because that gives the client write access the map itself is const. 63 | */ 64 | operator HashMapIterator() const { 65 | return HashMapIterator(_buckets_array, _node, _bucket_idx); 66 | } 67 | 68 | /* 69 | * Dereference operators: defines the behavior of dereferencing an iterator. 70 | * 71 | * Usage: 72 | * auto [key, value] = *iter; 73 | * auto value = iter->second; 74 | * iter->second = 3; // if iter is a regular iterator (not a const_iterator) 75 | * 76 | * Note that dereferencing an invalid or end() iterator is undefined behavior. 77 | */ 78 | reference operator*(); 79 | pointer operator->(); 80 | 81 | /* 82 | * Increment operators: moves the iterator to point to the next element, or end(). 83 | * 84 | * Usage: 85 | * ++iter; // prefix 86 | * iter++; // postfix 87 | * 88 | * Note: the prefix operator first increments, and the returns a reference to itself (which is incremented). 89 | * The postfix operator returns a copy of the original iterator, while the iterator itself is incremented. 90 | * 91 | * Note that incrementing an invalid or end() iterator is undefined behavior. 92 | */ 93 | HashMapIterator& operator++(); 94 | HashMapIterator operator++(int); 95 | 96 | /* 97 | * Equality operator: decides if two iterators are pointing to the same element. 98 | * 99 | * Usage: 100 | * if (iter == map.end()) {...}; 101 | */ 102 | template 103 | friend bool operator==(const HashMapIterator& lhs, const HashMapIterator& rhs); 104 | 105 | /* 106 | * Inequality operator: decides if two iterators are pointing to different elements. 107 | * 108 | * Usage: 109 | * if (iter != map.end()) {...}; 110 | */ 111 | template 112 | friend bool operator!=(const HashMapIterator& lhs, const HashMapIterator& rhs); 113 | 114 | /* 115 | * Special member functions: we explicitly state that we want the default compiler-generated functions. 116 | * Here we are following the rule of zero. You should think about why that is correct. 117 | */ 118 | HashMapIterator(const HashMapIterator& rhs) = default; 119 | HashMapIterator& operator=(const HashMapIterator& rhs) = default; 120 | 121 | HashMapIterator(HashMapIterator&& rhs) = default; 122 | HashMapIterator& operator=(HashMapIterator&& rhs) = default; 123 | 124 | private: 125 | using Node = typename Map::Node; 126 | using bucket_array_type = typename Map::bucket_array_type; 127 | 128 | /* 129 | * Instance variable: a pointer to the _buckets_array of the HashMap this iterator is for. 130 | */ 131 | bucket_array_type* _buckets_array; 132 | 133 | /* 134 | * Instance variable: pointer to the node that stores the element this iterator is currently pointing to. 135 | */ 136 | Node* _node; 137 | 138 | /* 139 | * Instance variable: the index of the bucket that _node is in. 140 | */ 141 | size_t _bucket_idx; 142 | 143 | /* 144 | * Private constructor for a HashMapIterator. 145 | * Friend classes can access the private members of class it is friends with, 146 | * so HashMap is able to call HashMapIterator's private constructor 147 | * (e.g, in begin()). We want the HashMapIterator constructor to be private 148 | * so a client can't randomly construct a HashMapIterator without asking for one 149 | * through the HashMap's interface. 150 | */ 151 | HashMapIterator(bucket_array_type* buckets_array, Node* node, size_t bucket_idx); 152 | }; 153 | 154 | template 155 | HashMapIterator::HashMapIterator(bucket_array_type* buckets_array, Node* node, size_t bucket_idx): 156 | _buckets_array(buckets_array), 157 | _node(node), 158 | _bucket_idx(bucket_idx) {}; 159 | #endif -------------------------------------------------------------------------------- /assignments/HashMap/hashmap_perf.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "hashmap.h" 10 | #include "gtest/gtest.h" 11 | #include "test_settings.h" 12 | 13 | using clock_type = std::chrono::high_resolution_clock; 14 | using ns = std::chrono::nanoseconds; 15 | 16 | std::string print_with_commas(long long int n) { 17 | std::string ans = ""; 18 | // Convert the given integer 19 | // to equivalent string 20 | std::string num = std::to_string(n); 21 | 22 | // Initialise count 23 | int count = 0; 24 | 25 | // Traverse the string in reverse 26 | for (int i = num.size() - 1; i >= 0; i--) { 27 | count++; 28 | ans.push_back(num[i]); 29 | 30 | // If three characters 31 | // are traversed 32 | if (count == 3) { 33 | ans.push_back(','); 34 | count = 0; 35 | } 36 | } 37 | 38 | // Reverse the string to get 39 | // the desired output 40 | reverse(ans.begin(), ans.end()); 41 | 42 | // If the given string is 43 | // less than 1000 44 | if (ans.size() % 4 == 0) { 45 | // Remove ',' 46 | ans.erase(ans.begin()); 47 | } 48 | return ans; 49 | } 50 | 51 | #if RUN_TEST_PERF 52 | void benchmark_insert_erase() { 53 | std::cout << "Task: insert then erase N elements, measured in ns." << '\n'; 54 | auto good_hash_function = [](const int& key) { 55 | return (key * 43037 + 52081) % 79229; 56 | }; 57 | 58 | std::vector my_map_timing; 59 | std::vector sizes{10, 100, 1000, 10000, 100000, 1000000}; 60 | for (size_t size : sizes) { 61 | std::vector million; 62 | for (size_t i = 0; i < size; i++) { 63 | million.push_back(i); 64 | } 65 | auto rng = std::default_random_engine {}; 66 | std::shuffle(million.begin(), million.end(), rng); 67 | size_t my_map_result, std_map_result; 68 | { 69 | auto my_start = clock_type::now(); 70 | 71 | HashMap my_map(size, good_hash_function); 72 | for (int element : million) { 73 | my_map.insert({element, element}); 74 | } 75 | 76 | for (int element : million) { 77 | my_map.erase(element); 78 | } 79 | 80 | auto my_end = clock_type::now(); 81 | auto end = std::chrono::duration_cast(my_end - my_start); 82 | 83 | my_map_result = end.count(); 84 | } 85 | 86 | { 87 | auto std_start = clock_type::now(); 88 | 89 | std::unordered_map std_map(size, good_hash_function); 90 | for (int element : million) { 91 | std_map.insert({element, element}); 92 | } 93 | 94 | for (int element : million) { 95 | std_map.erase(element); 96 | } 97 | 98 | auto std_end = clock_type::now(); 99 | auto end = std::chrono::duration_cast(std_end - std_start); 100 | 101 | std_map_result = end.count(); 102 | } 103 | 104 | std::cout << "size " << std::setw(10) << size; 105 | std::cout << " | HashMap: " << std::setw(13) << print_with_commas(my_map_result); 106 | std::cout << " | std:unordered_map: " << std::setw(13) << print_with_commas(std_map_result) << '\n'; 107 | my_map_timing.push_back(my_map_result); 108 | } 109 | EXPECT_TRUE(10*my_map_timing[0] < my_map_timing[3]); // Ensure runtime of N = 10 is much faster than N = 10000 110 | } 111 | 112 | void benchmark_find() { 113 | std::cout << "Task: find N elements (random hit/miss), measured in ns." << '\n'; 114 | auto good_hash_function = [](const int& key) { 115 | return (key * 43037 + 52081) % 79229; 116 | }; 117 | 118 | std::vector my_map_timing; 119 | std::vector sizes{10, 100, 1000, 10000, 100000, 1000000}; 120 | for (size_t size : sizes) { 121 | std::vector million; 122 | std::vector lookup; 123 | for (size_t i = 0; i < 2*size; i++) { 124 | million.push_back(i); 125 | lookup.push_back(i); 126 | } 127 | auto rng = std::default_random_engine {}; 128 | std::shuffle(million.begin(), million.end(), rng); 129 | std::shuffle(lookup.begin(), lookup.end(), rng); 130 | size_t my_map_result, std_map_result; 131 | { 132 | 133 | HashMap my_map(size, good_hash_function); 134 | for (size_t i = 0; i < million.size(); i += 2) { 135 | int element = million[i]; 136 | my_map.insert({element, element}); 137 | } 138 | auto my_start = clock_type::now(); 139 | int count = 0; 140 | for (size_t i = 0; i < lookup.size(); i += 2) { 141 | int element = lookup[i]; 142 | auto found = my_map.find(element); 143 | count += (found == my_map.end()); 144 | } 145 | 146 | auto my_end = clock_type::now(); 147 | auto end = std::chrono::duration_cast(my_end - my_start); 148 | 149 | my_map_result = end.count(); 150 | } 151 | 152 | { 153 | std::unordered_map std_map(size, good_hash_function); 154 | for (size_t i = 0; i < million.size(); i += 2) { 155 | int element = million[i]; 156 | std_map.insert({element, element}); 157 | } 158 | auto std_start = clock_type::now(); 159 | int count = 0; 160 | for (size_t i = 0; i < lookup.size(); i += 2) { 161 | int element = lookup[i]; 162 | auto found = std_map.find(element); 163 | count += (found == std_map.end()); 164 | } 165 | 166 | auto std_end = clock_type::now(); 167 | auto end = std::chrono::duration_cast(std_end - std_start); 168 | 169 | std_map_result = end.count(); 170 | } 171 | 172 | std::cout << "size " << std::setw(10) << size; 173 | std::cout << " | HashMap: " << std::setw(13) << print_with_commas(my_map_result); 174 | std::cout << " | std:unordered_map: " << std::setw(13) << print_with_commas(std_map_result) << '\n'; 175 | my_map_timing.push_back(my_map_result); 176 | } 177 | EXPECT_TRUE(10*my_map_timing[0] < my_map_timing[3]); // Ensure runtime of N = 10 is much faster than N = 10000 178 | } 179 | 180 | void benchmark_iterate() { 181 | std::cout << "Task: iterate over all N elements, measured in ns." << '\n'; 182 | auto good_hash_function = [](const int& key) { 183 | return (key * 43037 + 52081) % 79229; 184 | }; 185 | 186 | std::vector my_map_timing; 187 | std::vector std_map_timing; 188 | std::vector sizes{10, 100, 1000, 10000, 100000, 1000000}; 189 | for (size_t size : sizes) { 190 | std::vector million; 191 | for (size_t i = 0; i < size; i++) { 192 | million.push_back(i); 193 | } 194 | auto rng = std::default_random_engine {}; 195 | std::shuffle(million.begin(), million.end(), rng); 196 | size_t my_map_result, std_map_result; 197 | { 198 | HashMap my_map(size, good_hash_function); 199 | for (int element : million) { 200 | my_map.insert({element, element}); 201 | } 202 | 203 | auto my_start = clock_type::now(); 204 | size_t count = 0; 205 | for (const auto& [key, value] : my_map) { 206 | count += key; 207 | } 208 | auto my_end = clock_type::now(); 209 | auto end = std::chrono::duration_cast(my_end - my_start); 210 | 211 | my_map_result = end.count(); 212 | } 213 | 214 | { 215 | std::unordered_map std_map(size, good_hash_function); 216 | for (int element : million) { 217 | std_map.insert({element, element}); 218 | } 219 | 220 | auto std_start = clock_type::now(); 221 | size_t count = 0; 222 | for (const auto& [key, value] : std_map) { 223 | count += key; 224 | } 225 | auto std_end = clock_type::now(); 226 | auto end = std::chrono::duration_cast(std_end - std_start); 227 | 228 | std_map_result = end.count(); 229 | } 230 | 231 | std::cout << "size " << std::setw(10) << size; 232 | std::cout << " | HashMap: " << std::setw(13) << print_with_commas(my_map_result); 233 | std::cout << " | std:unordered_map: " << std::setw(13) << print_with_commas(std_map_result) << '\n'; 234 | my_map_timing.push_back(my_map_result); 235 | } 236 | EXPECT_TRUE(10*my_map_timing[0] < my_map_timing[3]); // Ensure runtime of N = 10 is much faster than N = 10000 237 | } 238 | #endif 239 | 240 | int main() { 241 | std::cout << "Performance Test: " << std::endl; 242 | #if RUN_TEST_PERF 243 | benchmark_find(); 244 | benchmark_insert_erase(); 245 | benchmark_iterate(); 246 | #endif 247 | return 0; 248 | } -------------------------------------------------------------------------------- /assignments/HashMap/test_settings.h: -------------------------------------------------------------------------------- 1 | // Test settings - use this file to change which tests are executed 2 | 3 | // Change the 0 to a 1 to run that test 4 | // Note that the tests won't compile until their respective functions 5 | // have a header in the .h, and implementations in the .cpp 6 | 7 | // Milestone 1 test cases of basic functionality 8 | // Basic Functions 9 | #define RUN_TEST_1A 1 10 | #define RUN_TEST_1B 0 11 | #define RUN_TEST_1C 0 12 | #define RUN_TEST_1D 0 13 | #define RUN_TEST_1E 0 14 | #define RUN_TEST_1F 0 15 | #define RUN_TEST_1G 0 16 | #define RUN_TEST_1H 0 17 | #define RUN_TEST_1I 0 18 | 19 | // Iterator 20 | #define RUN_TEST_1J 0 21 | #define RUN_TEST_1K 0 22 | #define RUN_TEST_1L 0 23 | #define RUN_TEST_1M 0 24 | #define RUN_TEST_1N 0 25 | #define RUN_TEST_1O 0 26 | #define RUN_TEST_1P 0 27 | #define RUN_TEST_1Q 0 28 | #define RUN_TEST_1R 0 29 | #define RUN_TEST_1S 0 30 | #define RUN_TEST_1T 0 31 | #define RUN_TEST_1U 0 32 | #define RUN_TEST_1V 0 33 | 34 | // Milestone 2: range constructor (optional) 35 | #define RUN_TEST_2A 0 36 | #define RUN_TEST_2B 0 37 | // Milestone 2: initializer list constructor (optional) 38 | #define RUN_TEST_2C 0 39 | #define RUN_TEST_2D 0 40 | 41 | // Milestone 3: operator[] 42 | #define RUN_TEST_3A 0 43 | #define RUN_TEST_3B 0 44 | // Milestone 3: operator<< 45 | #define RUN_TEST_3C 0 46 | #define RUN_TEST_3D 0 47 | // Milestone 3: operator== and operator!= 48 | #define RUN_TEST_3E 0 49 | #define RUN_TEST_3F 0 50 | 51 | // Milestone 4: copy operations 52 | #define RUN_TEST_4A 0 53 | #define RUN_TEST_4B 0 54 | #define RUN_TEST_4C 0 55 | 56 | // Milestone 4: move operations 57 | // warning: these may pass even if you haven't implemented them 58 | // - before implementing copy or move, 3AB will fail, 3CDEFGH will pass 59 | // - after implementing copy but not move, 3GH will fail, 3ABCDEF will pass 60 | // - after implementing copy & move, all of them should pass (you should aim for this) 61 | #define RUN_TEST_4D 0 62 | #define RUN_TEST_4E 0 63 | #define RUN_TEST_4F 0 64 | #define RUN_TEST_4G 0 65 | #define RUN_TEST_4H 0 66 | 67 | // Milestone 5: benchmark (optional) 68 | #define RUN_TEST_PERF 0 69 | -------------------------------------------------------------------------------- /assignments/KDTree/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(KDTree) 3 | 4 | # GoogleTest requires at least C++14 5 | set(CMAKE_CXX_STANDARD 20) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | 8 | include(FetchContent) 9 | FetchContent_Declare( 10 | googletest 11 | URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip 12 | ) 13 | 14 | # For Windows: Prevent overriding the parent project's compiler/linker settings 15 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 16 | FetchContent_MakeAvailable(googletest) 17 | 18 | enable_testing() 19 | 20 | add_executable(kd_tree_test kd_tree_test.cc) 21 | target_link_libraries(kd_tree_test GTest::gtest_main) 22 | 23 | 24 | include(GoogleTest) 25 | gtest_discover_tests(kd_tree_test) 26 | -------------------------------------------------------------------------------- /assignments/KDTree/README.md: -------------------------------------------------------------------------------- 1 | # KDTree 2 | In this programming assignment, you will implement a k-dimension tree which is designed for efficient storage and query of multi-dimension data. For detailed description of KDTree and guidance on this assignment, you can refer to this [document](./doc/005_assignment_3_kdtree.pdf). To build and test your codes, you can refer to following commands: 3 | ```shell 4 | mkdir -p build && cd build 5 | cmake .. 6 | make 7 | ./kd_tree_test 8 | ``` 9 | You can selectively enable testcases corresponding to the functionalities you have already implemented by changing macro definitions in the header of [kd_tree_test.cc](./kd_tree_test.cc). 10 | -------------------------------------------------------------------------------- /assignments/KDTree/bounded_priority_queue.h: -------------------------------------------------------------------------------- 1 | /** 2 | * File: BoundedPriorityQueue.h 3 | * Author: Keith Schwarz (htiek@cs.stanford.edu) 4 | * 5 | * An implementation of the bounded priority queue abstraction. 6 | * A bounded priority queue is in many ways like a regular priority 7 | * queue. It stores a collection of elements tagged with a real- 8 | * valued priority, and allows for access to the element whose 9 | * priority is the smallest. However, unlike a regular priority 10 | * queue, the number of elements in a bounded priority queue has 11 | * a hard limit that is specified in the constructor. Whenever an 12 | * element is added to the bounded priority queue such that the 13 | * size exceeds the maximum, the element with the highest priority 14 | * value will be ejected from the bounded priority queue. In this 15 | * sense, a bounded priority queue is like a high score table for 16 | * a video game that stores a fixed number of elements and deletes 17 | * the least-important entry whenever a new value is inserted. 18 | * 19 | * When creating a bounded priority queue, you must specify the 20 | * maximum number of elements to store in the queue as an argument 21 | * to the constructor. For example: 22 | * 23 | * BoundedPriorityQueue bpq(15); // Holds up to fifteen values. 24 | * 25 | * The maximum size of the bounded priority queue can be obtained 26 | * using the maxSize() function, as in 27 | * 28 | * size_t k = bpq.maxSize(); 29 | * 30 | * Beyond these restrictions, the bounded priority queue behaves 31 | * similarly to other containers. You can query its size using 32 | * size() and check whether it is empty using empty(). You 33 | * can enqueue an element into the bounded priority queue by 34 | * writing 35 | * 36 | * bpq.enqueue(elem, priority); 37 | * 38 | * Note that after enqueuing the element, there is no guarantee 39 | * that the value will actually be in the queue. If the queue 40 | * is full and the new element's priority exceeds the largest 41 | * priority in the container, it will not be added. 42 | * 43 | * You can dequeue elements from a bounded priority queue using 44 | * the dequeueMin() function, as in 45 | * 46 | * int val = bpq.dequeueMin(); 47 | * 48 | * The bounded priority queue also allows you to query the min 49 | * and max priorities of the values in the queue. These values 50 | * can be queried using the best() and worst() functions, which 51 | * return the smallest and largest priorities in the queue, 52 | * respectively. 53 | */ 54 | 55 | #ifndef BOUNDED_PQUEUE_INCLUDED 56 | #define BOUNDED_PQUEUE_INCLUDED 57 | 58 | #include 59 | #include 60 | #include 61 | 62 | using namespace std; 63 | 64 | template 65 | class BoundedPriorityQueue { 66 | public: 67 | // Constructor: BoundedPriorityQueue(size_t maxSize); 68 | // Usage: BoundedPriorityQueue bpq(15); 69 | // -------------------------------------------------- 70 | // Constructs a new, empty BoundedPriorityQueue with 71 | // maximum size equal to the constructor argument. 72 | /// 73 | explicit BoundedPriorityQueue(size_t maxSize); 74 | 75 | // void enqueue(const T& value, double priority); 76 | // Usage: bpq.enqueue("Hi!", 2.71828); 77 | // -------------------------------------------------- 78 | // Enqueues a new element into the BoundedPriorityQueue with 79 | // the specified priority. If this overflows the maximum 80 | // size of the queue, the element with the highest 81 | // priority will be deleted from the queue. Note that 82 | // this might be the element that was just added. 83 | void enqueue(const T& value, double priority); 84 | 85 | // T dequeueMin(); 86 | // Usage: int val = bpq.dequeueMin(); 87 | // -------------------------------------------------- 88 | // Returns the element from the BoundedPriorityQueue with the 89 | // smallest priority value, then removes that element 90 | // from the queue. 91 | T dequeueMin(); 92 | 93 | // size_t size() const; 94 | // bool empty() const; 95 | // Usage: while (!bpq.empty()) { ... } 96 | // -------------------------------------------------- 97 | // Returns the number of elements in the queue and whether 98 | // the queue is empty, respectively. 99 | size_t size() const; 100 | bool empty() const; 101 | 102 | // size_t maxSize() const; 103 | // Usage: size_t queueSize = bpq.maxSize(); 104 | // -------------------------------------------------- 105 | // Returns the maximum number of elements that can be 106 | // stored in the queue. 107 | size_t maxSize() const; 108 | 109 | // double best() const; 110 | // double worst() const; 111 | // Usage: double highestPriority = bpq.worst(); 112 | // -------------------------------------------------- 113 | // best() returns the smallest priority of an element 114 | // stored in the container (i.e. the priority of the 115 | // element that will be dequeued first using dequeueMin). 116 | // worst() returns the largest priority of an element 117 | // stored in the container. If an element is enqueued 118 | // with a priority above this value, it will automatically 119 | // be deleted from the queue. Both functions return 120 | // numeric_limits::infinity() if the queue is 121 | // empty. 122 | double best() const; 123 | double worst() const; 124 | 125 | private: 126 | // This class is layered on top of a multimap mapping from priorities 127 | // to elements with those priorities. 128 | multimap elems; 129 | size_t maximumSize; 130 | }; 131 | 132 | /** BoundedPriorityQueue class implementation details */ 133 | 134 | template 135 | BoundedPriorityQueue::BoundedPriorityQueue(size_t maxSize) { 136 | maximumSize = maxSize; 137 | } 138 | 139 | // enqueue adds the element to the map, then deletes the last element of the 140 | // map if there size exceeds the maximum size. 141 | template 142 | void BoundedPriorityQueue::enqueue(const T& value, double priority) { 143 | // Add the element to the collection. 144 | elems.insert(make_pair(priority, value)); 145 | 146 | // If there are too many elements in the queue, drop off the last one. 147 | if (size() > maxSize()) { 148 | typename multimap::iterator last = elems.end(); 149 | --last; // Now points to highest-priority element 150 | elems.erase(last); 151 | } 152 | } 153 | 154 | // dequeueMin copies the lowest element of the map (the one pointed at by 155 | // begin()) and then removes it. 156 | template 157 | T BoundedPriorityQueue::dequeueMin() { 158 | // Copy the best value. 159 | T result = elems.begin()->second; 160 | 161 | // Remove it from the map. 162 | elems.erase(elems.begin()); 163 | 164 | return result; 165 | } 166 | 167 | // size() and empty() call directly down to the underlying map. 168 | template 169 | size_t BoundedPriorityQueue::size() const { 170 | return elems.size(); 171 | } 172 | 173 | template 174 | bool BoundedPriorityQueue::empty() const { 175 | return elems.empty(); 176 | } 177 | 178 | // maxSize just returns the appropriate data member. 179 | template 180 | size_t BoundedPriorityQueue::maxSize() const { 181 | return maximumSize; 182 | } 183 | 184 | // The best() and worst() functions check if the queue is empty, 185 | // and if so return infinity. 186 | template 187 | double BoundedPriorityQueue::best() const { 188 | return empty()? numeric_limits::infinity() : elems.begin()->first; 189 | } 190 | 191 | template 192 | double BoundedPriorityQueue::worst() const { 193 | return empty()? numeric_limits::infinity() : elems.rbegin()->first; 194 | } 195 | 196 | #endif // BOUNDED_PQUEUE_INCLUDED 197 | -------------------------------------------------------------------------------- /assignments/KDTree/doc/005_assignment_3_kdtree.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/assignments/KDTree/doc/005_assignment_3_kdtree.pdf -------------------------------------------------------------------------------- /assignments/KDTree/kd_tree.h: -------------------------------------------------------------------------------- 1 | /** 2 | * File: kd_tree.h 3 | * Author: (your name here) 4 | * ------------------------ 5 | * An interface representing a kd-tree in some number of dimensions. The tree 6 | * can be constructed from a set of data and then queried for membership and 7 | * nearest neighbors. 8 | */ 9 | 10 | #ifndef KDTREE_INCLUDED 11 | #define KDTREE_INCLUDED 12 | 13 | #include 14 | #include "point.h" 15 | #include "math.h" 16 | #include "bounded_priority_queue.h" 17 | 18 | // "using namespace" in a header file is conventionally frowned upon, but I'm 19 | // including it here so that you may use things like size_t without having to 20 | // type std::size_t every time. 21 | 22 | template 23 | class KDTree { 24 | public: 25 | struct Node { 26 | Point point; 27 | ElemType element; 28 | Node* left_node; 29 | Node* right_node; 30 | }; 31 | 32 | // Constructor: KDTree(); 33 | // Usage: KDTree<3, int> myTree; 34 | // ---------------------------------------------------- 35 | // Constructs an empty KDTree. 36 | KDTree(); 37 | 38 | // Destructor: ~KDTree() 39 | // Usage: (implicit) 40 | // ---------------------------------------------------- 41 | // Cleans up all resources used by the KDTree. 42 | ~KDTree(); 43 | 44 | // KDTree(const KDTree& rhs); 45 | // KDTree& operator=(const KDTree& rhs); 46 | // Usage: KDTree<3, int> one = two; 47 | // Usage: one = two; 48 | // ----------------------------------------------------- 49 | // Deep-copies the contents of another KDTree into this one. 50 | KDTree(const KDTree& rhs); 51 | KDTree& operator=(const KDTree& rhs); 52 | 53 | // size_t dimension() const; 54 | // Usage: size_t dim = kd.dimension(); 55 | // ---------------------------------------------------- 56 | // Returns the dimension of the points stored in this KDTree. 57 | size_t dimension() const; 58 | 59 | // size_t size() const; 60 | // bool empty() const; 61 | // Usage: if (kd.empty()) 62 | // ---------------------------------------------------- 63 | // Returns the number of elements in the kd-tree and whether the tree is 64 | // empty. 65 | size_t size() const; 66 | bool empty() const; 67 | 68 | // bool contains(const Point& pt) const; 69 | // Usage: if (kd.contains(pt)) 70 | // ---------------------------------------------------- 71 | // Returns whether the specified point is contained in the KDTree. 72 | bool contains(const Point& pt) const; 73 | 74 | // void insert(const Point& pt, const ElemType& value); 75 | // Usage: kd.insert(v, "This value is associated with v."); 76 | // ---------------------------------------------------- 77 | // Inserts the point pt into the KDTree, associating it with the specified 78 | // value. If the element already existed in the tree, the new value will 79 | // overwrite the existing one. 80 | void insert(const Point& pt, const ElemType& value); 81 | 82 | // ElemType& operator[](const Point& pt); 83 | // Usage: kd[v] = "Some Value"; 84 | // ---------------------------------------------------- 85 | // Returns a reference to the value associated with point pt in the KDTree. 86 | // If the point does not exist, then it is added to the KDTree using the 87 | // default value of ElemType as its key. 88 | ElemType& operator[](const Point& pt); 89 | 90 | // ElemType& at(const Point& pt); 91 | // const ElemType& at(const Point& pt) const; 92 | // Usage: cout << kd.at(v) << endl; 93 | // ---------------------------------------------------- 94 | // Returns a reference to the key associated with the point pt. If the point 95 | // is not in the tree, this function throws an out_of_range exception. 96 | ElemType& at(const Point& pt); 97 | const ElemType& at(const Point& pt) const; 98 | 99 | // ElemType kNNValue(const Point& key, size_t k) const 100 | // Usage: cout << kd.kNNValue(v, 3) << endl; 101 | // ---------------------------------------------------- 102 | // Given a point v and an integer k, finds the k points in the KDTree 103 | // nearest to v and returns the most common value associated with those 104 | // points. In the event of a tie, one of the most frequent value will be 105 | // chosen. 106 | ElemType kNNValue(const Point& key, size_t k) const; 107 | 108 | private: 109 | // TODO: Add implementation details here. 110 | 111 | }; 112 | 113 | #endif // KDTREE_INCLUDED 114 | -------------------------------------------------------------------------------- /assignments/KDTree/point.h: -------------------------------------------------------------------------------- 1 | /** 2 | * File: Point.h 3 | * ------------- 4 | * A class representing a point in N-dimensional space. Unlike the other class 5 | * templates you've seen before, Point is parameterized over an integer rather 6 | * than a type. This allows the compiler to verify that the type is being used 7 | * correctly. 8 | */ 9 | #ifndef POINT_INCLUDED 10 | #define POINT_INCLUDED 11 | 12 | #include 13 | 14 | template 15 | class Point { 16 | public: 17 | // Type: iterator 18 | // Type: const_iterator 19 | // ------------------------------------------------------------------------ 20 | // Types representing iterators that can traverse and optionally modify the 21 | // elements of the Point. 22 | typedef double* iterator; 23 | typedef const double* const_iterator; 24 | 25 | // size_t size() const; 26 | // Usage: for (size_t i = 0; i < myPoint.size(); ++i) 27 | // ------------------------------------------------------------------------ 28 | // Returns N, the dimension of the point. 29 | size_t size() const; 30 | 31 | // double& operator[](size_t index); 32 | // double operator[](size_t index) const; 33 | // Usage: myPoint[3] = 137; 34 | // ------------------------------------------------------------------------ 35 | // Queries or retrieves the value of the point at a particular point. The 36 | // index is assumed to be in-range. 37 | double& operator[](size_t index); 38 | double operator[](size_t index) const; 39 | 40 | // iterator begin(); 41 | // iterator end(); 42 | // const_iterator begin() const; 43 | // const_iterator end() const; 44 | // Usage: for (Point<3>::iterator itr = myPoint.begin(); itr != myPoint.end(); ++itr) 45 | // ------------------------------------------------------------------------ 46 | // Returns iterators delineating the full range of elements in the Point. 47 | iterator begin(); 48 | iterator end(); 49 | 50 | const_iterator begin() const; 51 | const_iterator end() const; 52 | 53 | private: 54 | // The point's actual coordinates are stored in an array. 55 | double coords[N]; 56 | }; 57 | 58 | // double Distance(const Point& one, const Point& two); 59 | // Usage: double d = Distance(one, two); 60 | // ---------------------------------------------------------------------------- 61 | // Returns the Euclidean distance between two points. 62 | template 63 | double Distance(const Point& one, const Point& two); 64 | 65 | // bool operator==(const Point& one, const Point& two); 66 | // bool operator!=(const Point& one, const Point& two); 67 | // Usage: if (one == two) 68 | // ---------------------------------------------------------------------------- 69 | // Returns whether two points are equal or not equal. 70 | template 71 | bool operator==(const Point& one, const Point& two); 72 | 73 | template 74 | bool operator!=(const Point& one, const Point& two); 75 | 76 | /** Point class implementation details */ 77 | 78 | #include 79 | 80 | template 81 | size_t Point::size() const { 82 | return N; 83 | } 84 | 85 | template 86 | double& Point::operator[] (size_t index) { 87 | return coords[index]; 88 | } 89 | 90 | template 91 | double Point::operator[] (size_t index) const { 92 | return coords[index]; 93 | } 94 | 95 | template 96 | typename Point::iterator Point::begin() { 97 | return coords; 98 | } 99 | 100 | template 101 | typename Point::const_iterator Point::begin() const { 102 | return coords; 103 | } 104 | 105 | template 106 | typename Point::iterator Point::end() { 107 | return begin() + size(); 108 | } 109 | 110 | template 111 | typename Point::const_iterator Point::end() const { 112 | return begin() + size(); 113 | } 114 | 115 | // Computing the distance uses the standard distance formula: the square root of 116 | // the sum of the squares of the differences between matching components. 117 | template 118 | double Distance(const Point& one, const Point& two) { 119 | double result = 0.0; 120 | for (size_t i = 0; i < N; ++i) 121 | result += (one[i] - two[i]) * (one[i] - two[i]); 122 | 123 | return sqrt(result); 124 | } 125 | 126 | // Equality is implemented using the equal algorithm, which takes in two ranges 127 | // and reports whether they contain equal values. 128 | template 129 | bool operator==(const Point& one, const Point& two) { 130 | return std::equal(one.begin(), one.end(), two.begin()); 131 | } 132 | 133 | template 134 | bool operator!=(const Point& one, const Point& two) { 135 | return !(one == two); 136 | } 137 | 138 | #endif // POINT_INCLUDED 139 | -------------------------------------------------------------------------------- /slides/Spring2023/lec12_special_member_functions_s23.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Spring2023/lec12_special_member_functions_s23.pdf -------------------------------------------------------------------------------- /slides/Spring2023/lecture10_functions_and_lambdas_s23.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Spring2023/lecture10_functions_and_lambdas_s23.pdf -------------------------------------------------------------------------------- /slides/Spring2023/lecture11_operators_s23.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Spring2023/lecture11_operators_s23.pdf -------------------------------------------------------------------------------- /slides/Spring2023/lecture13_move_semantics_s23.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Spring2023/lecture13_move_semantics_s23.pdf -------------------------------------------------------------------------------- /slides/Spring2023/lecture14_typesafety_s23.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Spring2023/lecture14_typesafety_s23.pdf -------------------------------------------------------------------------------- /slides/Spring2023/lecture16_special_topics_s23.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Spring2023/lecture16_special_topics_s23.pdf -------------------------------------------------------------------------------- /slides/Spring2023/lecture1_welcome_s23.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Spring2023/lecture1_welcome_s23.pdf -------------------------------------------------------------------------------- /slides/Spring2023/lecture2_typesandstructs_s23.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Spring2023/lecture2_typesandstructs_s23.pdf -------------------------------------------------------------------------------- /slides/Spring2023/lecture3_initandref_s23.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Spring2023/lecture3_initandref_s23.pdf -------------------------------------------------------------------------------- /slides/Spring2023/lecture4_streams_s23.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Spring2023/lecture4_streams_s23.pdf -------------------------------------------------------------------------------- /slides/Spring2023/lecture5_containers_s23.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Spring2023/lecture5_containers_s23.pdf -------------------------------------------------------------------------------- /slides/Spring2023/lecture6_iterators_and_pointers_s23.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Spring2023/lecture6_iterators_and_pointers_s23.pdf -------------------------------------------------------------------------------- /slides/Spring2023/lecture7_classes_s23.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Spring2023/lecture7_classes_s23.pdf -------------------------------------------------------------------------------- /slides/Spring2023/lecture8_templateclassesconst_s23.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Spring2023/lecture8_templateclassesconst_s23.pdf -------------------------------------------------------------------------------- /slides/Spring2023/lecture9_template_functions_s23.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Spring2023/lecture9_template_functions_s23.pdf -------------------------------------------------------------------------------- /slides/Winter2021/WL10_Temp_classes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Winter2021/WL10_Temp_classes.pdf -------------------------------------------------------------------------------- /slides/Winter2021/WL11_Const.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Winter2021/WL11_Const.pdf -------------------------------------------------------------------------------- /slides/Winter2021/WL12_Operators.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Winter2021/WL12_Operators.pdf -------------------------------------------------------------------------------- /slides/Winter2021/WL13_SMF.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Winter2021/WL13_SMF.pdf -------------------------------------------------------------------------------- /slides/Winter2021/WL14-Move.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Winter2021/WL14-Move.pdf -------------------------------------------------------------------------------- /slides/Winter2021/WL15_RAII.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Winter2021/WL15_RAII.pdf -------------------------------------------------------------------------------- /slides/Winter2021/WL16-Wrapup.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Winter2021/WL16-Wrapup.pdf -------------------------------------------------------------------------------- /slides/Winter2021/WL2-Structures.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Winter2021/WL2-Structures.pdf -------------------------------------------------------------------------------- /slides/Winter2021/WL4_Streams.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Winter2021/WL4_Streams.pdf -------------------------------------------------------------------------------- /slides/Winter2021/WL5_Containers.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Winter2021/WL5_Containers.pdf -------------------------------------------------------------------------------- /slides/Winter2021/WL6_Iterators.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Winter2021/WL6_Iterators.pdf -------------------------------------------------------------------------------- /slides/Winter2021/WL7_Templates.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Winter2021/WL7_Templates.pdf -------------------------------------------------------------------------------- /slides/Winter2021/WL8_Functions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Winter2021/WL8_Functions.pdf -------------------------------------------------------------------------------- /slides/Winter2021/WL9-STL-Summary.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Winter2021/WL9-STL-Summary.pdf -------------------------------------------------------------------------------- /slides/Winter2021/WLecture1_intro.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Winter2021/WLecture1_intro.pdf -------------------------------------------------------------------------------- /slides/Winter2021/WLecture_3_Init_and_Ref.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Winter2021/WLecture_3_Init_and_Ref.pdf -------------------------------------------------------------------------------- /slides/Winter2021/Welcome to C++!.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/slides/Winter2021/Welcome to C++!.pdf -------------------------------------------------------------------------------- /solutions/GapBuffer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | cmake_minimum_required(VERSION 3.14) 3 | project(gap_buffer) 4 | 5 | # GoogleTest requires at least C++14 6 | set(CMAKE_CXX_STANDARD 14) 7 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 8 | 9 | include(FetchContent) 10 | FetchContent_Declare( 11 | googletest 12 | URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip 13 | ) 14 | # For Windows: Prevent overriding the parent project's compiler/linker settings 15 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 16 | FetchContent_MakeAvailable(googletest) 17 | 18 | enable_testing() 19 | 20 | add_executable( 21 | gap_buffer_test 22 | gap_buffer_test.cc 23 | ) 24 | target_link_libraries( 25 | gap_buffer_test 26 | GTest::gtest_main 27 | ) 28 | 29 | include(GoogleTest) 30 | gtest_discover_tests(gap_buffer_test) -------------------------------------------------------------------------------- /solutions/GapBuffer/gap_buffer.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef GAPBUFFER_H 3 | #define GAPBUFFER_H 4 | #include 5 | #include // for cout in debug 6 | #include 7 | #include // for stringstreams 8 | #include // for unique_ptr 9 | 10 | const int kDefaultSize = 10; 11 | 12 | // forward declaration for the GapBufferIterator class 13 | template 14 | class GapBufferIterator; 15 | 16 | // declaration for the GapBuffer class 17 | template 18 | class GapBuffer { 19 | public: 20 | friend class GapBufferIterator; 21 | 22 | using value_type = T; 23 | using size_type = size_t; 24 | using difference_type = ptrdiff_t; 25 | using reference = value_type&; 26 | using const_reference = const value_type&; 27 | using pointer = value_type*; 28 | using iterator = GapBufferIterator; 29 | 30 | explicit GapBuffer(); 31 | explicit GapBuffer(size_type count, const value_type& val = value_type()); 32 | ~GapBuffer(); 33 | GapBuffer(std::initializer_list init); 34 | GapBuffer(const GapBuffer& other); 35 | GapBuffer(GapBuffer&& other); 36 | GapBuffer& operator=(const GapBuffer& rhs); 37 | GapBuffer& operator=(GapBuffer&& rhs); 38 | 39 | void insert_at_cursor(const_reference element); 40 | void insert_at_cursor(value_type&& element); 41 | template 42 | void emplace_at_cursor(Args&&... args); // optional 43 | void delete_at_cursor(); 44 | reference get_at_cursor(); 45 | const_reference get_at_cursor() const; 46 | reference operator[](size_type pos); 47 | const_reference operator[](size_type pos) const; 48 | reference at(size_type pos); 49 | const_reference at(size_type pos) const; 50 | void move_cursor(int num); 51 | void reserve(size_type new_size); 52 | size_type size() const; 53 | size_type cursor_index() const; 54 | bool empty() const; 55 | void debug() const; 56 | 57 | iterator begin(); 58 | iterator end(); 59 | iterator cursor(); 60 | 61 | private: 62 | size_type _logical_size; // uses external_index 63 | size_type _buffer_size; // uses array_index 64 | size_type _cursor_index; // uses array_index 65 | size_type _gap_size; 66 | pointer _elems; // uses array_index 67 | 68 | size_type to_external_index(size_type array_index) const; 69 | size_type to_array_index(size_type external_index) const; 70 | void move_to_left_of_buffer(size_type num); 71 | }; 72 | 73 | // Class declaration of the GapBufferIterator class 74 | template 75 | class GapBufferIterator : public std::iterator { 76 | public: 77 | friend class GapBuffer; 78 | using value_type = T; 79 | using size_type = size_t; 80 | using difference_type = ptrdiff_t; 81 | using reference = value_type&; 82 | using iterator = GapBufferIterator; 83 | 84 | reference operator*(); 85 | iterator& operator++(); 86 | iterator operator++(int); 87 | iterator& operator--(); 88 | iterator operator--(int); 89 | 90 | // the operators below are implemented as non-friend non-members 91 | // iterator operator+(const iterator& lhs, size_type diff); 92 | // iterator operator-(const iterator& lhs, size_type diff); 93 | // iterator operator+(size_type diff, const iterator& rhs); 94 | 95 | /* we implemented these for you. don't change these */ 96 | friend bool operator==(const iterator& lhs, const iterator& rhs) { return lhs._index == rhs._index; } 97 | friend bool operator!=(const iterator& lhs, const iterator& rhs) { return !(lhs == rhs); } 98 | friend difference_type operator-(const iterator& lhs, const iterator& rhs) { return lhs._index - rhs._index; } 99 | iterator& operator+=(size_type diff) { _index += diff; return *this; } 100 | iterator& operator-=(size_type diff) { _index -= diff; return *this; } 101 | friend bool operator>(const iterator& lhs, const iterator& rhs) { return lhs._index < rhs._index; } 102 | friend bool operator<(const iterator& lhs, const iterator& rhs) { return lhs._index > rhs._index; } 103 | friend bool operator<=(const iterator& lhs, const iterator& rhs) { return !(lhs > rhs); } 104 | friend bool operator>=(const iterator& lhs, const iterator& rhs) { return !(lhs < rhs); } 105 | reference operator[](size_t index) { return *(*this + index); } 106 | 107 | private: 108 | GapBuffer* _pointee; 109 | size_t _index; 110 | GapBufferIterator(GapBuffer* pointee, size_t index) : _pointee(pointee), _index(index) {} 111 | }; 112 | 113 | // Part 1: basic functions 114 | template 115 | GapBuffer::GapBuffer(): 116 | _logical_size(0), 117 | _buffer_size(kDefaultSize), 118 | _cursor_index(0), 119 | _gap_size(kDefaultSize), 120 | _elems(new T[kDefaultSize]){ 121 | 122 | } 123 | 124 | template 125 | GapBuffer::GapBuffer(size_type count, const value_type& val): 126 | _logical_size(count), 127 | _buffer_size(2*count), 128 | _cursor_index(count), 129 | _gap_size(count), 130 | _elems(new T[2*count]){ 131 | for (size_type i = 0; i < count; i++){ 132 | _elems[i] = val; 133 | } 134 | } 135 | 136 | template 137 | void GapBuffer::insert_at_cursor(const_reference element) { 138 | if (_gap_size == 0) reserve(2*_buffer_size); 139 | _elems[_cursor_index] = element; 140 | _logical_size++; 141 | _cursor_index++; 142 | _gap_size--; 143 | } 144 | 145 | template 146 | void GapBuffer::delete_at_cursor() { 147 | if (_cursor_index == 0) return; 148 | _logical_size--; 149 | _cursor_index--; 150 | _gap_size++; 151 | } 152 | 153 | template 154 | typename GapBuffer::reference GapBuffer::get_at_cursor() { 155 | // TODO: implement this function (~1 line long) 156 | // Hint: check out the indexing helper functions we provide 157 | // Be sure to use the static_cast/const_cast trick here after implementing the const-version. 158 | if (_cursor_index == _logical_size) throw std::out_of_range("Cursor is out of range"); 159 | return _elems[to_array_index(_cursor_index)]; 160 | } 161 | 162 | template 163 | typename GapBuffer::reference GapBuffer::at(size_type pos) { 164 | if (pos >= _logical_size) throw std::out_of_range("The specified position is out of range"); 165 | return _elems[to_array_index(pos)]; 166 | } 167 | 168 | template 169 | typename GapBuffer::size_type GapBuffer::size() const { 170 | return _logical_size; 171 | } 172 | 173 | template 174 | typename GapBuffer::size_type GapBuffer::cursor_index() const { 175 | return _cursor_index; 176 | } 177 | 178 | template 179 | bool GapBuffer::empty() const { 180 | return _logical_size == 0; 181 | } 182 | 183 | // Part 2: const-correctness 184 | 185 | template 186 | typename GapBuffer::const_reference GapBuffer::get_at_cursor() const { 187 | return static_cast (const_cast*>(this)->get_at_cursor()); 188 | } 189 | 190 | template 191 | typename GapBuffer ::const_reference GapBuffer::at(size_type pos) const { 192 | return static_cast(const_cast*>(this)->at(pos)); 193 | } 194 | 195 | // Part 3: operator overloading 196 | template 197 | typename GapBuffer::reference GapBuffer::operator[](size_type pos) { 198 | return at(pos); 199 | } 200 | 201 | template 202 | typename GapBuffer::const_reference GapBuffer::operator[](size_type pos) const { 203 | return at(pos); 204 | } 205 | 206 | template 207 | std::ostream& operator<<(std::ostream& os, const GapBuffer& buf) { 208 | os << "{"; 209 | for (size_t i = 0; i < buf.size(); i++) { 210 | if (i != 0) os << " "; 211 | if (buf.cursor_index() == i) os << "^"; 212 | os << buf[i]; 213 | if (i < buf.size() - 1) os << ","; 214 | } 215 | if (buf.cursor_index() == buf.size()) os << "^"; 216 | os << "}"; 217 | return os; 218 | } 219 | 220 | template 221 | bool operator==(const GapBuffer& lhs, const GapBuffer& rhs) { 222 | // TODO: implement this operator (~1 line long) 223 | // Hint: std::equal can be used after you implement iterators 224 | if (lhs.size() != rhs.size()) return false; 225 | for (size_t i = 0; i < lhs.size(); i++) { 226 | if (rhs[i] != lhs[i]) return false; 227 | } 228 | return true; 229 | } 230 | 231 | template 232 | bool operator!=(const GapBuffer& lhs, const GapBuffer& rhs) { 233 | return !(lhs == rhs); 234 | } 235 | 236 | template 237 | bool operator<(const GapBuffer& lhs, const GapBuffer& rhs) { 238 | // TODO: implement this operator (~3 lines long) 239 | // Hint: std::lexicographical_compare can be used after you implement iterators. 240 | 241 | // Hint: if you get warnings about const_iterator, you can try using const_cast 242 | // as a hack to cast away the const-ness of your parameter. 243 | // auto& lhs_nonconst = const_cast&>(lhs); 244 | // auto& rhs_nonconst = const_cast&>(lhs); 245 | // use lhs_nonconst.begin(), etc. 246 | size_t min_size = std::min(lhs.size(), rhs.size()); 247 | for (size_t i = 0; i < min_size; i++) { 248 | if (lhs[i] != rhs[i]) return lhs[i] < rhs[i]; 249 | } 250 | return lhs.size() < rhs.size(); 251 | } 252 | 253 | template 254 | bool operator>(const GapBuffer& lhs, const GapBuffer& rhs) { 255 | size_t min_size = std::min(lhs.size(), rhs.size()); 256 | for (size_t i = 0; i < min_size; i++) { 257 | if (lhs[i] != rhs[i]) return lhs[i] > rhs[i]; 258 | } 259 | return lhs.size() > rhs.size(); 260 | } 261 | 262 | template 263 | bool operator<=(const GapBuffer& lhs, const GapBuffer& rhs) { 264 | // TODO: implement this operator (~1 line long) 265 | return !(lhs > rhs); 266 | } 267 | 268 | template 269 | bool operator>=(const GapBuffer& lhs, const GapBuffer& rhs) { 270 | // TODO: implement this operator (~1 line long) 271 | return !(lhs < rhs); 272 | } 273 | 274 | // Part 4: turn everything into a template! 275 | 276 | 277 | // Part 5: Implement iterators 278 | template 279 | typename GapBufferIterator::reference GapBufferIterator::operator*() { 280 | // TODO: implement this operator (~1 line long) 281 | return _pointee->at(_index); 282 | } 283 | 284 | template 285 | GapBufferIterator& GapBufferIterator::operator++() { 286 | // TODO: implement this prefix operator (~2 lines long) 287 | _index++; 288 | return *this; 289 | } 290 | 291 | template 292 | GapBufferIterator GapBufferIterator::operator++(int) { 293 | // TODO: implement this postfix operator (~3 lines long) 294 | iterator temp = *this; 295 | _index++; 296 | return temp; 297 | } 298 | 299 | template 300 | GapBufferIterator& GapBufferIterator::operator--() { 301 | // TODO: implement this prefix operator (~2 lines long) 302 | _index--; 303 | return *this; 304 | } 305 | 306 | template 307 | GapBufferIterator GapBufferIterator::operator--(int) { 308 | // TODO: implement this postfix operator (~3 lines long) 309 | iterator temp = *this; 310 | _index--; 311 | return temp; 312 | } 313 | 314 | template 315 | GapBufferIterator operator+(const GapBufferIterator& lhs, 316 | typename GapBufferIterator::size_type diff) { 317 | // TODO: implement this operator (~3 lines long) 318 | // Note: this operator is not a friend of the GapBufferIterator class 319 | // Hint: write the operator in terms of += 320 | GapBufferIterator temp = lhs; 321 | temp += diff; 322 | return temp; 323 | } 324 | 325 | template 326 | GapBufferIterator operator+(typename GapBufferIterator::size_type diff, 327 | const GapBufferIterator& rhs) { 328 | // TODO: implement this operator (~1 line long) 329 | // Note: this operator is not a friend of the GapBufferIterator class 330 | // Hint: write the operator in terms of the operator+ you wrote above. 331 | return rhs + diff; 332 | } 333 | 334 | template 335 | GapBufferIterator operator-(const GapBufferIterator& lhs, 336 | typename GapBufferIterator::size_type diff) { 337 | // TODO: implement this operator (~3 lines long) 338 | // Note: this operator is not a friend of the GapBufferIterator class 339 | // Hint: write the operator in terms of -= 340 | GapBufferIterator temp = lhs; 341 | temp -= diff; 342 | return temp; 343 | } 344 | 345 | // The functions that are part of the GapBuffer class is provided for you! 346 | template 347 | typename GapBuffer::iterator GapBuffer::begin() { 348 | return iterator(this, 0); 349 | } 350 | 351 | template 352 | typename GapBuffer::iterator GapBuffer::end() { 353 | return iterator(this, _logical_size); 354 | } 355 | 356 | template 357 | typename GapBuffer::iterator GapBuffer::cursor() { 358 | return iterator(this, _cursor_index); 359 | } 360 | 361 | // Part 6: Constructors and assignment 362 | 363 | template 364 | GapBuffer::~GapBuffer() { 365 | delete [] _elems; 366 | } 367 | template 368 | GapBuffer::GapBuffer(std::initializer_list init): 369 | _logical_size(init.size()), 370 | _buffer_size(2 * init.size()), 371 | _cursor_index(init.size()), 372 | _gap_size(init.size()), 373 | _elems(new T[2*init.size()]) { 374 | std::copy(init.begin(), init.end(), begin()); 375 | } 376 | 377 | template 378 | GapBuffer::GapBuffer(const GapBuffer& other): 379 | _logical_size(other._logical_size), 380 | _buffer_size(other._buffer_size), 381 | _cursor_index(other._cursor_index), 382 | _gap_size(other._gap_size), 383 | _elems(new T[other._buffer_size]) { 384 | std::copy(other._elems, other._elems + other._buffer_size, _elems); 385 | } 386 | 387 | template 388 | GapBuffer& GapBuffer::operator=(const GapBuffer& rhs) { 389 | // TODO: implement this copy assignment operator (~8 lines long) 390 | if (this == &rhs) return *this; 391 | _logical_size = rhs._logical_size; 392 | _buffer_size = rhs._buffer_size; 393 | _cursor_index = rhs._cursor_index; 394 | _gap_size = rhs._gap_size; 395 | delete [] _elems; 396 | _elems = new T[_buffer_size]; 397 | std::copy(rhs._elems, rhs._elems + rhs._buffer_size, _elems); 398 | return *this; 399 | } 400 | 401 | // Part 7: Move semantics 402 | template 403 | GapBuffer::GapBuffer(GapBuffer&& other) { 404 | // TODO: implement this move constructor (~4 lines long) 405 | // use initializer list! 406 | 407 | // Hint: if you get warnings about const_iterator, you can try using const_cast 408 | // as a hack to cast away the const-ness of your parameter. 409 | // auto& other_nonconst = const_cast&>(other); 410 | // use other.begin(), etc. 411 | _logical_size = std::move(other._logical_size); 412 | _buffer_size = std::move(other._buffer_size); 413 | _cursor_index = std::move(other._cursor_index); 414 | _gap_size = std::move(other._gap_size); 415 | _elems = std::move(other._elems); 416 | other._elems = nullptr; 417 | } 418 | 419 | template 420 | GapBuffer& GapBuffer::operator=(GapBuffer&& rhs) { 421 | // TODO: implement this move assignment operator (~7 lines long) 422 | 423 | // Hint: if you get warnings about const_iterator, you can try using const_cast 424 | // as a hack to cast away the const-ness of your parameter. 425 | // auto& rhs_nonconst = const_cast&>(rhs); 426 | // use rhs.begin(), etc. 427 | if (this == &rhs) return *this; 428 | _logical_size = std::move(rhs._logical_size); 429 | _buffer_size = std::move(rhs._buffer_size); 430 | _cursor_index = std::move(rhs._cursor_index); 431 | _gap_size = std::move(rhs._gap_size); 432 | delete [] _elems; 433 | _elems = std::move(rhs._elems); 434 | rhs._elems = nullptr; 435 | return *this; 436 | } 437 | 438 | template 439 | void GapBuffer::insert_at_cursor(value_type&& element) { 440 | // TODO: implement this insert function (takes in an r-value) (~7 lines long) 441 | //insert_at_cursor(element); // by default, calls the l-value version above 442 | // when you are ready to implement, remove the insert_at_cursor call. 443 | if (_gap_size == 0) { 444 | reserve(2*_buffer_size); 445 | } 446 | _elems[_cursor_index] = std::move(element); 447 | _cursor_index++; 448 | _logical_size++; 449 | _gap_size--; 450 | } 451 | 452 | // Part 8: Make your code RAII-compliant - change the code throughout 453 | 454 | // optional: 455 | template 456 | template 457 | void GapBuffer::emplace_at_cursor(Args&&... args) { 458 | // TODO: optional: implement function 459 | // remember to perfectly forward the arguments to the constructor of T. 460 | 461 | } 462 | 463 | 464 | // We've implemented the following functions for you. 465 | // However...they do use raw pointers, so you might want to turn them into smart pointers! 466 | template 467 | void GapBuffer::move_cursor(int delta) { 468 | int new_index = _cursor_index + delta; 469 | if (new_index < 0 || new_index > static_cast(_buffer_size)) { 470 | throw std::string("move_cursor: delta moves cursor out of bounds"); 471 | } 472 | if (delta > 0) { 473 | auto begin_move = _elems + _cursor_index + _gap_size; 474 | auto end_move = begin_move + delta; 475 | auto destination = _elems + _cursor_index; 476 | std::move(begin_move, end_move, destination); 477 | } else { 478 | auto end_move = _elems + _cursor_index; 479 | auto begin_move = end_move + delta; 480 | auto* destination = _elems + _cursor_index + _gap_size + delta; 481 | std::move(begin_move, end_move, destination); 482 | } 483 | _cursor_index += delta; 484 | } 485 | 486 | template 487 | void GapBuffer::reserve(size_type new_size) { 488 | if (_logical_size >= new_size) return; 489 | auto new_elems = new T[new_size]; 490 | std::move(_elems, _elems + _cursor_index, new_elems); 491 | size_t new_gap_size = new_size - _logical_size; 492 | std::move(_elems + _buffer_size - _logical_size + _cursor_index, 493 | _elems + _buffer_size, 494 | new_elems + _cursor_index + new_gap_size); 495 | _buffer_size = new_size; 496 | delete [] _elems; 497 | _elems = std::move(new_elems); 498 | _gap_size = new_gap_size; 499 | } 500 | 501 | template 502 | void GapBuffer::debug() const { 503 | std::cout << "["; 504 | for (size_t i = 0; i < _buffer_size; ++i) { 505 | if (i == _cursor_index) { 506 | std::cout << "|"; 507 | } else { 508 | std::cout << " "; 509 | } 510 | if (i >= _cursor_index && i < _cursor_index + _gap_size) { 511 | std::cout << "*"; 512 | } else { 513 | std::cout << _elems[i]; 514 | } 515 | } 516 | std::cout << (_cursor_index == _buffer_size ? "|" : " "); 517 | std::cout << "]" << std::endl; 518 | } 519 | 520 | template 521 | typename GapBuffer::size_type GapBuffer::to_external_index(size_type array_index) const { 522 | if (array_index < _cursor_index) { 523 | return array_index; 524 | } else if (array_index >= _cursor_index + _gap_size){ 525 | return array_index - _cursor_index; 526 | } else { 527 | throw ("to_external_index: array_index is out of bounds!"); 528 | } 529 | } 530 | 531 | template 532 | typename GapBuffer::size_type GapBuffer::to_array_index(size_type external_index) const { 533 | if (external_index < _cursor_index) { 534 | return external_index; 535 | } else { 536 | return external_index + _gap_size; 537 | } 538 | } 539 | 540 | 541 | #endif // GAPBUFFER_H 542 | -------------------------------------------------------------------------------- /solutions/HashMap/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | cmake_minimum_required(VERSION 3.14) 3 | project(hashmap) 4 | 5 | 6 | set(CMAKE_CXX_STANDARD 17) 7 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 8 | SET(CMAKE_BUILD_TYPE "Debug") 9 | 10 | SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g2 -ggdb") 11 | SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -Wall") 12 | 13 | include(FetchContent) 14 | FetchContent_Declare( 15 | googletest 16 | URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip 17 | ) 18 | # For Windows: Prevent overriding the parent project's compiler/linker settings 19 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 20 | FetchContent_MakeAvailable(googletest) 21 | 22 | enable_testing() 23 | 24 | add_executable( 25 | hashmap_test 26 | hashmap_test.cpp 27 | ) 28 | target_link_libraries( 29 | hashmap_test 30 | GTest::gtest_main 31 | ) 32 | 33 | add_executable( 34 | hashmap_perf 35 | hashmap_perf.cpp 36 | ) 37 | 38 | target_link_libraries( 39 | hashmap_perf 40 | GTest::gtest_main 41 | ) 42 | 43 | include(GoogleTest) 44 | gtest_discover_tests(hashmap_test) -------------------------------------------------------------------------------- /solutions/HashMap/answers.md: -------------------------------------------------------------------------------- 1 | 2 | # 1.Templates and Template Classes 3 | Q: In the rehash() function, the for-each loop contains an auto&. What is the deduced type of that auto, and why the ampersand necessary? 4 | 5 | A1: The deduced type of auto is (Node *); 6 | 7 | A2: The ampersand is necessary because we need to modify the content stored in the _bucket_array. 8 | 9 | # 2.HashMap Pair Type 10 | 11 | Q: STL containers store elements of type value_type, and for your HashMap this value_type is a std::pair. What would be the problem in the HashMap class if value_type were instead std::pair? Hint: think about the following lines of code: 12 | ```c++ 13 | HashMap map; 14 | map.insert({"Avery", 3}); 15 | auto iter = map.begin(); 16 | iter->first = "Anna"; 17 | auto anna_iter = map.find("Anna"); 18 | ``` 19 | 20 | A: The key should not be modified because we use the hash value of the key to find the correct bucket. 21 | 22 | # 3.Find vs find 23 | Q : In addition to the HashMap::find member function, there is also a std::find function in the STL algorithms library. 24 | If you were searching for key k in HashMap m, is it preferable to call m.find(k) or std::find(m.begin(), m.end(), k)? 25 | A : It is preferable to call m.find(k) because the amortized complexity is O(1) while std::find is O(N). 26 | 27 | # 4.RAII? 28 | Q : This HashMap class is RAII-compliant. Explain why. 29 | A : Because the destructor function will call the HashMap::clear() which free all the resources which HashMap acquires. 30 | 31 | # 5.Privacy 32 | Q : Why is the HashMapIterator's constructor private? How do HashMapIterators get constructed if the constructor is private? 33 | A : Because to construct a HashMapIterator, you need to have access to the private attribute of the HashMap. Since HashMapIterator is 34 | the friend class of HashMap, so HashMap has access to the private constructor of HashMapIterator. 35 | 36 | # 6.Increments 37 | Q : Briefly explain the implementation of HashMapIterator's operator++, which we provide for you. How does it work and what checks does it have? 38 | A : For the prefix ++, we first check the next node, if it is nullptr, which means the end of this bucket is reached, we find the next bucket which contains value. 39 | Otherwise, the next node is returned. For the postfix ++, we first copy the current iterator, then call the prefix ++ to increment the iterator and return the copied iterator. 40 | 41 | # 7.Did We Make A Mistake? 42 | Q : Why is there both a const and non-const version of at(), but only a non-const version of operator[]? 43 | (unlike in Vector where operator[] also had a const version). 44 | A : Because a key/mapped value pair will be added to the HashMap if no such key exists, which means will modify the HashMap. 45 | So the operator[] does not need a const version. 46 | 47 | # 8.Now Streaming on iTunes 48 | Q : Look at the function signature for the stream insertion (operator<<) in hashmap.cpp. 49 | Briefly explain the syntax for this function signature and how this works. 50 | A : The two arguments are both const to avoid copying. Ostream argument should not be constant because we will modify it. 51 | We return the reference to the modified ostream to support ostream chaining. 52 | 53 | # 9.Attachment Issues 54 | Q : Why is it that we need to implement the special member functions in the HashMap class, 55 | but we can default all the special member functions in the HashMapIterator class? 56 | A : For HashMapIterator, all its attributes are values, we can use the default SMF to copy value directly. However, for HashMap, 57 | we need to manage pointers to allocated to memory, so we implement the SMF ourselves; 58 | 59 | # 10.Move Semantics 60 | Q : In your move constructor or move assignment operator, why did you have to std::move each member, 61 | even though the parameter (named rhs) is already an r-value reference? 62 | A : Because the attributes of the rhs may not be r-value, and assign it to the lhs's corresponding attribute may trigger copy instead of move, so we need to use std::move. 63 | 64 | -------------------------------------------------------------------------------- /solutions/HashMap/hashmap.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "hashmap.h" 3 | 4 | template 5 | HashMap::HashMap() : _size(0), _hash_function(H()), _buckets_array(kDefaultBuckets, nullptr) {}; 6 | 7 | template 8 | HashMap::HashMap(size_t bucket_count, const H& hash): 9 | _size(0), 10 | _hash_function(hash), 11 | _buckets_array(bucket_count, nullptr) {}; 12 | 13 | template 14 | HashMap::~HashMap() { 15 | clear(); 16 | } 17 | 18 | template 19 | inline size_t HashMap::size() const { 20 | return _size; 21 | } 22 | 23 | template 24 | inline bool HashMap::empty() const { 25 | return _size == 0; 26 | } 27 | 28 | template 29 | inline float HashMap::load_factor() const { 30 | return ((float) _size) / _buckets_array.size(); 31 | } 32 | 33 | template 34 | inline size_t HashMap::bucket_count() const { 35 | return _buckets_array.size(); 36 | } 37 | 38 | template 39 | bool HashMap::contains(const K& key) const { 40 | auto [pre_node, cur_node] = find_node(key); 41 | return cur_node != nullptr; 42 | 43 | } 44 | 45 | template 46 | M& HashMap::at(const K& key) { 47 | auto [pre_node, cur_node] = find_node(key); 48 | if (cur_node == nullptr) throw std::out_of_range("HashMap::at: key not found"); 49 | auto& [cur_key, cur_value] = cur_node->value; 50 | return cur_value; 51 | } 52 | 53 | template 54 | const M& HashMap::at(const K& key) const { 55 | return static_cast(const_cast *>(this)->at(key)); 56 | } 57 | 58 | template 59 | void HashMap::clear() { 60 | for (auto& bucket : _buckets_array) { 61 | while (bucket != nullptr) 62 | { 63 | auto temp_bkt = bucket->next; 64 | delete bucket; 65 | bucket = temp_bkt; 66 | } 67 | } 68 | _size = 0; 69 | } 70 | 71 | template 72 | std::pair::iterator, bool> HashMap::insert(const value_type& kv_pair) { 73 | auto [pre_node, cur_node] = find_node(kv_pair.first); 74 | size_t index = _hash_function(kv_pair.first) % _buckets_array.size(); 75 | if (cur_node != nullptr) return {make_iterator(cur_node), false}; 76 | Node* new_node = new Node{kv_pair, nullptr}; 77 | if (pre_node == nullptr) { 78 | _buckets_array[index] = new_node; 79 | } else { 80 | pre_node->next = new_node; 81 | } 82 | ++_size; 83 | return {make_iterator(new_node), true}; 84 | } 85 | 86 | template 87 | bool HashMap::erase(const K& key) { 88 | size_t index = _hash_function(key) % _buckets_array.size(); 89 | auto [pre_node, cur_node] = find_node(key); 90 | if (cur_node == nullptr) return false; 91 | 92 | Node * temp = cur_node->next; 93 | delete cur_node; 94 | if (pre_node != nullptr) { 95 | pre_node->next = temp; 96 | } else { 97 | _buckets_array[index] = temp; 98 | } 99 | 100 | _size--; 101 | return true; 102 | } 103 | 104 | template 105 | typename HashMap::iterator HashMap::erase(const_iterator pos) { 106 | iterator temp = make_iterator(pos._node); 107 | ++temp; 108 | if (pos._node != nullptr) { 109 | erase(pos._node->value.first); 110 | } 111 | return temp; 112 | } 113 | 114 | template 115 | void HashMap::rehash(size_t new_buckets) { 116 | if (new_buckets == 0) throw std::out_of_range("HashMap::rehash: Invalid Input Parameters"); 117 | //if (new_buckets == bucket_count()) return; 118 | bucket_array_type temp_bkt_array = _buckets_array; 119 | 120 | _buckets_array.clear(); 121 | _buckets_array.resize(new_buckets, nullptr); 122 | 123 | for (auto& temp_bkt : temp_bkt_array) { 124 | while (temp_bkt != nullptr) 125 | { 126 | Node* temp = temp_bkt; 127 | temp_bkt = temp_bkt->next; 128 | const auto& [key, mapped] = temp->value; 129 | size_t index = _hash_function(key) % new_buckets; 130 | 131 | temp->next = _buckets_array[index]; 132 | _buckets_array[index] = temp; 133 | } 134 | 135 | } 136 | } 137 | 138 | template 139 | typename HashMap::iterator HashMap::begin() { 140 | size_t index = first_not_empty_bucket(); 141 | return make_iterator(_buckets_array[index]); 142 | } 143 | 144 | template 145 | typename HashMap::const_iterator HashMap::begin() const { 146 | return const_cast *>(this)->begin(); 147 | } 148 | 149 | template 150 | typename HashMap::iterator HashMap::end() { 151 | return make_iterator(nullptr); 152 | } 153 | 154 | template 155 | typename HashMap::const_iterator HashMap::end() const { 156 | return const_cast *>(this)->end(); 157 | } 158 | 159 | template 160 | typename HashMap::iterator HashMap::find(const K& key) { 161 | auto [pre_node, cur_node] = find_node(key); 162 | return make_iterator(cur_node); 163 | } 164 | 165 | template 166 | typename HashMap::const_iterator HashMap::find(const K& key) const { 167 | return const_cast *>(this)->find(key); 168 | } 169 | 170 | template 171 | void HashMap::debug() { 172 | std::cout << "HashMap Debug Info:" << std::endl; 173 | std::cout << "Bucket Count=" << bucket_count() <<" Size=" << size() << " Load Factor="<value.first << "-" << temp->value.second << " "; 180 | temp = temp->next; 181 | } 182 | std::cout << std::endl; 183 | } 184 | } 185 | 186 | template 187 | template 188 | HashMap::HashMap(InputIter begin, InputIter end, size_t bucket_count, const H& hash):HashMap(bucket_count, hash){ 189 | for (InputIter iter = begin; iter != end; iter++) { 190 | insert(*iter); 191 | } 192 | } 193 | 194 | template 195 | HashMap::HashMap(std::initializer_list init, size_t bucket_count, const H& hash): 196 | HashMap(init.begin(), init.end(), bucket_count, hash){} 197 | 198 | template 199 | M& HashMap::operator[](const K& key) { 200 | value_type default_kv{key, {}}; 201 | auto [iter, success] = insert(default_kv); 202 | return iter->second; 203 | } 204 | 205 | template 206 | HashMap::HashMap(const HashMap& map): 207 | _size(0), 208 | _hash_function(map._hash_function), 209 | _buckets_array(map.bucket_count(), nullptr){ 210 | 211 | for (const auto& kv_pair : map) { 212 | insert(kv_pair); 213 | } 214 | } 215 | 216 | template 217 | HashMap::HashMap(HashMap&& map): 218 | _size(std::move(map._size)), 219 | _hash_function(std::move(map._hash_function)), 220 | _buckets_array(std::move(map._buckets_array)) { 221 | 222 | map._buckets_array.resize(_buckets_array.size(), nullptr); 223 | map._size = 0; 224 | } 225 | 226 | template 227 | HashMap& HashMap::operator=(const HashMap& map) { 228 | if (this == &map) return *this; 229 | clear(); 230 | _hash_function = map._hash_function; 231 | for(const auto& kv_pair : map) { 232 | insert(kv_pair); 233 | } 234 | return *this; 235 | } 236 | 237 | template 238 | HashMap& HashMap::operator=(HashMap&& map) { 239 | if (this == &map) return *this; 240 | clear(); 241 | _size = std::move(map._size); 242 | _hash_function = map._hash_function; 243 | _buckets_array = std::move(map._buckets_array); 244 | 245 | map._size = 0; 246 | map._buckets_array.resize(_buckets_array.size(), nullptr); 247 | 248 | return *this; 249 | } 250 | 251 | template 252 | typename HashMap::node_pair HashMap::find_node(const K& key) const{ 253 | size_t index = _hash_function(key) % _buckets_array.size(); 254 | Node* pre_node = nullptr; 255 | Node* cur_node = _buckets_array[index]; 256 | while (cur_node != nullptr) 257 | { 258 | const auto& [cur_key, cur_val] = cur_node->value; 259 | if (cur_key == key) return {pre_node, cur_node}; 260 | pre_node = cur_node; 261 | cur_node = cur_node->next; 262 | } 263 | return {pre_node, cur_node}; 264 | } 265 | 266 | template 267 | size_t HashMap::first_not_empty_bucket() const { 268 | for (size_t i = 0; i < _buckets_array.size(); i++) { 269 | if (_buckets_array[i] != nullptr) return i; 270 | } 271 | return _buckets_array.size() - 1; 272 | } 273 | 274 | template 275 | typename HashMap::iterator HashMap::make_iterator(Node* curr) { 276 | size_t index = bucket_count(); 277 | if (curr != nullptr) { 278 | index = _hash_function(curr->value.first) % _buckets_array.size(); 279 | } 280 | 281 | return iterator(&_buckets_array, curr, index); 282 | 283 | } 284 | 285 | template 286 | std::ostream& operator<<(std::ostream& stream, const HashMap& map) { 287 | std::stringstream str_stream; 288 | for (const auto& kv_pair : map) { 289 | str_stream << kv_pair.first << ":" << kv_pair.second << ", "; 290 | } 291 | std::string str = str_stream.str(); 292 | if (str.size() != 0) { 293 | str.erase(str.size() - 2, 2); 294 | } 295 | 296 | stream << "{" << str << "}"; 297 | return stream; 298 | } 299 | 300 | template 301 | bool operator==(const HashMap& lhs, const HashMap& rhs) { 302 | for(const auto& kv_pair : lhs) { 303 | if (!rhs.contains(kv_pair.first)) return false; 304 | if (rhs.at(kv_pair.first) != kv_pair.second) return false; 305 | } 306 | return lhs.size() == rhs.size(); 307 | } 308 | 309 | template 310 | bool operator!=(const HashMap& lhs, const HashMap& rhs) { 311 | return !(lhs == rhs); 312 | } 313 | -------------------------------------------------------------------------------- /solutions/HashMap/hashmap.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef HASHMAP_H 3 | #define HASHMAP_H 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "hashmap_iterator.h" 11 | 12 | /* 13 | * Template class for a HashMap 14 | * 15 | * K = key type 16 | * M = mapped type 17 | * H = hash function type used to hash a key; if not provided, defaults to std::hash 18 | * 19 | * Notes: When dealing with the Stanford libraries, we often call M the value 20 | * (and maps store key/value pairs). 21 | * 22 | * However, we name it M for mapped type to avoid confusion with value_type. 23 | * value_type is what the container is storing, which is a std::pair. 24 | * 25 | * All STL containers have a value_type and STL algorithms may use the value_type alias, so 26 | * we try our best to follow that convention. 27 | * 28 | * Example: 29 | * HashMap 30 | * This means K = key = std::string, 31 | * M = mapped = int, 32 | * value_type = std::pair. 33 | * 34 | * Concept requirements: 35 | * - H is function type that with function prototype size_t hash(const K& key). 36 | * The const and reference are not required, but key cannot be modified in function. 37 | * - K and M must be regular (copyable, default constructible, and equality comparable). 38 | */ 39 | template> 40 | class HashMap { 41 | public: 42 | /* 43 | * Alias for std::pair, used by the STL (such as in std::inserter) 44 | * As noted above, value_type is not the same as the mapped_type! 45 | * 46 | * Usage: 47 | * HashMap::value_type val = {3, "Avery"}; 48 | * map.insert(val); 49 | */ 50 | using value_type = std::pair; 51 | 52 | /* 53 | * Alias for the iterator type. Recall that it's impossible for an external client 54 | * to figure out the type of this iterator (you would've never guessed what the template 55 | * parameters are here), which is why the aliases are crucial. 56 | * 57 | * Usage: 58 | * HashMap::iterator iter = map.begin(); 59 | */ 60 | using iterator = HashMapIterator; 61 | 62 | /* 63 | * Alias for the const_iterator type. Recall that it's impossible for an external client 64 | * to figure out the type of this iterator (you would've never guessed what the template 65 | * parameters are here), which is why the aliases are crucial. 66 | * 67 | * Usage: 68 | * const auto& cmap = map; 69 | * HashMap::iterator iter = cmap.begin(); 70 | * 71 | * Notes: recall that you cannot modify the element a const_iterator is pointing to. 72 | * Also, a const_iterator is not a const iterator! 73 | */ 74 | using const_iterator = HashMapIterator; 75 | 76 | /* 77 | * Declares that the HashMapIterator class are friends of the HashMap class. 78 | * This allows the HashMapIterators to see the private members, which is 79 | * important because the iterator needs to know what element it is pointing to. 80 | */ 81 | friend class HashMapIterator; 82 | friend class HashMapIterator; 83 | 84 | /* 85 | * Default constructor 86 | * Creates an empty HashMap with default number of buckets and hash function. 87 | * 88 | * Usage: 89 | * HashMap map; 90 | * HashMap map{}; 91 | * 92 | * Complexity: O(B), B = number of buckets 93 | */ 94 | HashMap(); 95 | 96 | /* 97 | * Constructor with bucket_count and hash function as parameters. 98 | * 99 | * Creates an empty HashMap with a specified initial bucket_count and hash funciton. 100 | * If no hash function provided, default value of H is used. 101 | * 102 | * Usage: 103 | * HashMap(10) map; 104 | * HashMap map(10, [](const K& key) {return key % 10; }); 105 | * HashMap map{10, [](const K& key) {return key % 10; }}; 106 | * 107 | * Complexity: O(B), B = number of buckets 108 | * 109 | * Notes : what is explicit? Explicit specifies that a constructor 110 | * cannot perform implicit conversion on the parameters, or use copy-initialization. 111 | * That's good, as nonsense like the following won't compile: 112 | * 113 | * HashMap map(1.0); // double -> int conversion not allowed. 114 | * HashMap map = 1; // copy-initialization, does not compile. 115 | */ 116 | explicit HashMap(size_t bucket_count, const H& hash = H()); 117 | 118 | /* 119 | * Destructor. 120 | * 121 | * Usage: (implicitly called when HashMap goes out of scope) 122 | * 123 | * Complexity: O(N), N = number of elements 124 | */ 125 | ~HashMap(); 126 | 127 | inline size_t size() const; 128 | inline bool empty() const; 129 | inline float load_factor() const; 130 | inline size_t bucket_count() const; 131 | 132 | /* 133 | * Returns whether or not the HashMap contains the given key. 134 | * 135 | * Parameters: const l-value reference to type K, the given key 136 | * Return value: bool 137 | * 138 | * Usage: 139 | * if (map.contains("Avery")) { map.at("Avery"); ... } 140 | * 141 | * Complexity: O(1) amortized average case, O(N) worst case, N = number of elements 142 | * 143 | * Notes: Recall that when using a std::map, you use the map.count(key) function 144 | * (returns 0 or 1) to check if key exists. In C++20, map.contains(key) will be available. 145 | * Since contains feels more natural to students who've used the Stanford libraries 146 | * and will be available in the future, we will implement map.contains(key). 147 | */ 148 | bool contains(const K& key) const; 149 | 150 | /* 151 | * Returns a l-value reference to the mapped value given a key. 152 | * If no such element exists, throws exception of type std::out_of_range. 153 | * 154 | * Parameters: key of type K. 155 | * Return value: l-value reference to type V, the mapped value of key. 156 | * 157 | * Usage: 158 | * map.at(3) = "Avery"; // assuming {3, "Avery"} is in the map. 159 | * std::string s = map.at(3); // s = "Avery" 160 | * 161 | * Exceptions: std::out_of_range if key is not in the map. 162 | * 163 | * Complexity: O(1) amortized average case, O(N) worst case, N = number of elements 164 | * 165 | * Notes: recall that operator[], which you will implement, does not throw exceptions, 166 | * if a key is not found. Instead, it will create a K/M pair for that key with a default 167 | * mapped value. This function is also not const-correct, which you will fix in milestone 2. 168 | */ 169 | M& at(const K& key); 170 | const M& at(const K& key) const; 171 | 172 | /* 173 | * Removes all K/M pairs in the HashMap. 174 | * 175 | * Parameters: none 176 | * Return value: none 177 | * 178 | * Usage: 179 | * map.clear(); 180 | * 181 | * Complexity: O(N), N = number of elements 182 | * 183 | * Notes: clear removes all the elements in the HashMap and frees the memory associated 184 | * with those elements, but the HashMap should still be in a valid state and is 185 | * ready to be inserted again, as if it were a newly constructed HashMap with no elements. 186 | * The number of buckets should stay the same. 187 | */ 188 | void clear(); 189 | 190 | /* 191 | * Inserts the K/M pair into the HashMap, if the key does not already exist. 192 | * If the key exists, then the operation is a no-op. 193 | * 194 | * Parameters: const l-value reference to value_type (K/M pair) 195 | * Return value: 196 | * pair, where: 197 | * iterator - iterator to the value_type element with the given key 198 | * this element may have been just added, or may have already existed. 199 | * bool - true if the element was successfully added, 200 | * false if the element already existed. 201 | * 202 | * Usage: 203 | * HashMap map; 204 | * auto [iter1, insert1] = map.insert({3, "Avery"}); // inserts {3, "Avery"}, iter1 points to that element, insert1 = true 205 | * auto [iter2, insert2] = map.insert({3, "Anna"}); // no-op, iter2 points to {3, "Avery"}, insert2 = false 206 | * 207 | * Complexity: O(1) amortized average case 208 | */ 209 | std::pair insert(const value_type& val); 210 | 211 | /* 212 | * Erases a K/M pair (if one exists) corresponding to given key from the HashMap. 213 | * This is a no-op if the key does not exist. 214 | * 215 | * Parameters: const l-value reference to K, key to be removed. 216 | * Return value: true if K/M pair was found and removed, false if key was not found. 217 | * 218 | * Usage: 219 | * map.erase(3); // assuming K = int, erases element with key 3, returns true 220 | * 221 | * Complexity: O(1) amortized average case, O(N) worst case, N = number of elements 222 | * 223 | * Notes: a call to erase should maintain the order of existing iterators, 224 | * other than iterators to the erased K/M element. 225 | */ 226 | bool erase(const K& key); 227 | 228 | /* 229 | * Erases the K/M pair that pos points to. 230 | * Behavior is undefined if pos is not a valid and dereferencable iterator. 231 | * 232 | * Parameters: const_iterator pos, iterator to element to be removed 233 | * Return value: the iterator immediately following pos, which may be end(). 234 | * 235 | * Usage: 236 | * auto iter = map.find(3); 237 | * auto next = map.erase(iter); // erases element that iter is pointing to 238 | * 239 | * Complexity: O(1) amortized average case, O(N) worst case, N = number of elements 240 | * 241 | * Notes: a call to erase should maintain the order of existing iterators, 242 | * other than iterators to the erased K/M element. 243 | */ 244 | iterator erase(const_iterator pos); 245 | 246 | /* 247 | * Resizes the array of buckets, and rehashes all elements. new_buckets could 248 | * be larger than, smaller than, or equal to the original number of buckets. 249 | * 250 | * Parameters: new_buckets - the new number of buckets. Must be greater than 0. 251 | * Return value: none 252 | * 253 | * Usage: 254 | * map.rehash(30) 255 | * 256 | * Exceptions: std::out_of_range if new_buckets = 0. 257 | * 258 | * Complexity: O(N) amortized average case, O(N^2) worst case, N = number of elements 259 | * 260 | * Notes: our minimal HashMap implementation does not support automatic rehashing, but 261 | * std::unordered_map will automatically rehash, even if you rehash to 262 | * a very small number of buckets. For this reason, std::unordered_map.rehash(0) 263 | * is allowed and forces an unconditional rehash. We will not require this behavior. 264 | * If you want, you could implement this. 265 | * 266 | * Previously, this function was part of the assignment. However, it's a fairly challenging 267 | * linked list problem, and students had a difficult time finding an elegant solution. 268 | * Instead, we will ask short answer questions on this function instead. 269 | */ 270 | void rehash(size_t new_bucket); 271 | 272 | /* 273 | * Returns an iterator to the first element. 274 | * This overload is used when the HashMap is non-const. 275 | * 276 | * Usage: 277 | * auto iter = map.begin(); 278 | */ 279 | iterator begin(); 280 | 281 | /* 282 | * Returns a const_iterator to the first element. 283 | * This overload is used when the HashMap is const. 284 | * 285 | * Usage: 286 | * auto iter = cmap.begin(); 287 | */ 288 | const_iterator begin() const; 289 | 290 | /* 291 | * Returns an iterator to one past the last element. 292 | * This overload is used when the HashMap is non-const. 293 | * 294 | * Usage: 295 | * while (iter != map.end()) {...} 296 | */ 297 | iterator end(); 298 | 299 | const_iterator end() const; 300 | 301 | /* 302 | * Finds the element with the given key, and returns an iterator to that element. 303 | * If an element is not found, an iterator to end() is returned. 304 | * 305 | * Parameters: const l-value reference to type K, the key we are looking for. 306 | * Return value: iterator to the K/M element with given key. 307 | * 308 | * Usage: 309 | * auto iter = map.find(4); 310 | * iter->second = "Hello"; // sets whatever 4 was mapped to to "Hello". 311 | * 312 | * Complexity: O(1) amortized average case, O(N) worst case, N = number of elements 313 | */ 314 | iterator find(const K& key); 315 | 316 | const_iterator find(const K& key) const; 317 | 318 | /* 319 | * Function that will print to std::cout the contents of the hash table as 320 | * linked lists, and also displays the size, number of buckets, and load factor. 321 | * 322 | * Parameters: none 323 | * Return value: none 324 | * 325 | * Usage: 326 | * map.debug(); 327 | * 328 | * Complexity: O(N), N = number of elements. 329 | * 330 | * Notes: debug will not compile if either K or V does not support operator<< for std::ostream. 331 | * this function will crash if your linked list logic is incorrect (eg. forgot to reset the 332 | * last node's next to nullptr). Check where the source of the compiler error comes from 333 | * before complaining to us that our starter code doesn't work! 334 | * 335 | * Tip: place map.debug() in various places in the test cases to figure out which operation 336 | * is failing. Super useful when we debugged our code. 337 | */ 338 | void debug(); 339 | 340 | /* EXTRA CONSTURCTORS */ 341 | 342 | /* 343 | * Range constructor 344 | * Creates a HashMap with the elements in the range [first, last). 345 | * 346 | * Requirements: InputIt must be iterators to a container whose elements are pair. 347 | * 348 | * Usage: 349 | * std::vector> vec {{'a', 3}, {'b', 5}, {'c', 7}}; 350 | * HashMap map{vec.begin(), vec.end()}; 351 | * 352 | * Complexity: O(N), where N = std::distance(first, last); 353 | */ 354 | template 355 | HashMap(InputIter begin, InputIter end, size_t bucket_count = kDefaultBuckets, const H& hash = H()); 356 | 357 | /* 358 | * Initializer list constructor 359 | * Creates a HashMap with the elements in the initializer list init 360 | * 361 | * Requirements: init must be an initializer_list whose elements are pair. 362 | * 363 | * Usage: 364 | * HashMap map{{'a', 3}, {'b', 5}, {'c', 7}}; 365 | * 366 | * Complexity: O(N), where N = init.size(); 367 | * 368 | * Notes: you may want to do some research on initializer_lists. The most important detail you need 369 | * to know is that they are very limited, and have three functions: init.begin(), init.end(), and init.size(). 370 | * There are no other ways to access the elements in an initializer_list. 371 | * As a result, you probably want to leverage the range constructor you wrote in the previous function! 372 | * 373 | * Also, you should check out the delegating constructor note in the .cpp file. 374 | */ 375 | HashMap(std::initializer_list init, size_t bucket_count = kDefaultBuckets, const H& hash = H()); 376 | 377 | /* 378 | * Indexing operator 379 | * Retrieves a reference to the mapped value corresponding to this key. 380 | * If no such key exists, a key/mapped value pair will be added to the HashMap. 381 | * The mapped value will have the default value for type M. 382 | * 383 | * Usage: 384 | * HashMap map; 385 | * map[3] = "Avery"; // creates the pair {3, "Avery"} 386 | * auto name = map[3]; // name is now "Avery" 387 | * auto name2 = map[4]; // creates the pair {4, ""}, name2 is now "" 388 | * 389 | * Complexity: O(1) average case amortized plus complexity of K and M's constructor 390 | */ 391 | M& operator[](const K& key); 392 | 393 | 394 | // TODO: declare headers for copy constructor/assignment, move constructor/assignment 395 | HashMap(const HashMap& map); 396 | HashMap(HashMap&& map); 397 | 398 | HashMap& operator=(const HashMap& map); 399 | HashMap& operator=(HashMap&& map); 400 | 401 | private: 402 | /* 403 | * node structure represented a node in a linked list. 404 | * Each node consists of a value_type (K/M pair) and a next pointer. 405 | * 406 | * This is implemented in the private section as clients should not be dealing 407 | * with anything related to the node struct. 408 | * 409 | * Usage; 410 | * HashMap::node n; 411 | * n->value = {3, 4}; 412 | * n->next = nullptr; 413 | */ 414 | struct Node 415 | { 416 | value_type value; 417 | Node* next; 418 | /* 419 | * Default constructor, so even if you forget to set next to nullptr it'll be fine. 420 | * 421 | */ 422 | Node() : value(value_type()), next(nullptr) {}; 423 | Node(const value_type& value, Node* next):value(value), next(next) {}; 424 | }; 425 | 426 | using node_pair = std::pair; 427 | node_pair find_node(const K& key) const; 428 | size_t first_not_empty_bucket() const; 429 | 430 | /* 431 | * Creates an iterator that points to the element curr->value. 432 | * 433 | * Hint: on the assignment, you should NOT need to call this function. 434 | */ 435 | iterator make_iterator(Node* curr); 436 | 437 | /* Private member variables */ 438 | size_t _size; 439 | H _hash_function; 440 | std::vector _buckets_array; 441 | 442 | static const size_t kDefaultBuckets = 10; 443 | using bucket_array_type = decltype(_buckets_array); 444 | }; 445 | 446 | #include "hashmap.cpp" 447 | #endif -------------------------------------------------------------------------------- /solutions/HashMap/hashmap_iterator.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef HASHMAP_ITERATOR_H 3 | #define HASHMAP_ITERATOR_H 4 | 5 | #include // for std::forward_iterator_tag 6 | #include // for std::conditional_t 7 | 8 | // forward declaration for the HashMap class 9 | template class HashMap; 10 | 11 | /* 12 | * Template class for a HashMapIterator 13 | * 14 | * Map = the type of HashMap this class is an iterator for. 15 | * IsConst = whether this is a const_iterator class. 16 | * 17 | * Concept requirements: 18 | * - Map must be a valid class HashMap 19 | */ 20 | template 21 | class HashMapIterator { 22 | public: 23 | 24 | /* 25 | * This alias is very important. When dealing with const_iterator, the value_type is always const, and 26 | * that prevents the client from modifying the elements via a const_iterator. The meta-function 27 | * std::conditional_t changes the value_type (at compile-time) to a const one if IsConst is true. 28 | */ 29 | using value_type = std::conditional_t; 30 | 31 | /* 32 | * Public aliases for this iterator class. Important so STL functions like std::iterator_traits 33 | * can parse this class for important information, like its iterator category. 34 | */ 35 | using iterator_category = std::forward_iterator_tag; 36 | using difference_type = std::ptrdiff_t; 37 | using pointer = value_type*; 38 | using reference = value_type&; 39 | 40 | /* 41 | * Friend declarations so the HashMap class this iterator is for can access the attributes of its iterators. 42 | * Also, to make conversions between iterator and const_iterator easy, we declare the corresponding 43 | * iterator and const_iterators as friends. 44 | */ 45 | friend Map; 46 | friend HashMapIterator; 47 | friend HashMapIterator; 48 | 49 | /* 50 | * Conversion operator: converts any iterator (iterator or const_iterator) to a const_iterator. 51 | * 52 | * Usage: 53 | * iterator iter = map.begin(); 54 | * const_iterator c_iter = iter; // implicit conversion 55 | * 56 | * Implicit conversion operators are usually frowned upon, because they can cause 57 | * some unexpected behavior. This is a rare case where a conversion operator is 58 | * very convenient. Many of the iterator functions in the HashMap class are 59 | * secretly using this conversion. 60 | * 61 | * Note: conversion in the opposite direction (const to non-const) is not safe 62 | * because that gives the client write access the map itself is const. 63 | */ 64 | operator HashMapIterator() const { 65 | return HashMapIterator(_buckets_array, _node, _bucket_idx); 66 | } 67 | 68 | /* 69 | * Dereference operators: defines the behavior of dereferencing an iterator. 70 | * 71 | * Usage: 72 | * auto [key, value] = *iter; 73 | * auto value = iter->second; 74 | * iter->second = 3; // if iter is a regular iterator (not a const_iterator) 75 | * 76 | * Note that dereferencing an invalid or end() iterator is undefined behavior. 77 | */ 78 | reference operator*() const; 79 | pointer operator->() const; 80 | 81 | /* 82 | * Increment operators: moves the iterator to point to the next element, or end(). 83 | * 84 | * Usage: 85 | * ++iter; // prefix 86 | * iter++; // postfix 87 | * 88 | * Note: the prefix operator first increments, and the returns a reference to itself (which is incremented). 89 | * The postfix operator returns a copy of the original iterator, while the iterator itself is incremented. 90 | * 91 | * Note that incrementing an invalid or end() iterator is undefined behavior. 92 | */ 93 | HashMapIterator& operator++(); 94 | HashMapIterator operator++(int); 95 | 96 | /* 97 | * Equality operator: decides if two iterators are pointing to the same element. 98 | * 99 | * Usage: 100 | * if (iter == map.end()) {...}; 101 | */ 102 | template 103 | friend bool operator==(const HashMapIterator& lhs, const HashMapIterator& rhs); 104 | 105 | /* 106 | * Inequality operator: decides if two iterators are pointing to different elements. 107 | * 108 | * Usage: 109 | * if (iter != map.end()) {...}; 110 | */ 111 | template 112 | friend bool operator!=(const HashMapIterator& lhs, const HashMapIterator& rhs); 113 | 114 | /* 115 | * Special member functions: we explicitly state that we want the default compiler-generated functions. 116 | * Here we are following the rule of zero. You should think about why that is correct. 117 | */ 118 | HashMapIterator(const HashMapIterator& rhs) = default; 119 | HashMapIterator& operator=(const HashMapIterator& rhs) = default; 120 | 121 | HashMapIterator(HashMapIterator&& rhs) = default; 122 | HashMapIterator& operator=(HashMapIterator&& rhs) = default; 123 | 124 | private: 125 | using Node = typename Map::Node; 126 | using bucket_array_type = typename Map::bucket_array_type; 127 | 128 | /* 129 | * Instance variable: a pointer to the _buckets_array of the HashMap this iterator is for. 130 | */ 131 | bucket_array_type* _buckets_array; 132 | 133 | /* 134 | * Instance variable: pointer to the node that stores the element this iterator is currently pointing to. 135 | */ 136 | Node* _node; 137 | 138 | /* 139 | * Instance variable: the index of the bucket that _node is in. 140 | */ 141 | size_t _bucket_idx; 142 | 143 | /* 144 | * Private constructor for a HashMapIterator. 145 | * Friend classes can access the private members of class it is friends with, 146 | * so HashMap is able to call HashMapIterator's private constructor 147 | * (e.g, in begin()). We want the HashMapIterator constructor to be private 148 | * so a client can't randomly construct a HashMapIterator without asking for one 149 | * through the HashMap's interface. 150 | */ 151 | HashMapIterator(bucket_array_type* buckets_array, Node* node, size_t bucket_idx); 152 | }; 153 | 154 | template 155 | typename HashMapIterator::reference HashMapIterator::operator*() const { 156 | return _node->value; 157 | } 158 | 159 | template 160 | typename HashMapIterator::pointer HashMapIterator::operator->() const { 161 | return &(_node->value); 162 | } 163 | 164 | template 165 | HashMapIterator::HashMapIterator(bucket_array_type* buckets_array, Node* node, size_t bucket_idx): 166 | _buckets_array(buckets_array), 167 | _node(node), 168 | _bucket_idx(bucket_idx) {}; 169 | 170 | template 171 | HashMapIterator& HashMapIterator::operator++(){ 172 | 173 | if (_node != nullptr) { 174 | Node* next_node = _node->next; 175 | while (next_node == nullptr && _bucket_idx < (_buckets_array->size() - 1)) 176 | { 177 | next_node = (*_buckets_array)[++_bucket_idx]; 178 | } 179 | _node = next_node; 180 | } 181 | return *this; 182 | } 183 | 184 | template 185 | HashMapIterator HashMapIterator::operator++(int) { 186 | HashMapIterator temp = *this; 187 | ++(*this); 188 | return temp; 189 | } 190 | 191 | template 192 | bool operator==(const HashMapIterator& rhs, const HashMapIterator& lhs) { 193 | return rhs._node == lhs._node; 194 | } 195 | 196 | template 197 | bool operator!=(const HashMapIterator& lhs, const HashMapIterator& rhs) { 198 | return lhs._node != rhs._node; 199 | } 200 | 201 | #endif -------------------------------------------------------------------------------- /solutions/HashMap/hashmap_perf.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "hashmap.h" 10 | #include "gtest/gtest.h" 11 | #include "test_settings.h" 12 | 13 | using clock_type = std::chrono::high_resolution_clock; 14 | using ns = std::chrono::nanoseconds; 15 | 16 | std::string print_with_commas(long long int n) { 17 | std::string ans = ""; 18 | // Convert the given integer 19 | // to equivalent string 20 | std::string num = std::to_string(n); 21 | 22 | // Initialise count 23 | int count = 0; 24 | 25 | // Traverse the string in reverse 26 | for (int i = num.size() - 1; i >= 0; i--) { 27 | count++; 28 | ans.push_back(num[i]); 29 | 30 | // If three characters 31 | // are traversed 32 | if (count == 3) { 33 | ans.push_back(','); 34 | count = 0; 35 | } 36 | } 37 | 38 | // Reverse the string to get 39 | // the desired output 40 | reverse(ans.begin(), ans.end()); 41 | 42 | // If the given string is 43 | // less than 1000 44 | if (ans.size() % 4 == 0) { 45 | // Remove ',' 46 | ans.erase(ans.begin()); 47 | } 48 | return ans; 49 | } 50 | 51 | #if RUN_TEST_PERF 52 | void benchmark_insert_erase() { 53 | std::cout << "Task: insert then erase N elements, measured in ns." << '\n'; 54 | auto good_hash_function = [](const int& key) { 55 | return (key * 43037 + 52081) % 79229; 56 | }; 57 | 58 | std::vector my_map_timing; 59 | std::vector sizes{10, 100, 1000, 10000, 100000, 1000000}; 60 | for (size_t size : sizes) { 61 | std::vector million; 62 | for (size_t i = 0; i < size; i++) { 63 | million.push_back(i); 64 | } 65 | auto rng = std::default_random_engine {}; 66 | std::shuffle(million.begin(), million.end(), rng); 67 | size_t my_map_result, std_map_result; 68 | { 69 | auto my_start = clock_type::now(); 70 | 71 | HashMap my_map(size, good_hash_function); 72 | for (int element : million) { 73 | my_map.insert({element, element}); 74 | } 75 | 76 | for (int element : million) { 77 | my_map.erase(element); 78 | } 79 | 80 | auto my_end = clock_type::now(); 81 | auto end = std::chrono::duration_cast(my_end - my_start); 82 | 83 | my_map_result = end.count(); 84 | } 85 | 86 | { 87 | auto std_start = clock_type::now(); 88 | 89 | std::unordered_map std_map(size, good_hash_function); 90 | for (int element : million) { 91 | std_map.insert({element, element}); 92 | } 93 | 94 | for (int element : million) { 95 | std_map.erase(element); 96 | } 97 | 98 | auto std_end = clock_type::now(); 99 | auto end = std::chrono::duration_cast(std_end - std_start); 100 | 101 | std_map_result = end.count(); 102 | } 103 | 104 | std::cout << "size " << std::setw(10) << size; 105 | std::cout << " | HashMap: " << std::setw(13) << print_with_commas(my_map_result); 106 | std::cout << " | std:unordered_map: " << std::setw(13) << print_with_commas(std_map_result) << '\n'; 107 | my_map_timing.push_back(my_map_result); 108 | } 109 | EXPECT_TRUE(10*my_map_timing[0] < my_map_timing[3]); // Ensure runtime of N = 10 is much faster than N = 10000 110 | } 111 | 112 | void benchmark_find() { 113 | std::cout << "Task: find N elements (random hit/miss), measured in ns." << '\n'; 114 | auto good_hash_function = [](const int& key) { 115 | return (key * 43037 + 52081) % 79229; 116 | }; 117 | 118 | std::vector my_map_timing; 119 | std::vector sizes{10, 100, 1000, 10000, 100000, 1000000}; 120 | for (size_t size : sizes) { 121 | std::vector million; 122 | std::vector lookup; 123 | for (size_t i = 0; i < 2*size; i++) { 124 | million.push_back(i); 125 | lookup.push_back(i); 126 | } 127 | auto rng = std::default_random_engine {}; 128 | std::shuffle(million.begin(), million.end(), rng); 129 | std::shuffle(lookup.begin(), lookup.end(), rng); 130 | size_t my_map_result, std_map_result; 131 | { 132 | 133 | HashMap my_map(size, good_hash_function); 134 | for (size_t i = 0; i < million.size(); i += 2) { 135 | int element = million[i]; 136 | my_map.insert({element, element}); 137 | } 138 | auto my_start = clock_type::now(); 139 | int count = 0; 140 | for (size_t i = 0; i < lookup.size(); i += 2) { 141 | int element = lookup[i]; 142 | auto found = my_map.find(element); 143 | count += (found == my_map.end()); 144 | } 145 | 146 | auto my_end = clock_type::now(); 147 | auto end = std::chrono::duration_cast(my_end - my_start); 148 | 149 | my_map_result = end.count(); 150 | } 151 | 152 | { 153 | std::unordered_map std_map(size, good_hash_function); 154 | for (size_t i = 0; i < million.size(); i += 2) { 155 | int element = million[i]; 156 | std_map.insert({element, element}); 157 | } 158 | auto std_start = clock_type::now(); 159 | int count = 0; 160 | for (size_t i = 0; i < lookup.size(); i += 2) { 161 | int element = lookup[i]; 162 | auto found = std_map.find(element); 163 | count += (found == std_map.end()); 164 | } 165 | 166 | auto std_end = clock_type::now(); 167 | auto end = std::chrono::duration_cast(std_end - std_start); 168 | 169 | std_map_result = end.count(); 170 | } 171 | 172 | std::cout << "size " << std::setw(10) << size; 173 | std::cout << " | HashMap: " << std::setw(13) << print_with_commas(my_map_result); 174 | std::cout << " | std:unordered_map: " << std::setw(13) << print_with_commas(std_map_result) << '\n'; 175 | my_map_timing.push_back(my_map_result); 176 | } 177 | EXPECT_TRUE(10*my_map_timing[0] < my_map_timing[3]); // Ensure runtime of N = 10 is much faster than N = 10000 178 | } 179 | 180 | void benchmark_iterate() { 181 | std::cout << "Task: iterate over all N elements, measured in ns." << '\n'; 182 | auto good_hash_function = [](const int& key) { 183 | return (key * 43037 + 52081) % 79229; 184 | }; 185 | 186 | std::vector my_map_timing; 187 | std::vector std_map_timing; 188 | std::vector sizes{10, 100, 1000, 10000, 100000, 1000000}; 189 | for (size_t size : sizes) { 190 | std::vector million; 191 | for (size_t i = 0; i < size; i++) { 192 | million.push_back(i); 193 | } 194 | auto rng = std::default_random_engine {}; 195 | std::shuffle(million.begin(), million.end(), rng); 196 | size_t my_map_result, std_map_result; 197 | { 198 | HashMap my_map(size, good_hash_function); 199 | for (int element : million) { 200 | my_map.insert({element, element}); 201 | } 202 | 203 | auto my_start = clock_type::now(); 204 | size_t count = 0; 205 | for (const auto& [key, value] : my_map) { 206 | count += key; 207 | } 208 | auto my_end = clock_type::now(); 209 | auto end = std::chrono::duration_cast(my_end - my_start); 210 | 211 | my_map_result = end.count(); 212 | } 213 | 214 | { 215 | std::unordered_map std_map(size, good_hash_function); 216 | for (int element : million) { 217 | std_map.insert({element, element}); 218 | } 219 | 220 | auto std_start = clock_type::now(); 221 | size_t count = 0; 222 | for (const auto& [key, value] : std_map) { 223 | count += key; 224 | } 225 | auto std_end = clock_type::now(); 226 | auto end = std::chrono::duration_cast(std_end - std_start); 227 | 228 | std_map_result = end.count(); 229 | } 230 | 231 | std::cout << "size " << std::setw(10) << size; 232 | std::cout << " | HashMap: " << std::setw(13) << print_with_commas(my_map_result); 233 | std::cout << " | std:unordered_map: " << std::setw(13) << print_with_commas(std_map_result) << '\n'; 234 | my_map_timing.push_back(my_map_result); 235 | } 236 | EXPECT_TRUE(10*my_map_timing[0] < my_map_timing[3]); // Ensure runtime of N = 10 is much faster than N = 10000 237 | } 238 | #endif 239 | 240 | int main() { 241 | std::cout << "Performance Test: " << std::endl; 242 | #if RUN_TEST_PERF 243 | benchmark_find(); 244 | benchmark_insert_erase(); 245 | benchmark_iterate(); 246 | #endif 247 | return 0; 248 | } -------------------------------------------------------------------------------- /solutions/HashMap/test_settings.h: -------------------------------------------------------------------------------- 1 | // Test settings - use this file to change which tests are executed 2 | 3 | 4 | // Change the 0 to a 1 to run that test 5 | // Note that the tests won't compile until their respective functions 6 | // have a header in the .h, and implementations in the .cpp 7 | 8 | // Milestone 1 test cases of basic functionality 9 | // Basic Functions 10 | #define RUN_TEST_1A 1 11 | #define RUN_TEST_1B 1 12 | #define RUN_TEST_1C 1 13 | #define RUN_TEST_1D 1 14 | #define RUN_TEST_1E 1 15 | #define RUN_TEST_1F 1 16 | #define RUN_TEST_1G 1 17 | #define RUN_TEST_1H 1 18 | #define RUN_TEST_1I 1 19 | 20 | // Iterator 21 | #define RUN_TEST_1J 1 22 | #define RUN_TEST_1K 1 23 | #define RUN_TEST_1L 1 24 | #define RUN_TEST_1M 1 25 | #define RUN_TEST_1N 1 26 | #define RUN_TEST_1O 1 27 | #define RUN_TEST_1P 1 28 | #define RUN_TEST_1Q 1 29 | #define RUN_TEST_1R 1 30 | #define RUN_TEST_1S 1 31 | #define RUN_TEST_1T 1 32 | #define RUN_TEST_1U 1 33 | #define RUN_TEST_1V 1 34 | 35 | // Milestone 2: range constructor (optional) 36 | #define RUN_TEST_2A 1 37 | #define RUN_TEST_2B 1 38 | // Milestone 2: initializer list constructor (optional) 39 | #define RUN_TEST_2C 1 40 | #define RUN_TEST_2D 1 41 | 42 | // Milestone 3: operator[] 43 | #define RUN_TEST_3A 1 44 | #define RUN_TEST_3B 1 45 | // Milestone 3: operator<< 46 | #define RUN_TEST_3C 1 47 | #define RUN_TEST_3D 1 48 | // Milestone 3: operator== and operator!= 49 | #define RUN_TEST_3E 1 50 | #define RUN_TEST_3F 1 51 | 52 | // Milestone 4: copy operations 53 | #define RUN_TEST_4A 1 54 | #define RUN_TEST_4B 1 55 | #define RUN_TEST_4C 1 56 | 57 | // Milestone 4: move operations 58 | // warning: these may pass even if you haven't implemented them 59 | // - before implementing copy or move, 3AB will fail, 3CDEFGH will pass 60 | // - after implementing copy but not move, 3GH will fail, 3ABCDEF will pass 61 | // - after implementing copy & move, all of them should pass (you should aim for this) 62 | #define RUN_TEST_4D 1 63 | #define RUN_TEST_4E 1 64 | #define RUN_TEST_4F 1 65 | #define RUN_TEST_4G 1 66 | #define RUN_TEST_4H 1 67 | 68 | // Milestone 5: benchmark (optional) 69 | #define RUN_TEST_PERF 1 70 | -------------------------------------------------------------------------------- /solutions/KDTree/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(KDTree) 3 | 4 | # GoogleTest requires at least C++14 5 | set(CMAKE_CXX_STANDARD 20) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | 8 | include(FetchContent) 9 | FetchContent_Declare( 10 | googletest 11 | URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip 12 | ) 13 | 14 | # For Windows: Prevent overriding the parent project's compiler/linker settings 15 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 16 | FetchContent_MakeAvailable(googletest) 17 | 18 | enable_testing() 19 | 20 | add_executable(kd_tree_test kd_tree_test.cc) 21 | target_link_libraries(kd_tree_test GTest::gtest_main) 22 | 23 | 24 | include(GoogleTest) 25 | gtest_discover_tests(kd_tree_test) -------------------------------------------------------------------------------- /solutions/KDTree/bounded_priority_queue.h: -------------------------------------------------------------------------------- 1 | /** 2 | * File: BoundedPriorityQueue.h 3 | * Author: Keith Schwarz (htiek@cs.stanford.edu) 4 | * 5 | * An implementation of the bounded priority queue abstraction. 6 | * A bounded priority queue is in many ways like a regular priority 7 | * queue. It stores a collection of elements tagged with a real- 8 | * valued priority, and allows for access to the element whose 9 | * priority is the smallest. However, unlike a regular priority 10 | * queue, the number of elements in a bounded priority queue has 11 | * a hard limit that is specified in the constructor. Whenever an 12 | * element is added to the bounded priority queue such that the 13 | * size exceeds the maximum, the element with the highest priority 14 | * value will be ejected from the bounded priority queue. In this 15 | * sense, a bounded priority queue is like a high score table for 16 | * a video game that stores a fixed number of elements and deletes 17 | * the least-important entry whenever a new value is inserted. 18 | * 19 | * When creating a bounded priority queue, you must specify the 20 | * maximum number of elements to store in the queue as an argument 21 | * to the constructor. For example: 22 | * 23 | * BoundedPriorityQueue bpq(15); // Holds up to fifteen values. 24 | * 25 | * The maximum size of the bounded priority queue can be obtained 26 | * using the maxSize() function, as in 27 | * 28 | * size_t k = bpq.maxSize(); 29 | * 30 | * Beyond these restrictions, the bounded priority queue behaves 31 | * similarly to other containers. You can query its size using 32 | * size() and check whether it is empty using empty(). You 33 | * can enqueue an element into the bounded priority queue by 34 | * writing 35 | * 36 | * bpq.enqueue(elem, priority); 37 | * 38 | * Note that after enqueuing the element, there is no guarantee 39 | * that the value will actually be in the queue. If the queue 40 | * is full and the new element's priority exceeds the largest 41 | * priority in the container, it will not be added. 42 | * 43 | * You can dequeue elements from a bounded priority queue using 44 | * the dequeueMin() function, as in 45 | * 46 | * int val = bpq.dequeueMin(); 47 | * 48 | * The bounded priority queue also allows you to query the min 49 | * and max priorities of the values in the queue. These values 50 | * can be queried using the best() and worst() functions, which 51 | * return the smallest and largest priorities in the queue, 52 | * respectively. 53 | */ 54 | 55 | #ifndef BOUNDED_PQUEUE_INCLUDED 56 | #define BOUNDED_PQUEUE_INCLUDED 57 | 58 | #include 59 | #include 60 | #include 61 | 62 | using namespace std; 63 | 64 | template 65 | class BoundedPriorityQueue { 66 | public: 67 | // Constructor: BoundedPriorityQueue(size_t maxSize); 68 | // Usage: BoundedPriorityQueue bpq(15); 69 | // -------------------------------------------------- 70 | // Constructs a new, empty BoundedPriorityQueue with 71 | // maximum size equal to the constructor argument. 72 | /// 73 | explicit BoundedPriorityQueue(size_t maxSize); 74 | 75 | // void enqueue(const T& value, double priority); 76 | // Usage: bpq.enqueue("Hi!", 2.71828); 77 | // -------------------------------------------------- 78 | // Enqueues a new element into the BoundedPriorityQueue with 79 | // the specified priority. If this overflows the maximum 80 | // size of the queue, the element with the highest 81 | // priority will be deleted from the queue. Note that 82 | // this might be the element that was just added. 83 | void enqueue(const T& value, double priority); 84 | 85 | // T dequeueMin(); 86 | // Usage: int val = bpq.dequeueMin(); 87 | // -------------------------------------------------- 88 | // Returns the element from the BoundedPriorityQueue with the 89 | // smallest priority value, then removes that element 90 | // from the queue. 91 | T dequeueMin(); 92 | 93 | // size_t size() const; 94 | // bool empty() const; 95 | // Usage: while (!bpq.empty()) { ... } 96 | // -------------------------------------------------- 97 | // Returns the number of elements in the queue and whether 98 | // the queue is empty, respectively. 99 | size_t size() const; 100 | bool empty() const; 101 | 102 | // size_t maxSize() const; 103 | // Usage: size_t queueSize = bpq.maxSize(); 104 | // -------------------------------------------------- 105 | // Returns the maximum number of elements that can be 106 | // stored in the queue. 107 | size_t maxSize() const; 108 | 109 | // double best() const; 110 | // double worst() const; 111 | // Usage: double highestPriority = bpq.worst(); 112 | // -------------------------------------------------- 113 | // best() returns the smallest priority of an element 114 | // stored in the container (i.e. the priority of the 115 | // element that will be dequeued first using dequeueMin). 116 | // worst() returns the largest priority of an element 117 | // stored in the container. If an element is enqueued 118 | // with a priority above this value, it will automatically 119 | // be deleted from the queue. Both functions return 120 | // numeric_limits::infinity() if the queue is 121 | // empty. 122 | double best() const; 123 | double worst() const; 124 | 125 | private: 126 | // This class is layered on top of a multimap mapping from priorities 127 | // to elements with those priorities. 128 | multimap elems; 129 | size_t maximumSize; 130 | }; 131 | 132 | /** BoundedPriorityQueue class implementation details */ 133 | 134 | template 135 | BoundedPriorityQueue::BoundedPriorityQueue(size_t maxSize) { 136 | maximumSize = maxSize; 137 | } 138 | 139 | // enqueue adds the element to the map, then deletes the last element of the 140 | // map if there size exceeds the maximum size. 141 | template 142 | void BoundedPriorityQueue::enqueue(const T& value, double priority) { 143 | // Add the element to the collection. 144 | elems.insert(make_pair(priority, value)); 145 | 146 | // If there are too many elements in the queue, drop off the last one. 147 | if (size() > maxSize()) { 148 | typename multimap::iterator last = elems.end(); 149 | --last; // Now points to highest-priority element 150 | elems.erase(last); 151 | } 152 | } 153 | 154 | // dequeueMin copies the lowest element of the map (the one pointed at by 155 | // begin()) and then removes it. 156 | template 157 | T BoundedPriorityQueue::dequeueMin() { 158 | // Copy the best value. 159 | T result = elems.begin()->second; 160 | 161 | // Remove it from the map. 162 | elems.erase(elems.begin()); 163 | 164 | return result; 165 | } 166 | 167 | // size() and empty() call directly down to the underlying map. 168 | template 169 | size_t BoundedPriorityQueue::size() const { 170 | return elems.size(); 171 | } 172 | 173 | template 174 | bool BoundedPriorityQueue::empty() const { 175 | return elems.empty(); 176 | } 177 | 178 | // maxSize just returns the appropriate data member. 179 | template 180 | size_t BoundedPriorityQueue::maxSize() const { 181 | return maximumSize; 182 | } 183 | 184 | // The best() and worst() functions check if the queue is empty, 185 | // and if so return infinity. 186 | template 187 | double BoundedPriorityQueue::best() const { 188 | return empty()? numeric_limits::infinity() : elems.begin()->first; 189 | } 190 | 191 | template 192 | double BoundedPriorityQueue::worst() const { 193 | return empty()? numeric_limits::infinity() : elems.rbegin()->first; 194 | } 195 | 196 | #endif // BOUNDED_PQUEUE_INCLUDED 197 | -------------------------------------------------------------------------------- /solutions/KDTree/kd_tree.h: -------------------------------------------------------------------------------- 1 | /** 2 | * File: kd_tree.h 3 | * Author: (your name here) 4 | * ------------------------ 5 | * An interface representing a kd-tree in some number of dimensions. The tree 6 | * can be constructed from a set of data and then queried for membership and 7 | * nearest neighbors. 8 | */ 9 | 10 | #ifndef KDTREE_INCLUDED 11 | #define KDTREE_INCLUDED 12 | 13 | #include 14 | #include "point.h" 15 | #include "math.h" 16 | #include "bounded_priority_queue.h" 17 | 18 | // "using namespace" in a header file is conventionally frowned upon, but I'm 19 | // including it here so that you may use things like size_t without having to 20 | // type std::size_t every time. 21 | 22 | template 23 | class KDTree { 24 | public: 25 | struct Node { 26 | Point point; 27 | ElemType element; 28 | Node* left_node; 29 | Node* right_node; 30 | }; 31 | 32 | // Constructor: KDTree(); 33 | // Usage: KDTree<3, int> myTree; 34 | // ---------------------------------------------------- 35 | // Constructs an empty KDTree. 36 | KDTree(); 37 | 38 | // Destructor: ~KDTree() 39 | // Usage: (implicit) 40 | // ---------------------------------------------------- 41 | // Cleans up all resources used by the KDTree. 42 | ~KDTree(); 43 | 44 | // KDTree(const KDTree& rhs); 45 | // KDTree& operator=(const KDTree& rhs); 46 | // Usage: KDTree<3, int> one = two; 47 | // Usage: one = two; 48 | // ----------------------------------------------------- 49 | // Deep-copies the contents of another KDTree into this one. 50 | KDTree(const KDTree& rhs); 51 | KDTree& operator=(const KDTree& rhs); 52 | 53 | // size_t dimension() const; 54 | // Usage: size_t dim = kd.dimension(); 55 | // ---------------------------------------------------- 56 | // Returns the dimension of the points stored in this KDTree. 57 | size_t dimension() const; 58 | 59 | // size_t size() const; 60 | // bool empty() const; 61 | // Usage: if (kd.empty()) 62 | // ---------------------------------------------------- 63 | // Returns the number of elements in the kd-tree and whether the tree is 64 | // empty. 65 | size_t size() const; 66 | bool empty() const; 67 | 68 | // bool contains(const Point& pt) const; 69 | // Usage: if (kd.contains(pt)) 70 | // ---------------------------------------------------- 71 | // Returns whether the specified point is contained in the KDTree. 72 | bool contains(const Point& pt) const; 73 | 74 | // void insert(const Point& pt, const ElemType& value); 75 | // Usage: kd.insert(v, "This value is associated with v."); 76 | // ---------------------------------------------------- 77 | // Inserts the point pt into the KDTree, associating it with the specified 78 | // value. If the element already existed in the tree, the new value will 79 | // overwrite the existing one. 80 | void insert(const Point& pt, const ElemType& value); 81 | 82 | // ElemType& operator[](const Point& pt); 83 | // Usage: kd[v] = "Some Value"; 84 | // ---------------------------------------------------- 85 | // Returns a reference to the value associated with point pt in the KDTree. 86 | // If the point does not exist, then it is added to the KDTree using the 87 | // default value of ElemType as its key. 88 | ElemType& operator[](const Point& pt); 89 | 90 | // ElemType& at(const Point& pt); 91 | // const ElemType& at(const Point& pt) const; 92 | // Usage: cout << kd.at(v) << endl; 93 | // ---------------------------------------------------- 94 | // Returns a reference to the key associated with the point pt. If the point 95 | // is not in the tree, this function throws an out_of_range exception. 96 | ElemType& at(const Point& pt); 97 | const ElemType& at(const Point& pt) const; 98 | 99 | // ElemType kNNValue(const Point& key, size_t k) const 100 | // Usage: cout << kd.kNNValue(v, 3) << endl; 101 | // ---------------------------------------------------- 102 | // Given a point v and an integer k, finds the k points in the KDTree 103 | // nearest to v and returns the most common value associated with those 104 | // points. In the event of a tie, one of the most frequent value will be 105 | // chosen. 106 | ElemType kNNValue(const Point& key, size_t k) const; 107 | 108 | private: 109 | // TODO: Add implementation details here. 110 | size_t tree_size; 111 | Node* root_node; 112 | 113 | Node* copy_tree(Node* source); 114 | void delete_tree(Node* root); 115 | Node* insert_node(Node*& root, const Point& point, const ElemType& elem, int idx); 116 | Node* find_node(Node* root, const Point& point, int idx) const; 117 | void find_knn(BoundedPriorityQueue* bpq, Node* root, const Point& pt, int idx) const; 118 | 119 | }; 120 | 121 | template 122 | void KDTree::delete_tree(Node* root) { 123 | if (root == NULL) return; 124 | delete_tree(root->left_node); 125 | delete_tree(root->right_node); 126 | delete root; 127 | root = NULL; 128 | } 129 | 130 | template 131 | typename KDTree::Node* KDTree::insert_node(Node*& root, const Point& point, const ElemType& elem, int idx) { 132 | if (root == NULL) { 133 | root = new Node; 134 | *root = {point, elem, NULL, NULL}; 135 | tree_size++; 136 | return root; 137 | } 138 | else { 139 | if (point == root->point) { 140 | root->element = elem; 141 | return root; 142 | } else if (point[idx] < root->point[idx]) { 143 | return insert_node(root->left_node, point, elem, (idx+1)%N); 144 | } else { 145 | return insert_node(root->right_node, point, elem, (idx+1)%N); 146 | } 147 | } 148 | } 149 | 150 | 151 | template 152 | typename KDTree::Node* KDTree::find_node(Node* root, const Point& point, int idx) const{ 153 | if (root == NULL) return root; 154 | if (root->point == point) return root; 155 | else { 156 | int nxt_idx = (idx+1)%N; 157 | if(point[idx] < root->point[idx]) { 158 | return find_node(root->left_node, point, nxt_idx); 159 | } 160 | else { 161 | return find_node(root->right_node, point, nxt_idx); 162 | } 163 | } 164 | } 165 | 166 | template 167 | void KDTree::find_knn(BoundedPriorityQueue* bpq, Node* root, const Point& pt, int idx) const { 168 | if (root == NULL) return; 169 | bpq->enqueue(root->element, Distance(pt, root->point)); 170 | int nxt_idx = (idx + 1) % N; 171 | if (pt[idx] < root->point[idx]) { 172 | find_knn(bpq, root->left_node, pt, nxt_idx); 173 | } else { 174 | find_knn(bpq, root->right_node, pt, nxt_idx); 175 | } 176 | 177 | double distance = fabs(root->point[idx] - pt[idx]); 178 | if (bpq->worst() > distance || bpq->size() < N) { 179 | if (pt[idx] < root->point[idx]) { 180 | find_knn(bpq, root->right_node, pt, nxt_idx); 181 | } else { 182 | find_knn(bpq, root->left_node, pt, nxt_idx); 183 | } 184 | } 185 | } 186 | 187 | template 188 | typename KDTree::Node* KDTree::copy_tree(Node* src) { 189 | if (src == NULL) return NULL; 190 | Node* copy_node = new Node; 191 | copy_node->element = src->element; 192 | copy_node->point = src->point; 193 | copy_node->left_node = copy_tree(src->left_node); 194 | copy_node->right_node = copy_tree(src->right_node); 195 | return copy_node; 196 | } 197 | 198 | /** KDTree class implementation details */ 199 | template 200 | KDTree::KDTree() { 201 | tree_size = 0; 202 | root_node = NULL; 203 | } 204 | 205 | template 206 | KDTree::~KDTree() { 207 | // TODO: Fill this in. 208 | delete_tree(root_node); 209 | } 210 | 211 | template 212 | size_t KDTree::dimension() const { 213 | return N; 214 | } 215 | 216 | template 217 | size_t KDTree::size() const { 218 | // TODO: Fill this in 219 | return tree_size; 220 | } 221 | 222 | template 223 | bool KDTree::empty() const { 224 | //TODO: Fill in this 225 | return tree_size == 0; 226 | } 227 | 228 | template 229 | bool KDTree::contains(const Point& pt) const { 230 | //TODO: Fill in this 231 | Node* node = find_node(root_node, pt, 0); 232 | return node != NULL; 233 | } 234 | 235 | template 236 | void KDTree::insert(const Point& pt, const ElemType& value) { 237 | //TODO: Fill in this 238 | insert_node(root_node, pt, value, 0); 239 | } 240 | 241 | template 242 | ElemType& KDTree::operator[](const Point& point) { 243 | //TODO: fill in this 244 | ElemType defaultElem {}; 245 | Node* node = find_node(root_node, point, 0); 246 | if (node == NULL) { 247 | node = insert_node(root_node, point, defaultElem, 0); 248 | } 249 | return node->element; 250 | } 251 | 252 | template 253 | ElemType& KDTree::at(const Point& point) { 254 | Node* node = find_node(root_node, point, 0); 255 | if (node == NULL) { 256 | throw std::out_of_range("Out of range"); 257 | } else { 258 | return node->element; 259 | } 260 | } 261 | 262 | template 263 | const ElemType& KDTree::at(const Point& point) const { 264 | Node* node = find_node(root_node, point, 0); 265 | if (node == NULL) { 266 | throw std::out_of_range("Out of range"); 267 | } else { 268 | return node->element; 269 | } 270 | } 271 | 272 | template 273 | ElemType KDTree::kNNValue(const Point& key, size_t k) const { 274 | BoundedPriorityQueue* bpq = new BoundedPriorityQueue(k); 275 | find_knn(bpq, root_node, key, 0); 276 | std::map freq_map; 277 | while (!bpq->empty()) { 278 | auto elem = bpq->dequeueMin(); 279 | if (freq_map.count(elem)) { 280 | freq_map[elem] = freq_map[elem] + 1; 281 | } else { 282 | freq_map[elem] = 1; 283 | } 284 | } 285 | 286 | ElemType max_freq_elem; 287 | size_t max_freq = 0; 288 | for (const auto& [elem, freq]:freq_map) { 289 | if (freq > max_freq) { 290 | max_freq = freq; 291 | max_freq_elem = elem; 292 | } 293 | } 294 | delete bpq; 295 | return max_freq_elem; 296 | } 297 | 298 | template 299 | KDTree::KDTree(const KDTree& rhs):tree_size(rhs.tree_size) { 300 | root_node = copy_tree(rhs.root_node); 301 | } 302 | 303 | template 304 | KDTree& KDTree::operator=(const KDTree& rhs) { 305 | if (&rhs == this) { 306 | return *this; 307 | } 308 | tree_size = rhs.size(); 309 | delete_tree(root_node); 310 | root_node = copy_tree(rhs.root_node); 311 | return *this; 312 | } 313 | 314 | #endif // KDTREE_INCLUDED 315 | -------------------------------------------------------------------------------- /solutions/KDTree/point.h: -------------------------------------------------------------------------------- 1 | /** 2 | * File: Point.h 3 | * ------------- 4 | * A class representing a point in N-dimensional space. Unlike the other class 5 | * templates you've seen before, Point is parameterized over an integer rather 6 | * than a type. This allows the compiler to verify that the type is being used 7 | * correctly. 8 | */ 9 | #ifndef POINT_INCLUDED 10 | #define POINT_INCLUDED 11 | 12 | #include 13 | 14 | template 15 | class Point { 16 | public: 17 | // Type: iterator 18 | // Type: const_iterator 19 | // ------------------------------------------------------------------------ 20 | // Types representing iterators that can traverse and optionally modify the 21 | // elements of the Point. 22 | typedef double* iterator; 23 | typedef const double* const_iterator; 24 | 25 | // size_t size() const; 26 | // Usage: for (size_t i = 0; i < myPoint.size(); ++i) 27 | // ------------------------------------------------------------------------ 28 | // Returns N, the dimension of the point. 29 | size_t size() const; 30 | 31 | // double& operator[](size_t index); 32 | // double operator[](size_t index) const; 33 | // Usage: myPoint[3] = 137; 34 | // ------------------------------------------------------------------------ 35 | // Queries or retrieves the value of the point at a particular point. The 36 | // index is assumed to be in-range. 37 | double& operator[](size_t index); 38 | double operator[](size_t index) const; 39 | 40 | // iterator begin(); 41 | // iterator end(); 42 | // const_iterator begin() const; 43 | // const_iterator end() const; 44 | // Usage: for (Point<3>::iterator itr = myPoint.begin(); itr != myPoint.end(); ++itr) 45 | // ------------------------------------------------------------------------ 46 | // Returns iterators delineating the full range of elements in the Point. 47 | iterator begin(); 48 | iterator end(); 49 | 50 | const_iterator begin() const; 51 | const_iterator end() const; 52 | 53 | private: 54 | // The point's actual coordinates are stored in an array. 55 | double coords[N]; 56 | }; 57 | 58 | // double Distance(const Point& one, const Point& two); 59 | // Usage: double d = Distance(one, two); 60 | // ---------------------------------------------------------------------------- 61 | // Returns the Euclidean distance between two points. 62 | template 63 | double Distance(const Point& one, const Point& two); 64 | 65 | // bool operator==(const Point& one, const Point& two); 66 | // bool operator!=(const Point& one, const Point& two); 67 | // Usage: if (one == two) 68 | // ---------------------------------------------------------------------------- 69 | // Returns whether two points are equal or not equal. 70 | template 71 | bool operator==(const Point& one, const Point& two); 72 | 73 | template 74 | bool operator!=(const Point& one, const Point& two); 75 | 76 | /** Point class implementation details */ 77 | 78 | #include 79 | 80 | template 81 | size_t Point::size() const { 82 | return N; 83 | } 84 | 85 | template 86 | double& Point::operator[] (size_t index) { 87 | return coords[index]; 88 | } 89 | 90 | template 91 | double Point::operator[] (size_t index) const { 92 | return coords[index]; 93 | } 94 | 95 | template 96 | typename Point::iterator Point::begin() { 97 | return coords; 98 | } 99 | 100 | template 101 | typename Point::const_iterator Point::begin() const { 102 | return coords; 103 | } 104 | 105 | template 106 | typename Point::iterator Point::end() { 107 | return begin() + size(); 108 | } 109 | 110 | template 111 | typename Point::const_iterator Point::end() const { 112 | return begin() + size(); 113 | } 114 | 115 | // Computing the distance uses the standard distance formula: the square root of 116 | // the sum of the squares of the differences between matching components. 117 | template 118 | double Distance(const Point& one, const Point& two) { 119 | double result = 0.0; 120 | for (size_t i = 0; i < N; ++i) 121 | result += (one[i] - two[i]) * (one[i] - two[i]); 122 | 123 | return sqrt(result); 124 | } 125 | 126 | // Equality is implemented using the equal algorithm, which takes in two ranges 127 | // and reports whether they contain equal values. 128 | template 129 | bool operator==(const Point& one, const Point& two) { 130 | return std::equal(one.begin(), one.end(), two.begin()); 131 | } 132 | 133 | template 134 | bool operator!=(const Point& one, const Point& two) { 135 | return !(one == two); 136 | } 137 | 138 | #endif // POINT_INCLUDED 139 | -------------------------------------------------------------------------------- /textbooks/full_course_reader.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wengwz/CS106L-Self-Learning/c1e997365e5b18c32a1793d70caf9e975c1410df/textbooks/full_course_reader.pdf --------------------------------------------------------------------------------