From d3c0d87faff3dad4320f8b931e7cd98c7d355a72 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 | 134 +++++++++++++++ lib/core/src/qx-bimap.dox | 245 ++++++++++++++++++++++++++++ 3 files changed, 381 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..e3371b30 --- /dev/null +++ b/lib/core/include/qx/core/qx-bimap.h @@ -0,0 +1,134 @@ +#ifndef QX_BIMAP_H +#define QX_BIMAP_H + +// Qt Includes +#include + +namespace Qx +{ + +template +class Bimap +{ +//-Instance Variables------------------------------------------------------------------------------------------- +private: + QHash mL2R; + QHash mR2L; + +//-Constructor-------------------------------------------------------------------------------------------------- +public: + Bimap() {} + + Bimap(std::initializer_list> list) + { + reserve(list.size()); + for(auto it = list.cbegin(); it != list.cend(); ++it) + insert(it->first, it->second); + } + +//-Class Functions------------------------------------------------------------------------------------------- +private: + template + void removeCrossReference(AMap& am, BMap& bm, const V& v) + { + Q_ASSERT(&am != &bm); + if(am.conains(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) { 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) { return mL2R.contains(l); } + bool containsRight(const Right& r) { return mR2L.contains(r); } + + Right fromLeft(const Left& l) + { + return mL2R.contains(l) ? *mL2R[l] : Right(); + } + + Right fromLeft(const Left& l, const Right& defaultValue) + { + return mL2R.contains(l) ? *mL2R[l] : defaultValue; + } + + Left fromRight(const Right& l) + { + return mR2L.contains(l) ? *mR2L[l] : Left(); + } + + Left fromRight(const Right& l, const Left& defaultValue) + { + return mR2L.contains(l) ? *mR2L[l] : 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(); } + + 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..ae4d95bc --- /dev/null +++ b/lib/core/src/qx-bimap.dox @@ -0,0 +1,245 @@ +namespace Qx +{ +//=============================================================================================================== +// 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. + */ + +//-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. + */ + +//-Class 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) + * + * 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) + * + * 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) + * + * 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) + * + * @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) + * + * 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) + * + * @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 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() + * + * 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() + * + * 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(). + */ + +/*! + * @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==(). + */ + +}