-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
246 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
#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')); | ||
CHECK(bimap.getRight(1) == 'a'); | ||
CHECK(bimap.getRight(2) == 'b'); | ||
CHECK(bimap.getLeft('a') == 1); | ||
CHECK(bimap.getLeft('b') == 2); | ||
|
||
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')); | ||
CHECK(bimap.getRight(1) == 'b'); | ||
CHECK(bimap.getLeft('b') == 1); | ||
|
||
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')); | ||
} |