Skip to content

Commit

Permalink
feat(memory): add UnorderedBimap
Browse files Browse the repository at this point in the history
  • Loading branch information
RiscadoA committed Oct 8, 2023
1 parent b014ec7 commit 4b29fa6
Show file tree
Hide file tree
Showing 3 changed files with 240 additions and 0 deletions.
164 changes: 164 additions & 0 deletions core/include/cubos/core/memory/unordered_bimap.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/// @file
/// @brief Class @ref cubos::core::memory::UnorderedBimap.
/// @ingroup core-memory

#pragma once

#include <unordered_map>

namespace cubos::core::memory
{
/// @brief A bidirectional hash table.
/// @tparam L Left type.
/// @tparam R Right type.
/// @tparam LHash Hash functor type for @p L.
/// @tparam RHash Hash functor type for @p R.
/// @ingroup core-memory
template <typename L, typename R, typename LHash = std::hash<L>, typename RHash = std::hash<R>>
class UnorderedBimap final
{
public:
using Iterator = std::unordered_map<L, R, LHash>::const_iterator;

/// @brief Adds a new entry to the map.
/// @note If any of the values already exists, the old entries with them are removed.
/// @param left Left value.
/// @param right Right value.
void add(L left, R right)
{
auto leftIt = mLeftToRight.find(left);
if (leftIt != mLeftToRight.end())
{
mRightToLeft.erase(leftIt->second);
}

auto rightIt = mRightToLeft.find(right);
if (rightIt != mRightToLeft.end())
{
mLeftToRight.erase(rightIt->second);
}

mLeftToRight.insert_or_assign(left, right);
mRightToLeft.insert_or_assign(right, left);
}

/// @brief Removes the entry associated to the given left value.
/// @param left Left value.
/// @return Whether the entry was removed.
bool removeLeft(const L& left)
{
auto it = mLeftToRight.find(left);
if (it == mLeftToRight.end())
{
return false;
}

mRightToLeft.erase(it->second);
mLeftToRight.erase(it);
return true;
}

/// @brief Removes the entry associated to the given right value.
/// @param right Right value.
/// @return Whether the entry was removed.
bool removeRight(const R& right)
{
auto it = mRightToLeft.find(right);
if (it == mRightToLeft.end())
{
return false;
}

mLeftToRight.erase(it->second);
mRightToLeft.erase(it);
return true;
}

/// @brief Checks if the map has the given entry.
/// @param left Left value.
/// @param right Right value.
/// @return Whether the map has the entry.
bool has(const L& left, const R& right) const
{
auto it = mLeftToRight.find(left);
if (it == mLeftToRight.end())
{
return false;
}
return it->second == right;
}

/// @brief Checks if the map contains the given left value.
/// @param left Left value.
/// @return Whether the map contains the value.
bool hasLeft(const L& left) const
{
return mLeftToRight.find(left) != mLeftToRight.end();
}

/// @brief Checks if the map contains the given right value.
/// @param right Right value.
/// @return Whether the map contains the value.
bool hasRight(const R& right) const
{
return mRightToLeft.find(right) != mRightToLeft.end();
}

/// @brief Gets the right value associated to the given left value.
/// @note Aborts if the left value isn't stored.
/// @param left Left value.
/// @return Right value.
const R& getRight(const L& left) const
{
return mLeftToRight.at(left);
}

/// @brief Gets the left value associated to the given right value.
/// @note Aborts if the right value isn't stored.
/// @param right Right value.
/// @return Left value.
const L& getLeft(const R& right) const
{
return mRightToLeft.at(right);
}

/// @brief Clears the map.
void clear()
{
mLeftToRight.clear();
mRightToLeft.clear();
}

/// @brief Gets the number of entries in the map.
/// @return Entry count.
std::size_t size() const
{
return mLeftToRight.size();
}

/// @brief Checks if the map is empty.
/// @return Whether the map is empty.
bool empty() const
{
return mLeftToRight.empty();
}

/// @brief Gets an iterator to the beginning of the map.
/// @return Iterator.
Iterator begin() const
{
return mLeftToRight.begin();
}

/// @brief Gets an iterator to the end of the map.
/// @return Iterator.
Iterator end() const
{
return mLeftToRight.end();
}

private:
std::unordered_map<L, R, LHash> mLeftToRight;
std::unordered_map<R, L, RHash> mRightToLeft;
};
} // namespace cubos::core::memory
2 changes: 2 additions & 0 deletions core/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ add_executable(
data/fs/file_system.cpp
data/context.cpp

memory/unordered_bimap.cpp

ecs/registry.cpp
ecs/world.cpp
ecs/query.cpp
Expand Down
74 changes: 74 additions & 0 deletions core/tests/memory/unordered_bimap.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#include <doctest/doctest.h>

#include <cubos/core/memory/unordered_bimap.hpp>

TEST_CASE("memory::UnorderedBimap")
{
using cubos::core::memory::UnorderedBimap;

UnorderedBimap<int, char> bimap{};

SUBCASE("just initialized")
{
}

SUBCASE("had entries")
{
bimap.add(1, 'a');

CHECK_FALSE(bimap.empty());
CHECK(bimap.size() == 1);

CHECK(bimap.has(1, 'a'));
CHECK(bimap.hasLeft(1));
CHECK(bimap.hasRight('a'));
CHECK(bimap.getLeft('a') == 1);
CHECK(bimap.getRight(1) == 'a');

CHECK(bimap.begin() != bimap.end());
CHECK(bimap.begin()->first == 1);
CHECK(bimap.begin()->second == 'a');
CHECK(++bimap.begin() == bimap.end());

bimap.add(2, 'b');

CHECK(bimap.size() == 2);
CHECK(bimap.has(1, 'a'));
CHECK(bimap.has(2, 'b'));

bimap.add(1, 'b');

CHECK(bimap.size() == 1);
CHECK(bimap.has(1, 'b'));
CHECK_FALSE(bimap.has(1, 'a'));
CHECK_FALSE(bimap.has(2, 'b'));

SUBCASE("remove left")
{
CHECK(bimap.removeLeft(1));
}

SUBCASE("remove right")
{
CHECK(bimap.removeRight('b'));
}

SUBCASE("clear")
{
bimap.clear();
}
}

// Shouldn't contain anything
CHECK(bimap.empty());
CHECK(bimap.size() == 0);

CHECK_FALSE(bimap.has(1, 'a'));
CHECK_FALSE(bimap.hasLeft(1));
CHECK_FALSE(bimap.hasRight('a'));

CHECK(bimap.begin() == bimap.end());

CHECK_FALSE(bimap.removeLeft(1));
CHECK_FALSE(bimap.removeRight('a'));
}

0 comments on commit 4b29fa6

Please sign in to comment.