From 7959d54064ae73e854fe5cf7c004abecfdafe6c8 Mon Sep 17 00:00:00 2001 From: phesch <ulila@student.kit.edu> Date: Tue, 10 May 2022 22:38:26 +0200 Subject: [PATCH] Add prefix methods for maps --- .../RobotAPI/libraries/armem/CMakeLists.txt | 1 + .../RobotAPI/libraries/armem/core/prefixes.h | 203 ++++++++++++++++++ .../armem/test/ArMemPrefixesTest.cpp | 93 ++++++++ .../libraries/armem/test/CMakeLists.txt | 1 + 4 files changed, 298 insertions(+) create mode 100644 source/RobotAPI/libraries/armem/core/prefixes.h create mode 100644 source/RobotAPI/libraries/armem/test/ArMemPrefixesTest.cpp diff --git a/source/RobotAPI/libraries/armem/CMakeLists.txt b/source/RobotAPI/libraries/armem/CMakeLists.txt index 237d90fa5..8183a5501 100644 --- a/source/RobotAPI/libraries/armem/CMakeLists.txt +++ b/source/RobotAPI/libraries/armem/CMakeLists.txt @@ -86,6 +86,7 @@ set(LIB_HEADERS core/MemoryID.h core/MemoryID_operators.h core/operations.h + core/prefixes.h core/SuccessHeader.h core/Time.h core/aron_conversions.h diff --git a/source/RobotAPI/libraries/armem/core/prefixes.h b/source/RobotAPI/libraries/armem/core/prefixes.h new file mode 100644 index 000000000..0435c42de --- /dev/null +++ b/source/RobotAPI/libraries/armem/core/prefixes.h @@ -0,0 +1,203 @@ +/* + * This file is part of ArmarX. + * + * ArmarX is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ArmarX is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @author phesch ( ulila at student dot kit dot edu ) + * @date 2022 + * @copyright http://www.gnu.org/licenses/gpl-2.0.txt + * GNU General Public License + */ + +#pragma once + +#include <map> +#include <optional> +#include <tuple> + +#include <RobotAPI/libraries/armem/core/MemoryID.h> + +namespace armarx::armem +{ + /** + * @brief Get the key-value pair from the map for which the returned key + * is the longest prefix of the given key among the keys in the map. + * + * `prefixFunc` is used to successively calculate the prefixes of the given key. + * It must be pure and return an empty optional when there is no shorter + * prefix of the given key (for strings, this would be the case when passed the empty string). + * + * @param keyValMap the map that contains the key-value-pairs to search + * @param prefixFunc the function that returns the longest non-identical prefix of the key + * @param key the key to calculate the prefixes of + */ + template <typename KeyT, typename ValueT> + std::optional<std::pair<KeyT, ValueT>> + getWithLongestPrefix(const std::map<KeyT, ValueT>& keyValMap, + const std::function<std::optional<KeyT>(KeyT&)>& prefixFunc, + const KeyT& key) + { + std::optional<KeyT> curKey = key; + std::optional<ValueT> value; + do + { + auto iterator = keyValMap.find(curKey.value()); + if (iterator != keyValMap.end()) + { + value = iterator->second; + } + else + { + curKey = prefixFunc(curKey.value()); + } + } while (!value.has_value() && curKey.has_value()); + + if (value.has_value()) + { + return {{curKey.value(), value.value()}}; + } + return {}; + } + + /** + * @brief Accumulate all the values in a map for which the keys are prefixes of the given key. + * + * `AccumulateT` is a type that the values will be accumulated into using `accumulateFunc`. + * `accumulateFunc` is a function that modifies the given accumulator + * (by, e.g., adding the given value to it). + * + * The values are accumulated in order from the longest key to the shortest. + * + * @see `getWithLongestPrefix` for a description of `prefixFunc` + * @param keyValMap the map that contains the key-value-pairs to search + * @param prefixFunc the function that returns the longest non-identical prefix of the key + * @param accumulateFunc the function that accumulates the values in the accumulator + * @param key the key to calculate the prefixes of + */ + template <typename KeyT, typename ValueT, typename AccumulateT> + AccumulateT + accumulateFromPrefixes(const std::map<KeyT, ValueT>& keyValMap, + const std::function<std::optional<KeyT>(KeyT&)>& prefixFunc, + const std::function<void(AccumulateT&, ValueT&)> accumulateFunc, + const KeyT& key) + { + std::optional<KeyT> curKey = key; + AccumulateT values; + do + { + std::optional<std::pair<KeyT, ValueT>> nextPair = + getWithLongestPrefix<KeyT, ValueT>(keyValMap, prefixFunc, curKey.value()); + if (nextPair.has_value()) + { + curKey = prefixFunc(nextPair.value().first); + accumulateFunc(values, nextPair.value().second); + } + else + { + curKey.reset(); + } + } while (curKey.has_value()); + + return values; + } + + /** + * @brief Collect all the values in a map for which the keys are prefixes of the given key. + * + * This is a specialization of the general `accumulateFromPrefixes` + * for collecting single values into a vector. + * + * @see `accumulateFromPrefixes` for a detailed description + * @param keyValMap the map that contains the key-value-pairs to search + * @param prefixFunc the function that returns the longest non-identical prefix of the key + * @param key the key to calculate the prefixes of + */ + template <typename KeyT, typename ValueT> + std::vector<ValueT> + accumulateFromPrefixes(const std::map<KeyT, ValueT>& keyValMap, + const std::function<std::optional<KeyT>(KeyT&)>& prefixFunc, + const KeyT& key) + { + return accumulateFromPrefixes<KeyT, ValueT, std::vector<ValueT>>( + keyValMap, + prefixFunc, + [](std::vector<ValueT>& values, ValueT& val) { values.push_back(val); }, + key); + } + + /** + * @brief Collect all the values in a map for which the keys are prefixes of the given key. + * + * This is a specialization of the general `accumulateFromPrefixes` + * for appending vector values into a single vector. + * + * @see `accumulateFromPrefixes` for a detailed description + * @param keyValMap the map that contains the key-value-pairs to search + * @param prefixFunc the function that returns the longest non-identical prefix of the key + * @param key the key to calculate the prefixes of + */ + template <typename KeyT, typename ValueT> + std::vector<ValueT> + accumulateFromPrefixes(const std::map<KeyT, std::vector<ValueT>>& keyValMap, + const std::function<std::optional<KeyT>(KeyT&)>& prefixFunc, + const KeyT& key) + { + return accumulateFromPrefixes<KeyT, std::vector<ValueT>, std::vector<ValueT>>( + keyValMap, + prefixFunc, + [](std::vector<ValueT>& values, std::vector<ValueT>& val) + { values.insert(values.end(), val.begin(), val.end()); }, + key); + } + + std::optional<MemoryID> inline getMemoryIDParent(MemoryID& memID) + { + if (!memID.hasMemoryName()) + { + return {}; + } + MemoryID parent = memID.removeLeafItem(); + return {parent}; + } + + /** + * @see `getWithLongestPrefix` + */ + template <typename ValueT> + std::optional<std::pair<MemoryID, ValueT>> + getWithLongestPrefix(const std::map<MemoryID, ValueT>& idMap, const MemoryID& key) + { + return getWithLongestPrefix<MemoryID, ValueT>(idMap, &getMemoryIDParent, key); + } + + /** + * @see `accumulateFromPrefixes` + */ + template <typename ValueT> + std::vector<ValueT> + accumulateFromPrefixes(const std::map<MemoryID, ValueT>& idMap, const MemoryID& key) + { + return accumulateFromPrefixes<MemoryID, ValueT>(idMap, &getMemoryIDParent, key); + } + + /** + * @see `accumulateFromPrefixes` + */ + template <typename ValueT> + std::vector<ValueT> + accumulateFromPrefixes(const std::map<MemoryID, std::vector<ValueT>>& idMap, + const MemoryID& key) + { + return accumulateFromPrefixes<MemoryID, ValueT>(idMap, &getMemoryIDParent, key); + } +} // namespace armarx::armem diff --git a/source/RobotAPI/libraries/armem/test/ArMemPrefixesTest.cpp b/source/RobotAPI/libraries/armem/test/ArMemPrefixesTest.cpp new file mode 100644 index 000000000..877b83daa --- /dev/null +++ b/source/RobotAPI/libraries/armem/test/ArMemPrefixesTest.cpp @@ -0,0 +1,93 @@ +/* + * This file is part of ArmarX. + * + * ArmarX is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ArmarX is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @package RobotAPI::armem + * @author phesch ( ulila at student dot kit dot edu ) + * @date 2022 + * @copyright http://www.gnu.org/licenses/gpl-2.0.txt + * GNU General Public License + */ + +#define BOOST_TEST_MODULE RobotAPI::armem + +#define ARMARX_BOOST_TEST + +#include <iostream> + +#include <RobotAPI/Test.h> +#include <RobotAPI/libraries/armem/core/error.h> +#include <RobotAPI/libraries/armem/core/prefixes.h> + + +namespace armem = armarx::armem; + +BOOST_AUTO_TEST_CASE(test_MemoryID_prefixes) +{ + std::map<armem::MemoryID, int> idMap; + std::map<armem::MemoryID, std::vector<int>> idListMap; + armem::MemoryID empty; + armem::MemoryID complete("mem", "coreSeg", "provSeg", "entity", armarx::DateTime::Now(), 0); + + BOOST_TEST_CONTEXT(VAROUT(idMap)) + { + BOOST_CHECK(not armem::getWithLongestPrefix(idMap, empty).has_value()); + BOOST_CHECK(not armem::getWithLongestPrefix(idMap, complete).has_value()); + BOOST_CHECK(armem::accumulateFromPrefixes(idMap, empty).empty()); + BOOST_CHECK(armem::accumulateFromPrefixes(idMap, complete).empty()); + + + BOOST_CHECK(armem::accumulateFromPrefixes(idListMap, empty).empty()); + BOOST_CHECK(armem::accumulateFromPrefixes(idListMap, complete).empty()); + } + + idMap[armem::MemoryID()] = 0; + idMap.emplace("mem", 1); + idMap.emplace("mem/coreSeg", 2); + idMap.emplace("mem/coreSeg/provSeg", 3); + idMap.emplace("mem/otherSeg/provSeg", 10); + + BOOST_TEST_CONTEXT(VAROUT(idMap)) + { + BOOST_CHECK(armem::getWithLongestPrefix(idMap, empty) + .value_or(std::make_pair(armem::MemoryID("inv"), -1)) + .second == 0); + BOOST_CHECK(armem::getWithLongestPrefix(idMap, complete.getCoreSegmentID()) + .value_or(std::make_pair(armem::MemoryID("inv"), -1)) + .second == 2); + BOOST_CHECK(armem::getWithLongestPrefix(idMap, complete) + .value_or(std::make_pair(armem::MemoryID("inv"), -1)) + .second == 3); + + BOOST_CHECK((armem::accumulateFromPrefixes(idMap, empty) == std::vector<int>{0})); + BOOST_CHECK((armem::accumulateFromPrefixes(idMap, complete.getCoreSegmentID()) == + std::vector<int>{2, 1, 0})); + BOOST_CHECK( + (armem::accumulateFromPrefixes(idMap, complete) == std::vector<int>{3, 2, 1, 0})); + } + + idListMap.emplace("mem", std::vector<int>{1, 2}); + idListMap.emplace("mem/coreSeg", std::vector<int>{3, 4, 5}); + idListMap.emplace("mem/coreSeg/provSeg", std::vector<int>{6, 7, 8}); + idListMap.emplace("mem/otherSeg/provSeg", std::vector<int>{9, 10, 11}); + + BOOST_TEST_CONTEXT(VAROUT(idListMap)) + { + BOOST_CHECK((armem::accumulateFromPrefixes(idListMap, empty).empty())); + BOOST_CHECK((armem::accumulateFromPrefixes(idListMap, complete.getCoreSegmentID()) == + std::vector<int>{3, 4, 5, 1, 2})); + BOOST_CHECK((armem::accumulateFromPrefixes(idListMap, complete) == + std::vector<int>{6, 7, 8, 3, 4, 5, 1, 2})); + } +} diff --git a/source/RobotAPI/libraries/armem/test/CMakeLists.txt b/source/RobotAPI/libraries/armem/test/CMakeLists.txt index 592b9aef3..da77a43b8 100644 --- a/source/RobotAPI/libraries/armem/test/CMakeLists.txt +++ b/source/RobotAPI/libraries/armem/test/CMakeLists.txt @@ -7,3 +7,4 @@ armarx_add_test(ArMemGetFindTest ArMemGetFindTest.cpp "${LIBS}") armarx_add_test(ArMemIceConversionsTest ArMemIceConversionsTest.cpp "${LIBS}") armarx_add_test(ArMemMemoryIDTest ArMemMemoryIDTest.cpp "${LIBS}") armarx_add_test(ArMemQueryBuilderTest ArMemQueryBuilderTest.cpp "${LIBS}") +armarx_add_test(ArMemPrefixesTest ArMemPrefixesTest.cpp "${LIBS}") -- GitLab