Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
tpl_radix_tree.H
Go to the documentation of this file.
1
2/*
3 Aleph_w
4
5 Data structures & Algorithms
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
91#ifndef TPL_RADIX_TREE_H
92#define TPL_RADIX_TREE_H
93
94#include <algorithm>
95#include <memory>
96#include <optional>
97#include <string>
98#include <tuple>
99#include <type_traits>
100#include <utility>
101
102#include <ah-errors.H>
103#include <tpl_array.H>
104
105namespace Aleph {
106
118template <typename T, typename Char = char>
120{
121public:
123 using Key = std::basic_string<Char>;
124
125private:
126 struct Node
127 {
128 Key edge_label; // label of the edge from the parent to this
129 // node; empty only for the root.
130 std::optional<T> value; // set iff some key ends exactly at this node.
132 // unordered; a node's children always have
133 // pairwise-distinct first characters in
134 // their own edge_label.
135
136 Node() = default;
137 explicit Node(Key label) : edge_label(std::move(label)) {}
138 };
139
140 static constexpr size_t npos = static_cast<size_t>(-1);
141
142 std::unique_ptr<Node> root_;
143 size_t size_ = 0;
144
145 // Only ever called with a `const Node *` (from find_node(),
146 // longest_prefix(), keys_with_prefix(), all read-only traversals);
147 // insert_impl()/erase() need the child's *slot index* to mutate the
148 // parent afterward, so they use find_child_slot() instead. No non-const
149 // overload is needed.
150 [[nodiscard]] static const Node *find_child(const Node *node, const Char c) noexcept
151 {
152 for (const auto &kv : node->children)
153 if (kv.first == c)
154 return kv.second.get();
155 return nullptr;
156 }
157
158 [[nodiscard]] static size_t find_child_slot(Node *node, const Char c) noexcept
159 {
160 for (size_t i = 0; i < node->children.size(); ++i)
161 if (node->children[i].first == c)
162 return i;
163 return npos;
164 }
165
166 static void add_child(Node *node, const Char c, std::unique_ptr<Node> child)
167 {
168 node->children.append(std::make_pair(c, std::move(child)));
169 }
170
173 static void remove_child_at(Node *node, const size_t idx)
174 {
175 const size_t last = node->children.size() - 1;
176 if (idx != last)
177 std::swap(node->children[idx], node->children[last]);
178 std::ignore = node->children.remove_last();
179 }
180
182 [[nodiscard]] static size_t common_prefix_length(const Key &label, const Key &key,
183 const size_t key_off) noexcept
184 {
185 const size_t max_n = std::min(label.size(), key.size() - key_off);
186 size_t n = 0;
187 while (n < max_n and label[n] == key[key_off + n])
188 ++n;
189 return n;
190 }
191
199 template <typename U>
200 bool insert_impl(const Key &key, U &&value, const bool assign_if_present = false)
201 {
202 Node *cur = root_.get();
203 size_t i = 0;
204
205 for (;;)
206 {
207 if (i == key.size())
208 {
209 if (cur->value.has_value())
210 {
212 *cur->value = std::forward<U>(value);
213 return false;
214 }
215 cur->value.emplace(std::forward<U>(value));
216 ++size_;
217 return true;
218 }
219
220 const Char c = key[i];
221 const size_t slot = find_child_slot(cur, c);
222
223 if (slot == npos)
224 {
225 auto leaf = std::make_unique<Node>(key.substr(i));
226 leaf->value.emplace(std::forward<U>(value));
227 add_child(cur, c, std::move(leaf));
228 ++size_;
229 return true;
230 }
231
232 Node *child = cur->children[slot].second.get();
233 const Key &label = child->edge_label;
234 const size_t remaining = key.size() - i;
235 const size_t lcp = common_prefix_length(label, key, i);
236
237 if (lcp == label.size())
238 {
239 // The whole edge matches; keep descending.
240 i += lcp;
241 cur = child;
242 continue;
243 }
244
245 // `label` only partially matches: split it at `lcp` (lcp >= 1,
246 // since `child` was found via its first character matching `c`).
247 Key split_label = label.substr(0, lcp);
248 Key child_suffix = label.substr(lcp);
249 auto split = std::make_unique<Node>(std::move(split_label));
250 split->children.reserve(lcp == remaining ? 1 : 2);
251
252 if (lcp == remaining)
253 {
254 // The new key ends exactly at the split point.
255 split->value.emplace(std::forward<U>(value));
256 }
257 else
258 {
259 auto leaf = std::make_unique<Node>(key.substr(i + lcp));
260 leaf->value.emplace(std::forward<U>(value));
261 const Char leaf_first = leaf->edge_label[0];
262 add_child(split.get(), leaf_first, std::move(leaf));
263 }
264
265 child->edge_label = std::move(child_suffix);
266 const Char child_first = child->edge_label[0];
267
268 split->children.append(std::make_pair(child_first, std::unique_ptr<Node>{}));
269 std::unique_ptr<Node> detached = std::move(cur->children[slot].second);
270 split->children[split->children.size() - 1].second = std::move(detached);
271 cur->children[slot].second = std::move(split);
272 ++size_;
273 return true;
274 }
275 }
276
277 [[nodiscard]] const Node *find_node(const Key &key) const noexcept
278 {
279 const Node *cur = root_.get();
280 size_t i = 0;
281 while (i < key.size())
282 {
283 const Char c = key[i];
284 const Node *child = find_child(cur, c);
285 if (child == nullptr)
286 return nullptr;
287
288 const Key &label = child->edge_label;
289 const size_t remaining = key.size() - i;
290 if (label.size() > remaining or key.compare(i, label.size(), label) != 0)
291 return nullptr;
292
293 i += label.size();
294 cur = child;
295 }
296 return cur;
297 }
298
299 static void collect_keys(const Node *node, Key &prefix_acc, Array<Key> &out)
300 {
301 if (node->value.has_value())
302 out.append(prefix_acc);
303 for (const auto &kv : node->children)
304 {
305 const size_t old_size = prefix_acc.size();
306 prefix_acc += kv.second->edge_label;
307 collect_keys(kv.second.get(), prefix_acc, out);
308 prefix_acc.resize(old_size);
309 }
310 }
311
312 [[nodiscard]] static std::unique_ptr<Node> clone_node(const Node *src)
313 requires std::is_copy_constructible_v<T>
314 {
315 auto dst = std::make_unique<Node>(src->edge_label);
316 if (src->value.has_value())
317 dst->value.emplace(*src->value);
318 dst->children.reserve(src->children.size());
319 for (const auto &kv : src->children)
320 dst->children.append(std::make_pair(kv.first, clone_node(kv.second.get())));
321 return dst;
322 }
323
324public:
329
339 ~RadixTree() = default;
340
347 {
348 root_.swap(other.root_);
349 std::swap(size_, other.size_);
350 }
351
359 {
360 if (this != &other)
361 {
362 auto empty_root = std::make_unique<Node>();
363 root_ = std::move(other.root_);
364 size_ = other.size_;
365 other.root_ = std::move(empty_root);
366 other.size_ = 0;
367 }
368 return *this;
369 }
370
376 requires std::is_copy_constructible_v<T>
377 : root_(clone_node(other.root_.get())), size_(other.size_)
378 {}
379
386 requires std::is_copy_constructible_v<T>
387 {
388 if (this != &other)
389 {
390 root_ = clone_node(other.root_.get());
391 size_ = other.size_;
392 }
393 return *this;
394 }
395
401 {
402 return size_;
403 }
404
410 {
411 return size_ == 0;
412 }
413
423 bool insert(const Key &key, const T &value)
424 {
425 return insert_impl(key, value);
426 }
427
439 bool insert(const Key &key, T &&value)
440 {
441 return insert_impl(key, std::move(value));
442 }
443
452 void insert_or_assign(const Key &key, T value)
453 {
454 insert_impl(key, std::move(value), /* assign_if_present = */ true);
455 }
456
465 bool erase(const Key &key)
466 {
467 struct Frame
468 {
469 Node *parent;
470 size_t slot;
471 };
472 Array<Frame> path;
473
474 Node *cur = root_.get();
475 size_t i = 0;
476 while (i < key.size())
477 {
478 const Char c = key[i];
479 const size_t slot = find_child_slot(cur, c);
480 if (slot == npos)
481 return false;
482
483 Node *child = cur->children[slot].second.get();
484 const Key &label = child->edge_label;
485 const size_t remaining = key.size() - i;
486 if (label.size() > remaining or key.compare(i, label.size(), label) != 0)
487 return false;
488
489 path.append(Frame{cur, slot});
490 i += label.size();
491 cur = child;
492 }
493
494 if (not cur->value.has_value())
495 return false;
496
497 cur->value.reset();
498 --size_;
499
500 // Bubble upward: prune now-empty leaves, and merge any node left with
501 // exactly one child and no value back into a single compressed edge.
502 while (not path.is_empty())
503 {
504 const Frame f = path.get_last();
505 std::ignore = path.remove_last();
506 Node *node = f.parent->children[f.slot].second.get();
507
508 if (node->children.is_empty() and not node->value.has_value())
509 {
510 remove_child_at(f.parent, f.slot);
511 continue; // f.parent's shape changed; the next frame re-checks it.
512 }
513
514 if (node->children.size() == 1 and not node->value.has_value())
515 {
516 auto &only = node->children[0];
517 only.second->edge_label = node->edge_label + only.second->edge_label;
518 f.parent->children[f.slot].second = std::move(only.second);
519 }
520
521 break; // Nothing above `node` changed shape; stop bubbling.
522 }
523
524 return true;
525 }
526
532 [[nodiscard]] bool contains(const Key &key) const noexcept
533 {
534 return find(key) != nullptr;
535 }
536
544 [[nodiscard]] const T *find(const Key &key) const noexcept
545 {
546 const Node *n = find_node(key);
547 return (n != nullptr and n->value.has_value()) ? &*n->value : nullptr;
548 }
549
557 [[nodiscard]] T *find(const Key &key) noexcept
558 {
559 const Node *n = find_node(key);
560 return (n != nullptr and n->value.has_value()) ? const_cast<T *>(&*n->value) : nullptr;
561 }
562
578 [[nodiscard]] std::optional<Key> longest_prefix(const Key &key) const
579 {
580 std::optional<Key> best;
581 const Node *cur = root_.get();
582 size_t i = 0;
583
584 if (cur->value.has_value())
585 best = Key{};
586
587 while (i < key.size())
588 {
589 const Char c = key[i];
590 const Node *child = find_child(cur, c);
591 if (child == nullptr)
592 break;
593
594 const Key &label = child->edge_label;
595 const size_t remaining = key.size() - i;
596 if (label.size() > remaining or key.compare(i, label.size(), label) != 0)
597 break;
598
599 i += label.size();
600 cur = child;
601 if (cur->value.has_value())
602 best = key.substr(0, i);
603 }
604
605 return best;
606 }
607
617 {
618 Array<Key> result;
619 const Node *cur = root_.get();
620 size_t i = 0;
621
622 while (i < prefix.size())
623 {
624 const Char c = prefix[i];
625 const Node *child = find_child(cur, c);
626 if (child == nullptr)
627 return result;
628
629 const Key &label = child->edge_label;
630 const size_t remaining = prefix.size() - i;
631 const size_t lcp = common_prefix_length(label, prefix, i);
632
633 if (lcp < label.size())
634 {
635 if (lcp != remaining)
636 return result; // genuine mismatch: nothing shares `prefix`.
637 // `prefix` runs out partway through this edge: every key under
638 // `child` still starts with `prefix` (the edge is consistent
639 // with it as far as it goes).
640 Key base = prefix.substr(0, i) + label;
641 collect_keys(child, base, result);
642 return result;
643 }
644
645 i += lcp;
646 cur = child;
647 }
648
649 Key base = prefix;
650 collect_keys(cur, base, result);
651 return result;
652 }
653
678 [[nodiscard]] bool verify() const
679 {
680 size_t counted_values = 0;
681 const bool structurally_valid = verify_rec(root_.get(), true, counted_values);
683 }
684
685private:
686 static bool verify_rec(const Node *node, const bool is_root, size_t &counted_values)
687 {
688 if (node->value.has_value())
690
691 if (not is_root)
692 {
693 if (node->edge_label.empty())
694 return false;
695 if (node->children.size() == 1 and not node->value.has_value())
696 return false;
697 }
698
699 for (size_t i = 0; i < node->children.size(); ++i)
700 for (size_t j = i + 1; j < node->children.size(); ++j)
701 if (node->children[i].first == node->children[j].first)
702 return false;
703
704 for (const auto &kv : node->children)
705 {
706 if (kv.second->edge_label.empty() or kv.second->edge_label[0] != kv.first)
707 return false;
708 if (not verify_rec(kv.second.get(), false, counted_values))
709 return false;
710 }
711
712 return true;
713 }
714};
715
716} // namespace Aleph
717
718#endif // TPL_RADIX_TREE_H
Exception handling system with formatted messages for Aleph-w.
size_t size_t int32_t value
Definition ca-c-api.h:116
size_t size_t int32_t * out
Definition ca-c-api.h:120
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:138
constexpr bool is_empty() const noexcept
Checks if the container is empty.
Definition tpl_array.H:359
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:250
T & get_last() noexcept
return a modifiable reference to the last element.
Definition tpl_array.H:392
Compressed prefix tree mapping std::basic_string<Char> keys to values of type T.
RadixTree(RadixTree &&other)
Move constructor: other is left as a valid, empty tree.
static void remove_child_at(Node *node, const size_t idx)
Remove the child at idx in O(1) by swapping with the last slot; the (unordered) children array does n...
static bool verify_rec(const Node *node, const bool is_root, size_t &counted_values)
RadixTree()
Construct an empty tree.
bool is_empty() const noexcept
Check whether the tree holds no keys.
const T * find(const Key &key) const noexcept
Look up key.
std::basic_string< Char > Key
Key type: strings over Char.
bool insert(const Key &key, T &&value)
Insert key with value moved in, only if key is absent.
static std::unique_ptr< Node > clone_node(const Node *src)
static void add_child(Node *node, const Char c, std::unique_ptr< Node > child)
bool insert(const Key &key, const T &value)
Insert key with a copy of value, only if key is absent.
RadixTree(const RadixTree &other)
Deep-copy constructor: every node is cloned independently.
size_t size() const noexcept
Return the number of keys currently stored.
bool verify() const
Recursively verify the tree's structural invariants.
T * find(const Key &key) noexcept
Non-const overload of find(const Key&).
static constexpr size_t npos
static size_t find_child_slot(Node *node, const Char c) noexcept
static size_t common_prefix_length(const Key &label, const Key &key, const size_t key_off) noexcept
Length of the common prefix between label and key[key_off..).
bool insert_impl(const Key &key, U &&value, const bool assign_if_present=false)
Shared implementation for insert()'s copy/move overloads and for insert_or_assign(): U is deduced as ...
std::optional< Key > longest_prefix(const Key &key) const
Find the longest stored key that is a prefix of key.
~RadixTree()=default
Destructor.
std::unique_ptr< Node > root_
void insert_or_assign(const Key &key, T value)
Insert key with value, or overwrite the existing value if key is already present.
bool contains(const Key &key) const noexcept
Check whether key is present.
static void collect_keys(const Node *node, Key &prefix_acc, Array< Key > &out)
Array< Key > keys_with_prefix(const Key &prefix) const
Return every stored key that starts with prefix.
static const Node * find_child(const Node *node, const Char c) noexcept
RadixTree & operator=(RadixTree &&other)
Move assignment operator: other is left as a valid, empty tree.
bool erase(const Key &key)
Remove key if present, merging any resulting single-child, valueless node back into a compressed edge...
const Node * find_node(const Key &key) const noexcept
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
and
Check uniqueness with explicit hash + equality functors.
std::decay_t< typename HeadC::Item_Type > T
Definition ah-zip.H:105
static void prefix(Node *root, DynList< Node * > &acc)
std::vector< std::string > & split(const std::string &s, const char delim, std::vector< std::string > &elems)
Split a std::string by a single delimiter character.
STL namespace.
Array< std::pair< Char, std::unique_ptr< Node > > > children
std::optional< T > value
Dynamic array container with automatic resizing.