[llvm] 5fda2a5 - [NFC][ADT] Add RadixTree (#164524)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Oct 24 17:41:54 PDT 2025
Author: Vitaly Buka
Date: 2025-10-25T00:41:50Z
New Revision: 5fda2a5d9c1a0f90da5d0afc412c9ad613702823
URL: https://github.com/llvm/llvm-project/commit/5fda2a5d9c1a0f90da5d0afc412c9ad613702823
DIFF: https://github.com/llvm/llvm-project/commit/5fda2a5d9c1a0f90da5d0afc412c9ad613702823.diff
LOG: [NFC][ADT] Add RadixTree (#164524)
This commit introduces a RadixTree implementation to LLVM.
RadixTree, as a Trie, is very efficient by searching for prefixes.
A Radix Tree is more efficient implementation of Trie.
The tree will be used to optimize Glob matching in SpecialCaseList:
* https://github.com/llvm/llvm-project/pull/164531
* https://github.com/llvm/llvm-project/pull/164543
* https://github.com/llvm/llvm-project/pull/164545
---------
Co-authored-by: Kazu Hirata <kazu at google.com>
Co-authored-by: Copilot <175728472+Copilot at users.noreply.github.com>
Added:
llvm/include/llvm/ADT/RadixTree.h
llvm/unittests/ADT/RadixTreeTest.cpp
Modified:
llvm/docs/ProgrammersManual.rst
llvm/unittests/ADT/CMakeLists.txt
Removed:
################################################################################
diff --git a/llvm/docs/ProgrammersManual.rst b/llvm/docs/ProgrammersManual.rst
index 9cdac9c59fa9b..d99b5843c2133 100644
--- a/llvm/docs/ProgrammersManual.rst
+++ b/llvm/docs/ProgrammersManual.rst
@@ -2161,6 +2161,16 @@ that are not simple pointers (use :ref:`SmallPtrSet <dss_smallptrset>` for
pointers). Note that ``DenseSet`` has the same requirements for the value type that
:ref:`DenseMap <dss_densemap>` has.
+.. _dss_radixtree:
+
+llvm/ADT/RadixTree.h
+^^^^^^^^^^^^^^^^^^^^
+
+``RadixTree`` is a trie-based data structure that stores range-like keys and
+their associated values. It is particularly efficient for storing keys that
+share common prefixes, as it can compress these prefixes to save memory. It
+supports efficient search of matching prefixes.
+
.. _dss_sparseset:
llvm/ADT/SparseSet.h
diff --git a/llvm/include/llvm/ADT/RadixTree.h b/llvm/include/llvm/ADT/RadixTree.h
new file mode 100644
index 0000000000000..d3c44e4e6345c
--- /dev/null
+++ b/llvm/include/llvm/ADT/RadixTree.h
@@ -0,0 +1,350 @@
+//===-- llvm/ADT/RadixTree.h - Radix Tree implementation --------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//===----------------------------------------------------------------------===//
+//
+// This file implements a Radix Tree.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_ADT_RADIXTREE_H
+#define LLVM_ADT_RADIXTREE_H
+
+#include "llvm/ADT/ADL.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/iterator.h"
+#include "llvm/ADT/iterator_range.h"
+#include <cassert>
+#include <cstddef>
+#include <iterator>
+#include <limits>
+#include <list>
+#include <utility>
+
+namespace llvm {
+
+/// \brief A Radix Tree implementation.
+///
+/// A Radix Tree (also known as a compact prefix tree or radix trie) is a
+/// data structure that stores a dynamic set or associative array where keys
+/// are strings and values are associated with these keys. Unlike a regular
+/// trie, the edges of a radix tree can be labeled with sequences of characters
+/// as well as single characters. This makes radix trees more efficient for
+/// storing sparse data sets, where many nodes in a regular trie would have
+/// only one child.
+///
+/// This implementation supports arbitrary key types that can be iterated over
+/// (e.g., `std::string`, `std::vector<char>`, `ArrayRef<char>`). The key type
+/// must provide `begin()` and `end()` for iteration.
+///
+/// The tree stores `std::pair<const KeyType, T>` as its value type.
+///
+/// Example usage:
+/// \code
+/// llvm::RadixTree<StringRef, int> Tree;
+/// Tree.emplace("apple", 1);
+/// Tree.emplace("grapefruit", 2);
+/// Tree.emplace("grape", 3);
+///
+/// // Find prefixes
+/// for (const auto &[Key, Value] : Tree.find_prefixes("grapefruit juice")) {
+/// // pair will be {"grape", 3}
+/// // pair will be {"grapefruit", 2}
+/// llvm::outs() << Key << ": " << Value << "\n";
+/// }
+///
+/// // Iterate over all elements
+/// for (const auto &[Key, Value] : Tree)
+/// llvm::outs() << Key << ": " << Value << "\n";
+/// \endcode
+///
+/// \note
+/// The `RadixTree` takes ownership of the `KeyType` and `T` objects
+/// inserted into it. When an element is removed or the tree is destroyed,
+/// these objects will be destructed.
+/// However, if `KeyType` is a reference-like type, e.g., StringRef or range,
+/// the user must guarantee that the referenced data has a lifetime longer than
+/// the tree.
+template <typename KeyType, typename T> class RadixTree {
+public:
+ using key_type = KeyType;
+ using mapped_type = T;
+ using value_type = std::pair<const KeyType, mapped_type>;
+
+private:
+ using KeyConstIteratorType =
+ decltype(adl_begin(std::declval<const key_type &>()));
+ using KeyConstIteratorRangeType = iterator_range<KeyConstIteratorType>;
+ using KeyValueType =
+ remove_cvref_t<decltype(*adl_begin(std::declval<key_type &>()))>;
+ using ContainerType = std::list<value_type>;
+
+ /// Represents an internal node in the Radix Tree.
+ struct Node {
+ KeyConstIteratorRangeType Key{KeyConstIteratorType{},
+ KeyConstIteratorType{}};
+ std::vector<Node> Children;
+
+ /// An iterator to the value associated with this node.
+ ///
+ /// If this node does not have a value (i.e., it's an internal node that
+ /// only serves as a path to other values), this iterator will be equal
+ /// to default constructed `ContainerType::iterator()`.
+ typename ContainerType::iterator Value;
+
+ /// The first character of the Key. Used for fast child lookup.
+ KeyValueType KeyFront;
+
+ Node() = default;
+ Node(const KeyConstIteratorRangeType &Key)
+ : Key(Key), KeyFront(*Key.begin()) {
+ assert(!Key.empty());
+ }
+
+ Node(Node &&) = default;
+ Node &operator=(Node &&) = default;
+
+ Node(const Node &) = delete;
+ Node &operator=(const Node &) = delete;
+
+ const Node *findChild(const KeyConstIteratorRangeType &Key) const {
+ if (Key.empty())
+ return nullptr;
+ for (const Node &Child : Children) {
+ assert(!Child.Key.empty()); // Only root can be empty.
+ if (Child.KeyFront == *Key.begin())
+ return &Child;
+ }
+ return nullptr;
+ }
+
+ Node *findChild(const KeyConstIteratorRangeType &Query) {
+ const Node *This = this;
+ return const_cast<Node *>(This->findChild(Query));
+ }
+
+ size_t countNodes() const {
+ size_t R = 1;
+ for (const Node &C : Children)
+ R += C.countNodes();
+ return R;
+ }
+
+ ///
+ /// Splits the current node into two.
+ ///
+ /// This function is used when a new key needs to be inserted that shares
+ /// a common prefix with the current node's key, but then diverges.
+ /// The current `Key` is truncated to the common prefix, and a new child
+ /// node is created for the remainder of the original node's `Key`.
+ ///
+ /// \param SplitPoint An iterator pointing to the character in the current
+ /// `Key` where the split should occur.
+ void split(KeyConstIteratorType SplitPoint) {
+ Node Child(make_range(SplitPoint, Key.end()));
+ Key = make_range(Key.begin(), SplitPoint);
+
+ Children.swap(Child.Children);
+ std::swap(Value, Child.Value);
+
+ Children.emplace_back(std::move(Child));
+ }
+ };
+
+ /// Root always corresponds to the empty key, which is the shortest possible
+ /// prefix for everything.
+ Node Root;
+ ContainerType KeyValuePairs;
+
+ /// Finds or creates a new tail or leaf node corresponding to the `Key`.
+ Node &findOrCreate(KeyConstIteratorRangeType Key) {
+ Node *Curr = &Root;
+ if (Key.empty())
+ return *Curr;
+
+ for (;;) {
+ auto [I1, I2] = llvm::mismatch(Key, Curr->Key);
+ Key = make_range(I1, Key.end());
+
+ if (I2 != Curr->Key.end()) {
+ // Match is partial. Either query is too short, or there is mismatching
+ // character. Split either way, and put new node in between of the
+ // current and its children.
+ Curr->split(I2);
+
+ // Split was caused by mismatch, so `findChild` would fail.
+ break;
+ }
+
+ Node *Child = Curr->findChild(Key);
+ if (!Child)
+ break;
+
+ // Move to child with the same first character.
+ Curr = Child;
+ }
+
+ if (Key.empty()) {
+ // The current node completely matches the key, return it.
+ return *Curr;
+ }
+
+ // `Key` is a suffix of original `Key` unmatched by path from the `Root` to
+ // the `Curr`, and we have no candidate in the children to match more.
+ // Create a new one.
+ return Curr->Children.emplace_back(Key);
+ }
+
+ ///
+ /// An iterator for traversing prefixes search results.
+ ///
+ /// This iterator is used by `find_prefixes` to traverse the tree and find
+ /// elements that are prefixes to the given key. It's a forward iterator.
+ ///
+ /// \tparam MappedType The type of the value pointed to by the iterator.
+ /// This will be `value_type` for non-const iterators
+ /// and `const value_type` for const iterators.
+ template <typename MappedType>
+ class IteratorImpl
+ : public iterator_facade_base<IteratorImpl<MappedType>,
+ std::forward_iterator_tag, MappedType> {
+ const Node *Curr = nullptr;
+ KeyConstIteratorRangeType Query{KeyConstIteratorType{},
+ KeyConstIteratorType{}};
+
+ void findNextValid() {
+ while (Curr && Curr->Value == typename ContainerType::iterator())
+ advance();
+ }
+
+ void advance() {
+ assert(Curr);
+ if (Query.empty()) {
+ Curr = nullptr;
+ return;
+ }
+
+ Curr = Curr->findChild(Query);
+ if (!Curr) {
+ Curr = nullptr;
+ return;
+ }
+
+ auto [I1, I2] = llvm::mismatch(Query, Curr->Key);
+ if (I2 != Curr->Key.end()) {
+ Curr = nullptr;
+ return;
+ }
+ Query = make_range(I1, Query.end());
+ }
+
+ friend class RadixTree;
+ IteratorImpl(const Node *C, const KeyConstIteratorRangeType &Q)
+ : Curr(C), Query(Q) {
+ findNextValid();
+ }
+
+ public:
+ IteratorImpl() = default;
+
+ MappedType &operator*() const { return *Curr->Value; }
+
+ IteratorImpl &operator++() {
+ advance();
+ findNextValid();
+ return *this;
+ }
+
+ bool operator==(const IteratorImpl &Other) const {
+ return Curr == Other.Curr;
+ }
+ };
+
+public:
+ RadixTree() = default;
+ RadixTree(RadixTree &&) = default;
+ RadixTree &operator=(RadixTree &&) = default;
+
+ using prefix_iterator = IteratorImpl<value_type>;
+ using const_prefix_iterator = IteratorImpl<const value_type>;
+
+ using iterator = typename ContainerType::iterator;
+ using const_iterator = typename ContainerType::const_iterator;
+
+ /// Returns true if the tree is empty.
+ bool empty() const { return KeyValuePairs.empty(); }
+
+ /// Returns the number of elements in the tree.
+ size_t size() const { return KeyValuePairs.size(); }
+
+ /// Returns the number of nodes in the tree.
+ ///
+ /// This function counts all internal nodes in the tree. It can be useful for
+ /// understanding the memory footprint or complexity of the tree structure.
+ size_t countNodes() const { return Root.countNodes(); }
+
+ /// Returns an iterator to the first element.
+ iterator begin() { return KeyValuePairs.begin(); }
+ const_iterator begin() const { return KeyValuePairs.begin(); }
+
+ /// Returns an iterator to the end of the tree.
+ iterator end() { return KeyValuePairs.end(); }
+ const_iterator end() const { return KeyValuePairs.end(); }
+
+ /// Constructs and inserts a new element into the tree.
+ ///
+ /// This function constructs an element in place within the tree. If an
+ /// element with the same key already exists, the insertion fails and the
+ /// function returns an iterator to the existing element along with `false`.
+ /// Otherwise, the new element is inserted and the function returns an
+ /// iterator to the new element along with `true`.
+ ///
+ /// \param Key The key of the element to construct.
+ /// \param Args Arguments to forward to the constructor of the mapped_type.
+ /// \return A pair consisting of an iterator to the inserted element (or to
+ /// the element that prevented insertion) and a boolean value
+ /// indicating whether the insertion took place.
+ template <typename... Ts>
+ std::pair<iterator, bool> emplace(key_type &&Key, Ts &&...Args) {
+ // We want to make new `Node` to refer key in the container, not the one
+ // from the argument.
+ // FIXME: Determine that we need a new node, before expanding
+ // `KeyValuePairs`.
+ const value_type &NewValue = KeyValuePairs.emplace_front(
+ std::move(Key), T(std::forward<Ts>(Args)...));
+ Node &Node = findOrCreate(NewValue.first);
+ bool HasValue = Node.Value != typename ContainerType::iterator();
+ if (!HasValue)
+ Node.Value = KeyValuePairs.begin();
+ else
+ KeyValuePairs.pop_front();
+ return {Node.Value, !HasValue};
+ }
+
+ ///
+ /// Finds all elements whose keys are prefixes of the given `Key`.
+ ///
+ /// This function returns an iterator range over all elements in the tree
+ /// whose keys are prefixes of the provided `Key`. For example, if the tree
+ /// contains "abcde", "abc", "abcdefgh", and `Key` is "abcde", this function
+ /// would return iterators to "abcde" and "abc".
+ ///
+ /// \param Key The key to search for prefixes of.
+ /// \return An `iterator_range` of `const_prefix_iterator`s, allowing
+ /// iteration over the found prefix elements.
+ /// \note The returned iterators reference the `Key` provided by the caller.
+ /// The caller must ensure that `Key` remains valid for the lifetime
+ /// of the iterators.
+ iterator_range<const_prefix_iterator>
+ find_prefixes(const key_type &Key) const {
+ return iterator_range<const_prefix_iterator>{
+ const_prefix_iterator(&Root, KeyConstIteratorRangeType(Key)),
+ const_prefix_iterator{}};
+ }
+};
+
+} // namespace llvm
+
+#endif // LLVM_ADT_RADIXTREE_H
diff --git a/llvm/unittests/ADT/CMakeLists.txt b/llvm/unittests/ADT/CMakeLists.txt
index 848ccba696e76..af503d9b82843 100644
--- a/llvm/unittests/ADT/CMakeLists.txt
+++ b/llvm/unittests/ADT/CMakeLists.txt
@@ -63,6 +63,7 @@ add_llvm_unittest(ADTTests
PointerUnionTest.cpp
PostOrderIteratorTest.cpp
PriorityWorklistTest.cpp
+ RadixTreeTest.cpp
RangeAdapterTest.cpp
RewriteBufferTest.cpp
SCCIteratorTest.cpp
diff --git a/llvm/unittests/ADT/RadixTreeTest.cpp b/llvm/unittests/ADT/RadixTreeTest.cpp
new file mode 100644
index 0000000000000..b2dd67c13a713
--- /dev/null
+++ b/llvm/unittests/ADT/RadixTreeTest.cpp
@@ -0,0 +1,379 @@
+//===- llvm/unittest/ADT/RadixTreeTest.cpp --------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ADT/RadixTree.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include <iterator>
+#include <list>
+#include <vector>
+
+using namespace llvm;
+namespace {
+
+using ::testing::ElementsAre;
+using ::testing::ElementsAreArray;
+using ::testing::Pair;
+using ::testing::UnorderedElementsAre;
+
+// Test with StringRef.
+
+TEST(RadixTreeTest, Empty) {
+ RadixTree<StringRef, int> T;
+ EXPECT_TRUE(T.empty());
+ EXPECT_EQ(T.size(), 0u);
+
+ EXPECT_TRUE(T.find_prefixes("").empty());
+ EXPECT_TRUE(T.find_prefixes("A").empty());
+
+ EXPECT_EQ(T.countNodes(), 1u);
+}
+
+TEST(RadixTreeTest, InsertEmpty) {
+ RadixTree<StringRef, int> T;
+ auto [It, IsNew] = T.emplace("", 4);
+ EXPECT_TRUE(!T.empty());
+ EXPECT_EQ(T.size(), 1u);
+ EXPECT_TRUE(IsNew);
+ const auto &[K, V] = *It;
+ EXPECT_TRUE(K.empty());
+ EXPECT_EQ(4, V);
+
+ EXPECT_THAT(T, ElementsAre(Pair("", 4)));
+
+ EXPECT_THAT(T.find_prefixes(""), ElementsAre(Pair("", 4)));
+
+ EXPECT_THAT(T.find_prefixes("a"), ElementsAre(Pair("", 4)));
+
+ EXPECT_EQ(T.countNodes(), 1u);
+}
+
+TEST(RadixTreeTest, Complex) {
+ RadixTree<StringRef, int> T;
+ T.emplace("abcd", 1);
+ EXPECT_EQ(T.countNodes(), 2u);
+ T.emplace("abklm", 2);
+ EXPECT_EQ(T.countNodes(), 4u);
+ T.emplace("123abklm", 3);
+ EXPECT_EQ(T.countNodes(), 5u);
+ T.emplace("123abklm", 4);
+ EXPECT_EQ(T.countNodes(), 5u);
+ T.emplace("ab", 5);
+ EXPECT_EQ(T.countNodes(), 5u);
+ T.emplace("1234567", 6);
+ EXPECT_EQ(T.countNodes(), 7u);
+ T.emplace("123456", 7);
+ EXPECT_EQ(T.countNodes(), 8u);
+ T.emplace("123456789", 8);
+ EXPECT_EQ(T.countNodes(), 9u);
+
+ EXPECT_THAT(T, UnorderedElementsAre(Pair("abcd", 1), Pair("abklm", 2),
+ Pair("123abklm", 3), Pair("ab", 5),
+ Pair("1234567", 6), Pair("123456", 7),
+ Pair("123456789", 8)));
+
+ EXPECT_THAT(T.find_prefixes("1234567890"),
+ UnorderedElementsAre(Pair("1234567", 6), Pair("123456", 7),
+ Pair("123456789", 8)));
+
+ EXPECT_THAT(T.find_prefixes("123abklm"),
+ UnorderedElementsAre(Pair("123abklm", 3)));
+
+ EXPECT_THAT(T.find_prefixes("abcdefg"),
+ UnorderedElementsAre(Pair("abcd", 1), Pair("ab", 5)));
+
+ EXPECT_EQ(T.countNodes(), 9u);
+}
+
+TEST(RadixTreeTest, ValueWith2Parameters) {
+ RadixTree<StringRef, std::pair<std::string, int>> T;
+ T.emplace("abcd", "a", 3);
+
+ EXPECT_THAT(T, UnorderedElementsAre(Pair("abcd", Pair("a", 3))));
+}
+
+// Test
diff erent types, less readable.
+
+template <typename T> struct TestData {
+ static const T Data1[];
+ static const T Data2[];
+};
+
+template <> const char TestData<char>::Data1[] = "abcdedcba";
+template <> const char TestData<char>::Data2[] = "abCDEDCba";
+
+template <> const int TestData<int>::Data1[] = {1, 2, 3, 4, 5, 4, 3, 2, 1};
+template <> const int TestData<int>::Data2[] = {1, 2, 4, 8, 16, 8, 4, 2, 1};
+
+template <typename T> class RadixTreeTypeTest : public ::testing::Test {
+public:
+ using IteratorType = decltype(adl_begin(std::declval<const T &>()));
+ using CharType = remove_cvref_t<decltype(*adl_begin(std::declval<T &>()))>;
+
+ T make(const CharType *Data, size_t N) { return T(StringRef(Data, N)); }
+
+ T make1(size_t N) { return make(TestData<CharType>::Data1, N); }
+ T make2(size_t N) { return make(TestData<CharType>::Data2, N); }
+};
+
+template <>
+iterator_range<StringRef::const_iterator>
+RadixTreeTypeTest<iterator_range<StringRef::const_iterator>>::make(
+ const char *Data, size_t N) {
+ return StringRef(Data).take_front(N);
+}
+
+template <>
+iterator_range<StringRef::const_reverse_iterator>
+RadixTreeTypeTest<iterator_range<StringRef::const_reverse_iterator>>::make(
+ const char *Data, size_t N) {
+ return reverse(StringRef(Data).take_back(N));
+}
+
+template <>
+ArrayRef<int> RadixTreeTypeTest<ArrayRef<int>>::make(const int *Data,
+ size_t N) {
+ return ArrayRef<int>(Data, Data + N);
+}
+
+template <>
+std::vector<int> RadixTreeTypeTest<std::vector<int>>::make(const int *Data,
+ size_t N) {
+ return std::vector<int>(Data, Data + N);
+}
+
+template <>
+std::list<int> RadixTreeTypeTest<std::list<int>>::make(const int *Data,
+ size_t N) {
+ return std::list<int>(Data, Data + N);
+}
+
+class TypeNameGenerator {
+public:
+ template <typename T> static std::string GetName(int) {
+ if (std::is_same_v<T, StringRef>)
+ return "StringRef";
+ if (std::is_same_v<T, std::string>)
+ return "string";
+ if (std::is_same_v<T, iterator_range<StringRef::const_iterator>>)
+ return "iterator_range";
+ if (std::is_same_v<T, iterator_range<StringRef::const_reverse_iterator>>)
+ return "reverse_iterator_range";
+ if (std::is_same_v<T, ArrayRef<int>>)
+ return "ArrayRef";
+ if (std::is_same_v<T, std::vector<int>>)
+ return "vector";
+ if (std::is_same_v<T, std::list<int>>)
+ return "list";
+ return "Unknown";
+ }
+};
+
+using TestTypes =
+ ::testing::Types<StringRef, std::string,
+ iterator_range<StringRef::const_iterator>,
+ iterator_range<StringRef::const_reverse_iterator>,
+ ArrayRef<int>, std::vector<int>, std::list<int>>;
+
+TYPED_TEST_SUITE(RadixTreeTypeTest, TestTypes, TypeNameGenerator);
+
+TYPED_TEST(RadixTreeTypeTest, Helpers) {
+ for (size_t i = 0; i < 9; ++i) {
+ auto R1 = this->make1(i);
+ auto R2 = this->make2(i);
+ EXPECT_EQ(llvm::range_size(R1), i);
+ EXPECT_EQ(llvm::range_size(R2), i);
+ auto [I1, I2] = llvm::mismatch(R1, R2);
+ // Exactly 2 first elements of Data1 and Data2 must match.
+ EXPECT_EQ(std::distance(R1.begin(), I1), std::min<int>(2, i));
+ }
+}
+
+TYPED_TEST(RadixTreeTypeTest, Empty) {
+ RadixTree<TypeParam, int> T;
+ EXPECT_TRUE(T.empty());
+ EXPECT_EQ(T.size(), 0u);
+
+ EXPECT_TRUE(T.find_prefixes(this->make1(0)).empty());
+ EXPECT_TRUE(T.find_prefixes(this->make2(1)).empty());
+
+ EXPECT_EQ(T.countNodes(), 1u);
+}
+
+TYPED_TEST(RadixTreeTypeTest, InsertEmpty) {
+ using TreeType = RadixTree<TypeParam, int>;
+ TreeType T;
+ auto [It, IsNew] = T.emplace(this->make1(0), 5);
+ EXPECT_TRUE(!T.empty());
+ EXPECT_EQ(T.size(), 1u);
+ EXPECT_TRUE(IsNew);
+ const auto &[K, V] = *It;
+ EXPECT_TRUE(K.empty());
+ EXPECT_EQ(5, V);
+
+ EXPECT_THAT(T.find_prefixes(this->make1(0)),
+ ElementsAre(Pair(ElementsAre(), 5)));
+
+ EXPECT_THAT(T.find_prefixes(this->make2(1)),
+ ElementsAre(Pair(ElementsAre(), 5)));
+
+ EXPECT_THAT(T, ElementsAre(Pair(ElementsAre(), 5)));
+
+ EXPECT_EQ(T.countNodes(), 1u);
+}
+
+TYPED_TEST(RadixTreeTypeTest, InsertEmptyTwice) {
+ using TreeType = RadixTree<TypeParam, int>;
+ TreeType T;
+ T.emplace(this->make1(0), 5);
+ auto [It, IsNew] = T.emplace(this->make1(0), 6);
+ EXPECT_TRUE(!T.empty());
+ EXPECT_EQ(T.size(), 1u);
+ EXPECT_TRUE(!IsNew);
+ const auto &[K, V] = *It;
+ EXPECT_TRUE(K.empty());
+ EXPECT_EQ(5, V);
+
+ EXPECT_THAT(T.find_prefixes(this->make1(0)),
+ ElementsAre(Pair(ElementsAre(), 5)));
+
+ EXPECT_THAT(T.find_prefixes(this->make2(1)),
+ ElementsAre(Pair(ElementsAre(), 5)));
+
+ EXPECT_THAT(T, ElementsAre(Pair(ElementsAre(), 5)));
+
+ EXPECT_EQ(T.countNodes(), 1u);
+}
+
+TYPED_TEST(RadixTreeTypeTest, InsertOne) {
+ using TreeType = RadixTree<TypeParam, int>;
+ TreeType T;
+ auto [It, IsNew] = T.emplace(this->make1(1), 4);
+ EXPECT_TRUE(!T.empty());
+ EXPECT_EQ(T.size(), 1u);
+ EXPECT_TRUE(IsNew);
+ const auto &[K, V] = *It;
+ EXPECT_THAT(K, ElementsAreArray(this->make1(1)));
+ EXPECT_EQ(4, V);
+
+ EXPECT_THAT(T, ElementsAre(Pair(ElementsAreArray(this->make1(1)), 4)));
+
+ EXPECT_THAT(T.find_prefixes(this->make1(1)),
+ ElementsAre(Pair(ElementsAreArray(this->make1(1)), 4)));
+
+ EXPECT_THAT(T.find_prefixes(this->make1(2)),
+ ElementsAre(Pair(ElementsAreArray(this->make1(1)), 4)));
+
+ EXPECT_EQ(T.countNodes(), 2u);
+}
+
+TYPED_TEST(RadixTreeTypeTest, InsertOneTwice) {
+ using TreeType = RadixTree<TypeParam, int>;
+ TreeType T;
+ T.emplace(this->make1(1), 4);
+ auto [It, IsNew] = T.emplace(this->make1(1), 4);
+ EXPECT_TRUE(!T.empty());
+ EXPECT_EQ(T.size(), 1u);
+ EXPECT_TRUE(!IsNew);
+
+ EXPECT_THAT(T, ElementsAre(Pair(ElementsAreArray(this->make1(1)), 4)));
+ EXPECT_EQ(T.countNodes(), 2u);
+}
+
+TYPED_TEST(RadixTreeTypeTest, InsertSuperStrings) {
+ using TreeType = RadixTree<TypeParam, int>;
+ TreeType T;
+
+ for (size_t Len = 0; Len < 7; Len += 2) {
+ auto [It, IsNew] = T.emplace(this->make1(Len), Len);
+ EXPECT_TRUE(IsNew);
+ }
+
+ EXPECT_THAT(T,
+ UnorderedElementsAre(Pair(ElementsAreArray(this->make1(0)), 0),
+ Pair(ElementsAreArray(this->make1(2)), 2),
+ Pair(ElementsAreArray(this->make1(4)), 4),
+ Pair(ElementsAreArray(this->make1(6)), 6)));
+
+ EXPECT_THAT(T.find_prefixes(this->make1(0)),
+ UnorderedElementsAre(Pair(ElementsAreArray(this->make1(0)), 0)));
+
+ EXPECT_THAT(T.find_prefixes(this->make1(3)),
+ UnorderedElementsAre(Pair(ElementsAreArray(this->make1(0)), 0),
+ Pair(ElementsAreArray(this->make1(2)), 2)));
+
+ EXPECT_THAT(T.find_prefixes(this->make1(7)),
+ UnorderedElementsAre(Pair(ElementsAreArray(this->make1(0)), 0),
+ Pair(ElementsAreArray(this->make1(2)), 2),
+ Pair(ElementsAreArray(this->make1(4)), 4),
+ Pair(ElementsAreArray(this->make1(6)), 6)));
+
+ EXPECT_EQ(T.countNodes(), 4u);
+}
+
+TYPED_TEST(RadixTreeTypeTest, InsertSubStrings) {
+ using TreeType = RadixTree<TypeParam, int>;
+ TreeType T;
+
+ for (size_t Len = 0; Len < 7; Len += 2) {
+ auto [It, IsNew] = T.emplace(this->make1(7 - Len), 7 - Len);
+ EXPECT_TRUE(IsNew);
+ }
+
+ EXPECT_THAT(T,
+ UnorderedElementsAre(Pair(ElementsAreArray(this->make1(1)), 1),
+ Pair(ElementsAreArray(this->make1(3)), 3),
+ Pair(ElementsAreArray(this->make1(5)), 5),
+ Pair(ElementsAreArray(this->make1(7)), 7)));
+
+ EXPECT_THAT(T.find_prefixes(this->make1(0)), UnorderedElementsAre());
+
+ EXPECT_THAT(T.find_prefixes(this->make1(3)),
+ UnorderedElementsAre(Pair(ElementsAreArray(this->make1(1)), 1),
+ Pair(ElementsAreArray(this->make1(3)), 3)));
+
+ EXPECT_THAT(T.find_prefixes(this->make1(6)),
+ UnorderedElementsAre(Pair(ElementsAreArray(this->make1(1)), 1),
+ Pair(ElementsAreArray(this->make1(3)), 3),
+ Pair(ElementsAreArray(this->make1(5)), 5)));
+
+ EXPECT_EQ(T.countNodes(), 5u);
+}
+
+TYPED_TEST(RadixTreeTypeTest, InsertVShape) {
+ using TreeType = RadixTree<TypeParam, int>;
+ TreeType T;
+
+ EXPECT_EQ(T.countNodes(), 1u);
+ T.emplace(this->make1(5), 15);
+ EXPECT_EQ(T.countNodes(), 2u);
+ T.emplace(this->make2(6), 26);
+ EXPECT_EQ(T.countNodes(), 4u);
+ T.emplace(this->make2(1), 21);
+ EXPECT_EQ(T.countNodes(), 5u);
+
+ EXPECT_THAT(T,
+ UnorderedElementsAre(Pair(ElementsAreArray(this->make1(5)), 15),
+ Pair(ElementsAreArray(this->make2(6)), 26),
+ Pair(ElementsAreArray(this->make2(1)), 21)));
+
+ EXPECT_THAT(T.find_prefixes(this->make1(7)),
+ UnorderedElementsAre(Pair(ElementsAreArray(this->make2(1)), 21),
+ Pair(ElementsAreArray(this->make1(5)), 15)));
+
+ EXPECT_THAT(T.find_prefixes(this->make2(7)),
+ UnorderedElementsAre(Pair(ElementsAreArray(this->make2(1)), 21),
+ Pair(ElementsAreArray(this->make2(6)), 26)));
+
+ EXPECT_EQ(T.countNodes(), 5u);
+}
+
+} // namespace
More information about the llvm-commits
mailing list