From 06e77678571ad37ed7632656df4fab5a5a29b0ab Mon Sep 17 00:00:00 2001 From: Timur Ammaev Date: Tue, 6 Feb 2024 17:23:55 +0300 Subject: [PATCH] Added SortBy method --- Docs/Doxyfile | 5 ++-- Examples/CMakeLists.txt | 7 +++-- Examples/SortExample.cpp | 35 +++++++++++++++++++++++++ Source/ReadWriteMutex.h | 39 +++------------------------- Source/ToString.h | 4 +-- Source/Vault.cpp | 26 ++++++++++++++++++- Source/Vault.h | 54 +++++++++++++++++++++++++++++++-------- Source/VaultRecordRef.cpp | 8 +++--- Source/VaultRecordRef.h | 30 ++++++++++++---------- 9 files changed, 138 insertions(+), 70 deletions(-) create mode 100644 Examples/SortExample.cpp diff --git a/Docs/Doxyfile b/Docs/Doxyfile index 3a63670..3d2436f 100644 --- a/Docs/Doxyfile +++ b/Docs/Doxyfile @@ -42,7 +42,7 @@ DOXYFILE_ENCODING = UTF-8 # title of most generated pages and in a few other places. # The default value is: My Project. -PROJECT_NAME = "MVault" +PROJECT_NAME = MVault # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version @@ -950,7 +950,8 @@ WARN_LOGFILE = # Note: If this tag is empty the current directory is searched. INPUT = ../Source \ - . + . \ + ../Examples/SortExample.cpp # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses diff --git a/Examples/CMakeLists.txt b/Examples/CMakeLists.txt index c5c6b65..05d31ac 100644 --- a/Examples/CMakeLists.txt +++ b/Examples/CMakeLists.txt @@ -8,9 +8,12 @@ endif() set(CMAKE_CXX_STANDARD 11) -project(Example1) +project(Examples) include_directories(../Source) add_executable(Example1 Example1.cpp) -target_link_libraries(Example1 MVault) \ No newline at end of file +target_link_libraries(Example1 MVault) + +add_executable(SortExample SortExample.cpp) +target_link_libraries(SortExample MVault) \ No newline at end of file diff --git a/Examples/SortExample.cpp b/Examples/SortExample.cpp new file mode 100644 index 0000000..4c77885 --- /dev/null +++ b/Examples/SortExample.cpp @@ -0,0 +1,35 @@ +#include "../Source/Vault.h" + +int main() +{ + mvlt::Vault vlt; + + vlt.SetKey("id", -1); + vlt.SetKey("rid", -1); + + for (int i = 0; i < 20; ++i) + { + vlt.CreateRecord({ {"id", i}, {"rid", 20 - i} }); + } + + vlt.PrintAsTable(); + + std::cout << "The first 5 entries after sorting in ascending order by id" << std::endl; + std::vector sorted = vlt.GetSortedRecords("id", false, 5); + for (const auto& it : sorted) + { + it.ReadLock(); + it.PrintRecord(); + it.ReadUnlock(); + } + + std::cout << "The first 5 entries after sorting in descending order by id" << std::endl; + sorted = vlt.GetSortedRecords("id", true, 5); + for (const auto& it : sorted) + it.PrintRecord(); + + std::cout << "The first 5 entries after sorting in ascending order by rid" << std::endl; + sorted = vlt.GetSortedRecords("rid", false, 5); + for (const auto& it : sorted) + it.PrintRecord(); +} \ No newline at end of file diff --git a/Source/ReadWriteMutex.h b/Source/ReadWriteMutex.h index 21cfb68..6f80209 100644 --- a/Source/ReadWriteMutex.h +++ b/Source/ReadWriteMutex.h @@ -57,41 +57,6 @@ class ReadWriteMutex The read lock will ensure that no thread using the write lock gets into the code section until all threads using the read lock are unblocked. At the same time, after the write lock, no new threads with a read lock will enter the code section until all threads using the write lock are unblocked. Recursiveness allows you to call blocking methods in the same thread multiple times without self-locking. - At the same time, it is important to follow the blocking procedure. 3 situations are allowed: - 1. ReadLock -> ReadLock -> ReadUnlock -> ReadUnlock - \code - RecursiveReadWriteMutex rrwx; - rrwx.ReadLock(); - rrwx.ReadLock(); - rrwx.ReadUnlock(); - rrwx.ReadUnlock(); - \endcode - 2. WriteLock -> WriteLock -> WriteUnlock -> WriteUnlock - \code - RecursiveReadWriteMutex rrwx; - rrwx.WriteLock(); - rrwx.WriteLock(); - rrwx.WriteUnlock(); - rrwx.WriteUnlock(); - \endcode - 3. WriteLock -> ReadLock -> ReadUnlock -> WriteUnlock - \code - RecursiveReadWriteMutex rrwx; - rrwx.WriteLock(); - rrwx.ReadLock(); - rrwx.ReadUnlock(); - rrwx.WriteUnlock(); - \endcode - - Situation: ReadLock -> WriteLock -> WriteUnlock -> ReadUnlock - \code - RecursiveReadWriteMutex rrwx; - rrwx.ReadLock(); - rrwx.WriteLock(); - rrwx.WriteUnlock(); - rrwx.ReadUnlock(); - \endcode - It is prohibited because it violates the logic of read and write operations and leads to deadlocking */ class RecursiveReadWriteMutex { @@ -104,6 +69,8 @@ class RecursiveReadWriteMutex Using this method, you can lock the code section for reading, which means that all threads using the read lock will have access to data inside the code section but threads using the write lock will wait until all read operations are completed. + Note that, in fact, blocking for reading inside writing does not make sense, + since the code section is already locked and therefore nothing will happen inside the function in such a situation. */ void ReadLock(); @@ -116,9 +83,11 @@ class RecursiveReadWriteMutex This method provides exclusive access to a section of code for a single thread. All write operations will be performed sequentially. This method takes precedence over the read lock, which means that after calling this method, no new read operations will be started. + Note that if the write lock is called inside the read lock, then this will be equivalent to unlocking for reading and then locking for writing. */ void WriteLock(); /// \brief A method for unlocking a section of code for writing + /// Note that if the write unlock is called inside the read lock, then this will be equivalent to unlocking for writing and then locking for reading. void WriteUnlock(); }; \ No newline at end of file diff --git a/Source/ToString.h b/Source/ToString.h index 861f451..f02431b 100644 --- a/Source/ToString.h +++ b/Source/ToString.h @@ -18,7 +18,7 @@ namespace mvlt For example, like this - \code + \code{.cpp} template <> std::string mvlt::ToString(const std::vector& data) { @@ -41,7 +41,7 @@ namespace mvlt By calling this function: - \code + \code{.cpp} std::vector vec = {1, 2, 3, 4, 5}; std::cout << ToString(vec) << std::endl; \endcode diff --git a/Source/Vault.cpp b/Source/Vault.cpp index 40c61e8..dc381ac 100644 --- a/Source/Vault.cpp +++ b/Source/Vault.cpp @@ -35,6 +35,7 @@ namespace mvlt VaultRecordAdders.erase(keyName); VaultRecordClearers.erase(keyName); VaultRecordErasers.erase(keyName); + VaultRecordSorters.erase(keyName); // Erase key data from all records for (auto& it : RecordsSet) @@ -115,6 +116,7 @@ namespace mvlt VaultRecordAdders.clear(); VaultRecordClearers.clear(); VaultRecordErasers.clear(); + VaultRecordSorters.clear(); // Delete all Records for (auto& it : RecordsSet) @@ -186,6 +188,28 @@ namespace mvlt return res; } + std::vector Vault::GetSortedRecords(const std::string& keyName, const bool& isReverse, const std::size_t& amountOfRecords) const + { + std::vector res; + std::size_t counter = 0; + + RecursiveReadWriteMtx.ReadLock(); + + /// \todo Проверка + VaultRecordSorters.find(keyName)->second([&](const VaultRecordRef& vaultRecordRef) + { + if (counter >= amountOfRecords) return false; + + res.emplace_back(vaultRecordRef); + ++counter; + return true; + }, isReverse); + + RecursiveReadWriteMtx.ReadUnlock(); + + return res; + } + void Vault::PrintVault(const std::size_t amountOfRecords) const { RecursiveReadWriteMtx.ReadLock(); @@ -195,7 +219,7 @@ namespace mvlt for (const auto& record : RecordsSet) { - std::cout << "Data storage record " << record << ":" << std::endl; + std::cout << "Vault record " << record << ":" << std::endl; for (const std::string& key : keys) { diff --git a/Source/Vault.h b/Source/Vault.h index 3c32595..83dab9d 100644 --- a/Source/Vault.h +++ b/Source/Vault.h @@ -55,15 +55,23 @@ namespace mvlt */ mutable VaultStructureMap VaultMapStructure; - // unordered_map of functions that add a new element to the VaultStructureHashMap + // Unordered_map of functions that add a new element to the VaultStructureHashMap std::unordered_map> VaultRecordAdders; - // unordered_map of functions that delete all data from the unordered_map's stored in the VaultRecordAdders + // Unordered_map of functions that delete all data from the unordered_map's std::unordered_map> VaultRecordClearers; - // unordered_map of functions that erase record from the unordered_map's stored in the VaultRecordAdders + // Unordered_map of functions that erase record from the unordered_map's std::unordered_map> VaultRecordErasers; + // Unordered_map of functions for getting sorted data. + // The key is a string with the name of the key from the Vault. + // The std::function is used as the value, in which the lambda function is written at the time of adding the key. + // Lambda accepts a function that is called for each entry inside. VaultRecordRef is passed to her. + // By default, iteration by records occurs in ascending order. + // isReverse parameter is used for the reverse order. + std::unordered_map functionToSortedData, bool isReverse )>> VaultRecordSorters; + // Unordered set with all VaultRecord pointers std::unordered_set RecordsSet; @@ -173,6 +181,22 @@ namespace mvlt } ); + VaultRecordSorters.emplace(keyName, [=](std::function functionToSortedData, bool isReverse) + { + if (!isReverse) + { + for (const auto& it : *TtoVaultRecordMap) + if(!functionToSortedData(VaultRecordRef(it.second, &VaultHashMapStructure, &VaultMapStructure, &RecursiveReadWriteMtx))) + break; + } + else + { + for (auto it = TtoVaultRecordMap->rbegin(); it != TtoVaultRecordMap->rend(); ++it) + if(!functionToSortedData(VaultRecordRef(it->second, &VaultHashMapStructure, &VaultMapStructure, &RecursiveReadWriteMtx))) + break; + } + }); + // Add new data to record set for (auto& it : RecordsSet) { @@ -199,7 +223,7 @@ namespace mvlt \tparam Any type of data except for c arrays \param [in] keyName the name of the key to search for - \param [in] keyValue the value of the key + \param [in] defaultKeyValue the value of the key \return returns true if the key was found otherwise returns false */ @@ -232,28 +256,28 @@ namespace mvlt The Vault in the example has 2 keys. One is the id of the int type, and the second is the name of the std::string type Usage example: - \code + \code{.cpp} // A record with id 0 and name mrognor will be created vlt.CreateRecord({ {"id", 0}, {"name", std::string("mrognor")} }); \endcode or - \code + \code{.cpp} // A record with id 0 and name mrognor will be created vlt.CreateRecord({ {"name", std::string("mrognor")}, {"id", 0} }); \endcode or - \code + \code{.cpp} // A record with name mrognor will be created. The Id will be set to the default value vlt.CreateRecord({ {"name", std::string("mrognor")} }); \endcode or - \code + \code{.cpp} // A record with id 0 and name mrognor will be created std::vector> params = { {"id", 0}, {"name", std::string("mrognor")} }; VaultRecordRef vltrr = vlt.CreateRecord(params); @@ -261,7 +285,7 @@ namespace mvlt what is equivalent to such a code without passing values to a function - \code + \code{.cpp} VaultRecordRef vltrr = vlt.CreateRecord(); vltrr.SetData("id", 0); @@ -281,7 +305,6 @@ namespace mvlt \param [in] keyName the name of the key to search for \param [in] keyValue the value of the key to be found - \param [out] foundedRecord a ref to the VaultRecordRef where found record will be placed \return ref to requested record */ @@ -329,6 +352,17 @@ namespace mvlt /// \return vector with keys std::vector GetKeys() const; + /** + \brief Method for getting sorted records + + \param[in] keyName The key by which the data should be sorted + \param[in] isReverse Sort in descending order or descending order. By default, ascending + \param[in] amountOfRecords The number of records. By default, everything is + + \return A vector with links to records. The order of entries in the vector is determined by the amountOfRecords parameter + */ + std::vector GetSortedRecords(const std::string& keyName, const bool& isReverse = false, const std::size_t& amountOfRecords = -1) const; + /// \brief A method for displaying the contents of a Vault on the screen void PrintVault(const std::size_t amountOfRecords = 0) const; diff --git a/Source/VaultRecordRef.cpp b/Source/VaultRecordRef.cpp index fdc4e20..3cb76a7 100644 --- a/Source/VaultRecordRef.cpp +++ b/Source/VaultRecordRef.cpp @@ -101,14 +101,14 @@ namespace mvlt VaultMapStructure = nullptr; } - void VaultRecordRef::WriteLock() + void VaultRecordRef::ReadLock() const { - VaultRecucrsiveReadWriteMtx->WriteLock(); + VaultRecucrsiveReadWriteMtx->ReadLock(); } - void VaultRecordRef::WriteUnlock() + void VaultRecordRef::ReadUnlock() const { - VaultRecucrsiveReadWriteMtx->WriteUnlock(); + VaultRecucrsiveReadWriteMtx->ReadUnlock(); } VaultRecordRef::~VaultRecordRef() diff --git a/Source/VaultRecordRef.h b/Source/VaultRecordRef.h index 973964c..35c543a 100644 --- a/Source/VaultRecordRef.h +++ b/Source/VaultRecordRef.h @@ -55,6 +55,8 @@ namespace mvlt /// \param [in] other other VaultRecordRef object VaultRecordRef(const VaultRecordRef& other); + /// \brief Assignment operator + /// \param [in] other other VaultRecordRef object VaultRecordRef& operator=(const VaultRecordRef& other); /// \brief Comparison operator @@ -202,33 +204,33 @@ namespace mvlt void Unlink(); /** - \brief A method for locking record for writing + \brief A method for locking record Usage example: - \code - Vault ds; - ds.SetKey("Id", 0); - ds.SetKey>("Slaves", std::vector()); + \code{.cpp} + Vault vlt; + vlt.SetKey("Id", 0); + vlt.SetKey>("Friends", std::vector()); ... - std::vector slaves; - VaultRecordRef dsrr = DS.GetRecord("Slaves", slaves); + std::vector friends; + VaultRecordRef vltrr = vlt.GetRecord("Friends", friends); // Lock so that another thread cannot change the vector - dsrr.WriteLock(); + vltrr.ReadLock(); - for (const int& i : slaves) - std::cout << slaves << std::endl; + for (const int& friend : friends) + std::cout << friend << std::endl; - dsrr.WriteUnlock(); + vltrr.ReadUnlock(); \endcode */ - void WriteLock(); + void ReadLock() const; - /// \brief A method for unlocking record for writing - void WriteUnlock(); + /// \brief A method for unlocking record + void ReadUnlock() const; /// \brief Default destructor ~VaultRecordRef();