From 9043d76da8214c8bbe3f3343c72c5382b85dc36a Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Wed, 23 Oct 2024 18:39:56 -0400 Subject: [PATCH] Add Bimap, a basic bi-directional map --- lib/core/CMakeLists.txt | 2 + lib/core/include/qx/core/qx-bimap.h | 181 +++++++++++++++ lib/core/src/qx-bimap.dox | 341 ++++++++++++++++++++++++++++ 3 files changed, 524 insertions(+) create mode 100644 lib/core/include/qx/core/qx-bimap.h create mode 100644 lib/core/src/qx-bimap.dox diff --git a/lib/core/CMakeLists.txt b/lib/core/CMakeLists.txt index 5d382754..772f0543 100644 --- a/lib/core/CMakeLists.txt +++ b/lib/core/CMakeLists.txt @@ -5,6 +5,7 @@ qx_add_component("Core" qx-algorithm.h qx-array.h qx-base85.h + qx-bimap.h qx-bitarray.h qx-bytearray.h qx-char.h @@ -74,6 +75,7 @@ qx_add_component("Core" __private/qx-processwaiter_linux.h __private/qx-processwaiter_linux.cpp DOC_ONLY + qx-bimap.dox qx-regularexpression.dox qx-bytearray.dox qx-exclusiveaccess.dox diff --git a/lib/core/include/qx/core/qx-bimap.h b/lib/core/include/qx/core/qx-bimap.h new file mode 100644 index 00000000..635e8796 --- /dev/null +++ b/lib/core/include/qx/core/qx-bimap.h @@ -0,0 +1,181 @@ +#ifndef QX_BIMAP_H +#define QX_BIMAP_H + +#include + +// Qt Includes +#include + +namespace Qx +{ + +template +concept asymmetric_bimap = !std::same_as; + +template +class Bimap +{ +//-Aliases------------------------------------------------------------------------------------------------------ +public: + using left_type = Left; + using right_type = Right; + +//-Instance Variables------------------------------------------------------------------------------------------- +private: + QHash mL2R; + QHash mR2L; + +//-Constructor-------------------------------------------------------------------------------------------------- +public: + Bimap() {} + + Bimap(std::initializer_list> list) + { + reserve(list.size()); + for(auto it = list.begin(); it != list.end(); ++it) + insert(it->first, it->second); + } + +//-Class Functions------------------------------------------------------------------------------------------- +private: + template + void removeCrossReference(AMap& am, BMap& bm, const V& v) + { + if constexpr(std::same_as) + Q_ASSERT(&am != &bm); // Ensure different maps are used if the types are the same + + if(am.contains(v)) + bm.remove(*am[v]); + } + + template + bool remove(AMap& am, BMap& bm, const V& v) + { + if(!am.contains(v)) + return false; + + bm.remove(*am[v]); + am.remove(v); + return true; + } + +//-Instance Functions------------------------------------------------------------------------------------------- +private: + bool existingRelation(const Left& l, const Right& r) const { return mL2R.contains(l) && *mL2R[l] == r; } + + void removeCrossReferences(const Left& l, const Right& r) + { + // Remove to-be stale relations + removeCrossReference(mL2R, mR2L, l); + removeCrossReference(mR2L, mL2R, r); + } + + void addOrUpdateRelation(const Left& l, const Right& r) + { + auto lItr = mL2R.insert(l, nullptr); + auto rItr = mR2L.insert(r, nullptr); + lItr.value() = &rItr.key(); + rItr.value() = &lItr.key(); + } + +public: + void insert(const Left& l, const Right& r) + { + if(existingRelation(l, r)) + return; + + removeCrossReferences(l, r); + addOrUpdateRelation(l, r); + } + + bool containsLeft(const Left& l) const { return mL2R.contains(l); } + bool containsRight(const Right& r) const { return mR2L.contains(r); } + + Right fromLeft(const Left& l) const + { + return mL2R.contains(l) ? *mL2R[l] : Right(); + } + + Right fromLeft(const Left& l, const Right& defaultValue) const + { + return mL2R.contains(l) ? *mL2R[l] : defaultValue; + } + + Left fromRight(const Right& l) const + { + return mR2L.contains(l) ? *mR2L[l] : Left(); + } + + Left fromRight(const Right& l, const Left& defaultValue) const + { + return mR2L.contains(l) ? *mR2L[l] : defaultValue; + } + + Right from(const Left& l) const requires asymmetric_bimap { return fromLeft(l); } + Right from(const Left& l, const Right& defaultValue) const requires asymmetric_bimap { return fromLeft(l, defaultValue); } + Left from(const Right& r) const requires asymmetric_bimap { return fromRight(r); } + Left from(const Right& r, const Left& defaultValue) const requires asymmetric_bimap { return fromRight(r, defaultValue); } + + Left toLeft(const Right& r) const { return fromRight(r); } + Left toLeft(const Right& r, const Left& defaultValue) const { return fromRight(r, defaultValue); } + Right toRight(const Left& r) const { return fromLeft(r); } + Right toRight(const Left& r, const Right& defaultValue) const { return fromLeft(r, defaultValue); } + + bool removeLeft(const Left& l) { return remove(mL2R, mR2L, l); } + bool removeRight(const Right& r) { return remove(mR2L, mL2R, r); } + + qsizetype size() const { return mL2R.size(); } + qsizetype count() const { return size(); } + bool isEmpty() const { return size() == 0; } + bool empty() const { return isEmpty(); } + + qsizetype capacity() const { return mL2R.capacity(); } + void clear() { mL2R.clear(); mR2L.clear(); } + void reserve(qsizetype size) { mL2R.reserve(size); mR2L.reserve(size); } + void squeeze() { mL2R.squeeze(); mR2L.squeeze(); } + + QList lefts() const { return mL2R.keys(); } + QList rights() const { return mR2L.keys(); } + +//-Operators--------------------------------------------------------------------------------------------- +public: + /* TODO: Having non-const versions of these that return a reference would require + * const_cast<>'ing away the constness of the key of the "other" map, and I'm not + * sure if modifying that reference directly instead of using QHash's methods would + * break the hash or not. + */ + Right operator[](const Left& l) const requires asymmetric_bimap + { + /* Alternatively these [] operators could insert a default constructed pair opposite if the + * key is not found, like QHash::operator[]() does by using our insert function (to handle + * both maps), but for now we do this. + */ + if(!mL2R.contains(l)) + throw std::invalid_argument("Access into bimap with a value it does not contain!"); + return *mL2R[l]; + } + + Left operator[](const Right& r) const requires asymmetric_bimap + { + if(!mR2L.contains(r)) + throw std::invalid_argument("Access into bimap with a value it does not contain!"); + return *mR2L[r]; + } + + bool operator==(const Bimap& other) const + { + const auto& oL2R = other.mL2R; + for (auto [l, rp] : mL2R.asKeyValueRange()) + if(!oL2R.contains(l) || *rp != *oL2R[l]) + return false; + + return true; + } + + bool operator!=(const Bimap& other) const = default; +}; + + +} + +#endif // QX_BIMAP_H diff --git a/lib/core/src/qx-bimap.dox b/lib/core/src/qx-bimap.dox new file mode 100644 index 00000000..a7345841 --- /dev/null +++ b/lib/core/src/qx-bimap.dox @@ -0,0 +1,341 @@ +namespace Qx +{ + +/*! + * @concept asymmetric_bimap + * @brief Specifies that a bimap has different Left and Right types. + * + * Satisfied if @c Left is not the same as @c Right. + */ + +//=============================================================================================================== +// Bimap +//=============================================================================================================== + +/*! + * @class Bimap qx/core/qx-bimap.h + * @ingroup qx-core + * + * @brief The Bimap template class offers a rudimentary bi-directional associative map. + * + * Qx::Bimap is like QHash, except that instead of Key and Value there is + * Left and Right, meaning that no neither type in any specialization of the container is more + * significant than the other. Lookup of one of the "side's" values using the other is possible + * via fromLeft() and fromRight(). + * + * @warning This class is somewhat incomplete as it currently does not have iterators, nor + * a copy constructor/assignment operator implemented. + */ + +//-Aliases-------------------------------------------------------------------------------------------------- +//Public: +/*! + * @typedef Bimap::left_type + * + * Typedef for Left. + */ + +/*! + * @typedef Bimap::right_type + * + * Typedef for Right. + */ + +//-Constructor---------------------------------------------------------------------------------------------- +//Public: +/*! + * @fn Bimap::Bimap() + * + * Creates an empty bimap. + * + * @sa clear(). + */ + +/*! + * @fn Bimap::Bimap(std::initializer_list> list) + * + * Creates a bimap with a copy of each of the elements in the initializer list @a list. + */ + +//-Instance Functions---------------------------------------------------------------------------------------------- +//Public: +/*! + * @fn void Bimap::insert(const Left& l, const Right& r) + * + * Inserts a new relation between the Left value @a l and Right value @a r. + * + * If there is already a relationship for either value, that relationship is + * removed. + */ + +/*! + * @fn bool Bimap::containsLeft(const Left& l) const + * + * Returns @c true if the bimap contains a relationship with the Left value @a l; otherwise, + * returns @c false. + * + * @sa containsRight() and count(). + */ + +/*! + * @fn bool Bimap::containsRight(const Right& r) const + * + * Returns @c true if the bimap contains a relationship with the Right value @a r; otherwise, + * returns @c false. + * + * @sa containsLeft() and count(). + */ + +/*! + * @fn Right Bimap::fromLeft(const Left& l) const + * + * Returns the Right value associated with Left value @a l. + * + * If the bimap does not contain a relationship with @a l, a default constructed Right + * value is returned. + * + * @sa fromRight(). + */ + +/*! + * @fn Right Bimap::fromLeft(const Left& l, const Right& defaultValue) const + * + * @overload + * + * Returns the Right value associated with Left value @a l. + * + * If the bimap does not contain a relationship with @a l, @a defaultValue is returned. + */ + +/*! + * @fn Left Bimap::fromRight(const Right& r) const + * + * Returns the Left value associated with Right value @a r. + * + * If the bimap does not contain a relationship with @a r, a default constructed Left + * value is returned. + * + * @sa fromLeft(). + */ + +/*! + * @fn Left Bimap::fromRight(const Right& r, const Left& defaultValue) const + * + * @overload + * + * Returns the Left value associated with Right value @a r. + * + * If the bimap does not contain a relationship with @a r, @a defaultValue is returned. + */ + +/*! + * @fn Right Bimap::from(const Left& l) const + * + * Same as fromLeft(). + */ + +/*! + * @fn Right Bimap::from(const Left& l, const Right& defaultValue) const + * + * Same as fromLeft(const Left&, const Right&). + */ + +/*! + * @fn Left Bimap::from(const Right& r) const + * + * Same as fromRight(). + */ + +/*! + * @fn Left Bimap::from(const Right& r, const Left& defaultValue) const + * + * Same as fromLeft(const Right&, const Left&). + */ + +/*! + * @fn Left Bimap::toLeft(const Right& r) const + * + * Same as fromRight(). + * + * @sa toRight(). + */ + +/*! + * @fn Left Bimap::toLeft(const Right& r, const Left& defaultValue) const + * + * @overload + */ + +/*! + * @fn Right Bimap::toRight(const Left& l) const + * + * Same as fromLeft(). + * + * @sa toLeft(). + */ + +/*! + * @fn Right Bimap::toRight(const Left& l, const Right& defaultValue) const + * + * @overload + */ + +/*! + * @fn bool Bimap::removeLeft(const Left& l) + * + * Removes the relationship containing the Left value @a l from the bimap if present and + * returns @c true; otherwise, returns @c false. + * + * @sa removeRight() and clear(). + */ + +/*! + * @fn bool Bimap::removeRight(const Right& r) + * + * Removes the relationship containing the Right value @a r from the bimap if present and + * returns @c true; otherwise, returns @c false. + * + * @sa removeLeft() and clear(). + */ + +/*! + * @fn qsizetype Bimap::size() const + * + * Returns the number of relations in the bimap. + * + * @sa isEmpty() and count(). + */ + +/*! + * @fn qsizetype Bimap::count() const + * + * Same as size(). + */ + +/*! + * @fn bool Bimap::isEmpty() const + * + * Returns @c true if the bimap contains no relations; otherwise, returns @c false. + * + * @sa size(). + */ + +/*! + * @fn bool Bimap::empty() const + * + * Same as isEmpty(). + */ + +/*! + * @fn qsizetype Bimap::capacity() const + * + * Returns the number of buckets in the bimap's internal table. + * + * The sole purpose of this function is to provide a means of fine tuning Bimap's memory + * usage. In general, you will rarely ever need to call this function. If you want to know + * how many items are in the bimap, call size(). + * + * @sa reserve() and squeeze(). + */ + +/*! + * @fn void Bimap::clear() + * + * Removes all relations from the bimap and frees up all memory used by it. + * + * @sa remove(). + */ + +/*! + * @fn void Bimap::reserve() + * + * Ensures that the bimap's internal table has space to store at least @a size items without + * having to grow the table. + * + * This function is useful for code that needs to build a huge bimap and wants to avoid repeated + * reallocation. + * + * In general, you will rarely ever need to call this function. Bimap's internal table + * automatically grows to provide good performance without wasting too much memory. + * + * @sa squeeze() and capacity(). + */ + +/*! + * @fn void Bimap::squeeze() + * + * Reduces the size of the Bimap's internal table to save memory. + * + * The sole purpose of this function is to provide a means of fine tuning Bimap's memory usage. + * In general, you will rarely ever need to call this function. + * + * @sa reserve() and capacity(). + */ + +/*! + * @fn QList Bimap::lefts() const + * + * Returns a list containing all of the Left values in the bimap, in an arbitrary order. + * + * This function creates a new list, in linear time. + * + * @sa rights() and fromRight(). + */ + +/*! + * @fn QList Bimap::rights() const + * + * Returns a list containing all of the Right values in the bimap, in an arbitrary order. + * + * This function creates a new list, in linear time. + * + * @sa lefts() and fromLeft(). + */ + +//-Operators--------------------------------------------------------------------------------------------- +//Public: +/*! + * @fn Right Bimap::operator[](const Left& l) const + * + * Returns the Right value associated with the Left value @a l. + * + * Throws `std::invalid_argument()` if the bimap does not contain a relation with the Left value @a l. + * + * @sa fromLeft() and fromRight(). + */ + +/*! + * @fn Left Bimap::operator[](const Right& r) const + * + * Returns the Left value associated with the Right value @a r. + * + * Throws `std::invalid_argument()` if the bimap does not contain a relation with the Right value @a r. + * + * @sa fromLeft() and fromRight(). + */ + +/*! + * @fn bool Bimap::operator==(const Bimap& other) const + * + * Returns @c true if @a other is equal to this bimap; otherwise, returns @c false. + * + * Two bimap's are considered equal if they contain the same (right, left) relationships. + * + * This function requires the Right and Left types to implement operator==(). + * + * @sa operator!=(). + */ + +/*! + * @fn bool Bimap::operator!=(const Bimap& other) const + * + * Returns @c true if @a other is not equal to this bimap; otherwise, returns @c false. + * + * Two bimap's are considered equal if they contain the same (right, left) relationships. + * + * This function requires the Right and Left types to implement operator==(). + * + * @sa operator==(). + */ + +}