diff --git a/CMakeLists.txt b/CMakeLists.txt index a57d44c..60396bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,9 +4,9 @@ project(ordered_map LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -# Add source files -add_library(ordered_map src/doubly_linked_list.cpp) -target_include_directories(ordered_map PUBLIC include) +# Create an INTERFACE library since this is header-only +add_library(ordered_map INTERFACE) +target_include_directories(ordered_map INTERFACE include) # Enable testing enable_testing() @@ -35,6 +35,7 @@ add_executable( tests/ordered_map/insertion_tests.cpp tests/ordered_map/lookup_tests.cpp tests/ordered_map/iterator_tests.cpp + tests/ordered_map/constructor_tests.cpp ) target_link_libraries(tests PRIVATE ordered_map Catch2::Catch2WithMain) diff --git a/README.md b/README.md index ed773d1..91de09d 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,12 @@ -# Ordered Map (building in progress...) +# Ordered Map Library -A C++ project implementing an ordered map using a doubly-linked list to maintain element insertion order, built with CMake, tested with Catch2, and formatted with Clang-Format. +A C++ implementation of an ordered map data structure that maintains insertion order while providing O(1) key-value lookups. ## Table of Contents +- [Features](#features) +- [Usage](#usage) +- [Implementation Details](#implementation-details) - [Prerequisites](#prerequisites) - [Project Structure](#project-structure) - [Setting Up VS Code (or Cursor)](#setting-up-vs-code-or-cursor) @@ -11,8 +14,47 @@ A C++ project implementing an ordered map using a doubly-linked list to maintain - [Building and Running Tests](#building-and-running-tests) - [GitHub Actions CI](#github-actions-ci) - [Adding a New Test](#adding-a-new-test) -- [Troubleshooting](#troubleshooting) -- [Project Status](#project-status) +- [Future Improvements](#future-improvements) + +## Features + +- Maintains insertion order of key-value pairs +- O(1) key-value lookups using hash table +- O(1) insertion and deletion operations +- STL-like iterator interface +- Memory safe with smart pointers +- Exception handling for out-of-range access + +## Usage + +```cpp +#include "ordered_map.hpp" + +// Create an ordered map +OrderedMap map; + +// Insert key-value pairs +map.insert("apple", 1); +map.insert("banana", 2); +map.insert("cherry", 3); + +// Access values +int value = map["apple"]; // Returns 1 + +// Iterate in insertion order +for (const auto& pair : map) { + std::cout << pair.first << ": " << pair.second << std::endl; +} +``` + +## Implementation Details + +The implementation uses a combination of: + +- Doubly linked list for maintaining insertion order +- Hash table (std::unordered_map) for O(1) lookups +- Smart pointers for memory management +- STL-compatible iterator interface ## Prerequisites @@ -43,7 +85,7 @@ ordered_map/ ├── .clang-format # Code formatting rules ├── include/ │ ├── doubly_linked_list.hpp # Doubly-linked list implementation -│ └── ordered_map.hpp # (Future) Ordered map interface +│ └── ordered_map.hpp # Ordered map interface ├── src/ ├── tests/ │ └── doubly_linked_list/ # Test files for doubly linked list @@ -152,41 +194,20 @@ ordered_map/ ./build.sh ``` -## Troubleshooting - -- **Squiggly Lines**: - - Ensure `build/compile_commands.json` exists (via `build.sh`). - - Verify `.vscode/settings.json` paths. -- **Build Errors**: - - Clear build: `rm -rf build && ./build.sh`. - - Check prerequisites. -- **Test Failures**: - - Inspect assertions in the relevant test file. - - Run specific tests: `./build/tests --tags [tag]`. -- **GitHub Actions Failures**: - - Check logs in the "Actions" tab on GitHub. - - Ensure `build.sh` and dependencies are compatible with `ubuntu-latest`. - -## Project Status - -The project is currently in development, with the following components: - -1. ✅ Doubly Linked List Implementation - - - Basic operations (push_back, pop_back, push_front, pop_front) - - Memory management using smart pointers - - Comprehensive test coverage organized by functionality - - Iterator support (basic implementation) - -2. 🔄 Ordered Map Implementation (Coming Soon) - - Will use the doubly linked list as its underlying data structure - - Will maintain insertion order while providing map-like functionality - -### Checklist for future improvements - -- [ ] DoublyLinkedList: Add tests for copying & moving -- [ ] DoublyLinkedList: Add `erase` method to erase elements given their iterator +## Future Improvements + +- [ ] Add const iterators +- [ ] Add reverse iterators +- [ ] Add range-based operations +- [ ] Add performance benchmarks +- [ ] Add support for custom allocators +- [ ] Add std::move and copying semantics +- [ ] **DoublyLinkedList:** Add tests for copying & moving +- [ ] **DoublyLinkedList:** Add `erase` method to erase elements given their iterator +- [ ] **DoublyLinkedList:** Add pre and post decrement operators +- [ ] **DoublyLinkedList:** Test front(), back(), insertion and deletion functions for copying behavior +- [ ] **OrderedMap:** Add support for initializing map with +- [ ] **OrderedMap:** Add support for custom hash functions +- [ ] **OrderedMap:** Add `erase` method to erase elements given their iterator or key +- [ ] Test for memory leaks -- [ ] DoublyLinkedList: Add pre and post decrement operators -- [ ] DoublyLinkedList: Test front(), back(), insertion and deletion functions for copying behavior -- [ ] DoublyLinkedList: Test front(), back(), insertion and deletion functions for destructor behavior diff --git a/include/ordered_map.hpp b/include/ordered_map.hpp index 1fe5686..5765020 100644 --- a/include/ordered_map.hpp +++ b/include/ordered_map.hpp @@ -17,6 +17,20 @@ class OrderedMap { public: OrderedMap() {} + + OrderedMap(std::initializer_list> init_list){ + for (const auto& pair : init_list) { + insert(pair.first, pair.second); + } + } + + template + OrderedMap(ParamIterator begin, ParamIterator end){ + for (auto it = begin; it != end; it++) { + insert(it->first, it->second); + } + } + void insert(const KeyType& key, const ValueType& value) { if (_map.find(key) == _map.end()) { _list.push_back(std::make_pair(key, value)); @@ -55,6 +69,10 @@ class OrderedMap { return *_it; } + std::pair* operator->() { + return &(*_it); + } + Iterator& operator++() { _it++; return *this; @@ -74,6 +92,7 @@ class OrderedMap { return !(*this == other); } + }; Iterator begin() { diff --git a/src/doubly_linked_list.cpp b/src/doubly_linked_list.cpp deleted file mode 100644 index 0a5f75b..0000000 --- a/src/doubly_linked_list.cpp +++ /dev/null @@ -1,3 +0,0 @@ -#include "doubly_linked_list.hpp" - - diff --git a/tests/ordered_map/constructor_tests.cpp b/tests/ordered_map/constructor_tests.cpp new file mode 100644 index 0000000..201b4dc --- /dev/null +++ b/tests/ordered_map/constructor_tests.cpp @@ -0,0 +1,54 @@ +#include +#include "ordered_map.hpp" +#include + +TEST_CASE("empty ordered map has size 0", "[constructor]"){ + OrderedMap o_map; + REQUIRE(o_map.size() == 0); +} + +TEST_CASE("ordered map constructed with initializer list has size 2", "[constructor]"){ + OrderedMap o_map = {{1, 2}, {3, 4}}; + REQUIRE(o_map.size() == 2); +} + +TEST_CASE("ordered map with initializer list contains the given values", "[constructor]"){ + OrderedMap o_map = {{1, 2}, {3, 4}}; + REQUIRE(o_map.at(1) == 2); + REQUIRE(o_map.at(3) == 4); +} + +TEST_CASE("ordered map constructed with vector of pairs iterator contains the given values", "[constructor]"){ + std::vector> vec = {{1, 2}, {3, 4}}; + OrderedMap o_map(vec.begin(), vec.end()); + REQUIRE(o_map.at(1) == 2); + REQUIRE(o_map.at(3) == 4); +} + +TEST_CASE("ordered map constructed with vector of pairs iterator maintains order", "[constructor]"){ + std::vector> vec; + for (int i = 0; i < 1000; i++) { + vec.push_back({std::to_string(i), i}); + } + + OrderedMap o_map(vec.begin(), vec.end()); + + auto it_map = o_map.begin(); + auto it_vec = vec.begin(); + while (it_map != o_map.end() && it_vec != vec.end()) { + REQUIRE(it_map->first == it_vec->first); + REQUIRE(it_map->second == it_vec->second); + it_map++; + it_vec++; + } + REQUIRE(it_map == o_map.end()); + REQUIRE(it_vec == vec.end()); +} + +TEST_CASE("ordered map constructed with map iterators contains the given values", "[constructor]"){ + std::map map = {{1, 2}, {3, 4}}; + OrderedMap o_map(map.begin(), map.end()); + REQUIRE(o_map.at(1) == 2); + REQUIRE(o_map.at(3) == 4); +} + diff --git a/tests/ordered_map/insertion_tests.cpp b/tests/ordered_map/insertion_tests.cpp index 3f6f95c..605ef74 100644 --- a/tests/ordered_map/insertion_tests.cpp +++ b/tests/ordered_map/insertion_tests.cpp @@ -6,32 +6,32 @@ TEST_CASE("empty ordered map has size 0", "[insert]") { REQUIRE(o_map.size() == 0); } -TEST_CASE("inserting <1, 2> into an empty ordered map", "[insert]") { +TEST_CASE("inserting {1, 2} into an empty ordered map", "[insert]") { OrderedMap o_map; o_map.insert(1, 2); REQUIRE(o_map.size() == 1); } -TEST_CASE("insert <1, 2> and <1, 3>, size = 1", "[insert]") { +TEST_CASE("insert {1, 2} and {1, 3}, size = 1", "[insert]") { OrderedMap o_map; o_map.insert(1, 2); o_map.insert(1, 3); REQUIRE(o_map.size() == 1); } -TEST_CASE("insert <1, 2> and <2, 3>, size = 2", "[insert]") { +TEST_CASE("insert {1, 2} and {2, 3}, size = 2", "[insert]") { OrderedMap o_map; o_map.insert(1, 2); o_map.insert(2, 3); REQUIRE(o_map.size() == 2); } +TEST_CASE("insert {1, 2} into the map 3 times, size = 1", "[insert]"){ + OrderedMap o_map; + o_map.insert(1, 2); + o_map.insert(1, 2); + o_map.insert(1, 2); - - - - - - - + REQUIRE(o_map.size() == 1); +} diff --git a/tests/ordered_map/size_tests.cpp b/tests/ordered_map/size_tests.cpp index f9cd138..4640e7f 100644 --- a/tests/ordered_map/size_tests.cpp +++ b/tests/ordered_map/size_tests.cpp @@ -19,6 +19,15 @@ TEST_CASE("size is 2 after inserting two elements", "[size]") { REQUIRE(map.size() == 2); } +TEST_CASE("size is 100,000 after inserting 100,000 elements", "[size]") { + OrderedMap map; + int size = int(1e5); + for (int i = 0; i < size; i++) { + map.insert(i, i); + } + REQUIRE(map.size() == size); +} + TEST_CASE("empty() returns true when map is empty", "[empty]") { OrderedMap map; REQUIRE(map.empty()); @@ -29,5 +38,3 @@ TEST_CASE("empty() returns false when map is not empty", "[empty]") { map.insert(1, 2); REQUIRE(!map.empty()); } - -