diff --git a/cpp/modmesh/toggle/CMakeLists.txt b/cpp/modmesh/toggle/CMakeLists.txt index 403b08c9..dbf127da 100644 --- a/cpp/modmesh/toggle/CMakeLists.txt +++ b/cpp/modmesh/toggle/CMakeLists.txt @@ -5,6 +5,7 @@ cmake_minimum_required(VERSION 3.16) set(MODMESH_TOGGLE_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/profile.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/RadixTree.hpp ${CMAKE_CURRENT_SOURCE_DIR}/toggle.hpp CACHE FILEPATH "" FORCE) diff --git a/cpp/modmesh/toggle/RadixTree.hpp b/cpp/modmesh/toggle/RadixTree.hpp new file mode 100644 index 00000000..aca29db0 --- /dev/null +++ b/cpp/modmesh/toggle/RadixTree.hpp @@ -0,0 +1,214 @@ +#pragma once + +/* + * Copyright (c) 2023, Quentin Tsai + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace modmesh +{ +/** + * Simple Timed Entry + */ +class TimedEntry +{ +public: + size_t count() const { return m_count; } + double time() const { return m_time; } + + TimedEntry & add_time(double time) + { + ++m_count; + m_time += time; + return *this; + } + + friend std::ostream & operator<<(std::ostream & os, const TimedEntry & entry) + { + os << "Count: " << entry.count() << " - Time: " << entry.time(); + return os; + } + +private: + size_t m_count = 0; + double m_time = 0.0; +}; /* end class TimedEntry */ + +template +class RadixTreeNode +{ +private: + using ChildList = std::list>; + +public: + RadixTreeNode() + : prev(nullptr){}; + RadixTreeNode(std::string name, int key) + : m_name(std::move(name)) + , m_key(key) + , prev(nullptr) + { + } + + const int & getKey() const { return m_key; } + const std::string & getName() const { return m_name; } + T & getData() { return m_data; } + const T & getData() const { return m_data; } + const ChildList & getChildren() const { return m_children; } + + // Add a child node + RadixTreeNode * addChild(std::string childName, int childKey) + { + auto newChild = std::make_unique>(std::move(childName), std::move(childKey)); + newChild->prev = this; + m_children.push_back(std::move(newChild)); + return m_children.back().get(); + } + + // Get a child node with a given key + RadixTreeNode * getChild(int key) const + { + auto it = std::find_if(m_children.begin(), m_children.end(), [&](const auto & child) + { return child->getKey() == key; }); + return (it != m_children.end()) ? it->get() : nullptr; + } + + // Get prev node + RadixTreeNode * getPrev() const + { + return prev; + } + + // // print node information + std::string Info() const + { + std::ostringstream oss; + if (nullptr != prev) + { + oss << getName() << " - "; + oss << getData(); + } + return oss.str(); + } + +private: + int m_key = -1; + std::string m_name; + T m_data; + ChildList m_children; + RadixTreeNode * prev; +}; /* end class RadixTreeNode */ + +/* +Ref: +https://kalkicode.com/radix-tree-implementation +https://www.algotree.org/algorithms/trie/ +*/ +template +class RadixTree +{ +public: + RadixTree() + : m_root(std::make_unique>()) + , m_currentNode(m_root.get()) + { + } + + T & entry(const std::string & name) + { + int id = getId(name); + + RadixTreeNode * child = m_currentNode->getChild(id); + + if (!child) + { + m_currentNode = m_currentNode->addChild(name, id); + } + else + { + m_currentNode = child; + } + + return m_currentNode->getData(); + } + + void moveCurrentToParent() + { + if (m_currentNode != m_root.get()) + { + m_currentNode = m_currentNode->getPrev(); + } + } + + const RadixTreeNode * getCurrentNode() const { return m_currentNode; } + const int getUniqueNode() const + { + return m_unique_id; + } + + void print() const + { + printRecursive(m_root.get(), 0); + } + +private: + void printRecursive(const RadixTreeNode * node, int depth) const + { + for (int i = 0; i < depth; ++i) + { + std::cout << " "; + } + + std::cout << node->Info(); + + for (const auto & child : node->getChildren()) + { + printRecursive(child.get(), depth + 1); + } + } + + int getId(const std::string & name) + { + auto [it, inserted] = m_id_map.try_emplace(name, m_unique_id++); + return it->second; + } + + std::unique_ptr> m_root; + RadixTreeNode * m_currentNode; + std::unordered_map m_id_map; + int m_unique_id = 0; +}; /* end class RadixTree */ + +} /* end namespace modmesh */ +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/gtests/CMakeLists.txt b/gtests/CMakeLists.txt index 29b1db7b..9a5f011c 100644 --- a/gtests/CMakeLists.txt +++ b/gtests/CMakeLists.txt @@ -22,6 +22,7 @@ add_executable( test_nopython_buffer.cpp test_nopython_modmesh.cpp test_nopython_inout.cpp + test_nonpython_radixtree.cpp ) target_link_libraries( test_nopython diff --git a/gtests/test_nonpython_radixtree.cpp b/gtests/test_nonpython_radixtree.cpp new file mode 100644 index 00000000..dce840c9 --- /dev/null +++ b/gtests/test_nonpython_radixtree.cpp @@ -0,0 +1,67 @@ +#include +#include + +#ifdef Py_PYTHON_H +#error "Python.h should not be included." +#endif + +TEST(RadixTree, construction) +{ + using namespace modmesh; + RadixTree radixTree; + EXPECT_EQ(radixTree.getUniqueNode(), 0); +} + +TEST(RadixTree, single_insertion) +{ + using namespace modmesh; + RadixTree radixTree; + TimedEntry & entry1 = radixTree.entry("a"); + entry1.add_time(5.2); + + EXPECT_EQ(entry1.count(), 1); + EXPECT_DOUBLE_EQ(entry1.time(), 5.2); + EXPECT_EQ(radixTree.getUniqueNode(), 1); + + const RadixTreeNode * const node = radixTree.getCurrentNode(); + EXPECT_EQ(node->getName(), "a"); + EXPECT_EQ(node->Info(), "a - Count: 1 - Time: 5.2"); +} + +TEST(RadixTree, multiple_insertion) +{ + using namespace modmesh; + RadixTree radixTree; + TimedEntry & entry1 = radixTree.entry("a"); + entry1.add_time(5.2); + const RadixTreeNode * const node1 = radixTree.getCurrentNode(); + + TimedEntry & entry2 = radixTree.entry("b"); + entry2.add_time(10.1); + + EXPECT_EQ(radixTree.getUniqueNode(), 2); + + const RadixTreeNode * const node2 = radixTree.getCurrentNode(); + EXPECT_EQ(node2->getPrev(), node1); +} + +TEST(RadixTree, move_current_pointer) +{ + using namespace modmesh; + RadixTree radixTree; + TimedEntry & entry1 = radixTree.entry("a"); + entry1.add_time(5.2); + const RadixTreeNode * const node1 = radixTree.getCurrentNode(); + + TimedEntry & entry2 = radixTree.entry("b"); + entry2.add_time(10.1); + + radixTree.moveCurrentToParent(); + const RadixTreeNode * const node2 = radixTree.getCurrentNode(); + + EXPECT_EQ(radixTree.getUniqueNode(), 2); + EXPECT_EQ(node2->getName(), "a"); + EXPECT_EQ(node2, node1); +} + +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: