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