Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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)

Expand Down
105 changes: 63 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,60 @@
# 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)
- [Formatting Code](#formatting-code)
- [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<std::string, int> 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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
19 changes: 19 additions & 0 deletions include/ordered_map.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ class OrderedMap {

public:
OrderedMap() {}

OrderedMap(std::initializer_list<std::pair<const KeyType, ValueType>> init_list){
for (const auto& pair : init_list) {
insert(pair.first, pair.second);
}
}

template <typename ParamIterator>
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));
Expand Down Expand Up @@ -55,6 +69,10 @@ class OrderedMap {
return *_it;
}

std::pair<KeyType, ValueType>* operator->() {
return &(*_it);
}

Iterator& operator++() {
_it++;
return *this;
Expand All @@ -74,6 +92,7 @@ class OrderedMap {
return !(*this == other);
}


};

Iterator begin() {
Expand Down
3 changes: 0 additions & 3 deletions src/doubly_linked_list.cpp

This file was deleted.

54 changes: 54 additions & 0 deletions tests/ordered_map/constructor_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#include <catch2/catch_test_macros.hpp>
#include "ordered_map.hpp"
#include <map>

TEST_CASE("empty ordered map has size 0", "[constructor]"){
OrderedMap<int, int> o_map;
REQUIRE(o_map.size() == 0);
}

TEST_CASE("ordered map constructed with initializer list has size 2", "[constructor]"){
OrderedMap<int, int> o_map = {{1, 2}, {3, 4}};
REQUIRE(o_map.size() == 2);
}

TEST_CASE("ordered map with initializer list contains the given values", "[constructor]"){
OrderedMap<int, int> 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<std::pair<const int, int>> vec = {{1, 2}, {3, 4}};
OrderedMap<int, int> 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<std::pair<const std::string, int >> vec;
for (int i = 0; i < 1000; i++) {
vec.push_back({std::to_string(i), i});
}

OrderedMap<std::string, int> 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<const int, int> map = {{1, 2}, {3, 4}};
OrderedMap<int, int> o_map(map.begin(), map.end());
REQUIRE(o_map.at(1) == 2);
REQUIRE(o_map.at(3) == 4);
}

20 changes: 10 additions & 10 deletions tests/ordered_map/insertion_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<int, int> 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<int, int> 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<int, int> 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<int, int> o_map;
o_map.insert(1, 2);
o_map.insert(1, 2);
o_map.insert(1, 2);








REQUIRE(o_map.size() == 1);
}

11 changes: 9 additions & 2 deletions tests/ordered_map/size_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<int, int> 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<int, int> map;
REQUIRE(map.empty());
Expand All @@ -29,5 +38,3 @@ TEST_CASE("empty() returns false when map is not empty", "[empty]") {
map.insert(1, 2);
REQUIRE(!map.empty());
}