Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
radix_tree_test.cc
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
36#include <gtest/gtest.h>
37
38#include <prefix-tree.H>
39#include <tpl_radix_tree.H>
40
41#include <algorithm>
42#include <map>
43#include <memory>
44#include <random>
45#include <set>
46#include <stdexcept>
47#include <string>
48#include <vector>
49
50using namespace Aleph;
51
52namespace
53{
54 std::vector<std::string> sorted(std::vector<std::string> v)
55 {
56 std::sort(v.begin(), v.end());
57 return v;
58 }
59
60 std::vector<std::string> to_vector(const Array<std::string> &a)
61 {
62 std::vector<std::string> v;
63 for (const auto &s : a)
64 v.push_back(s);
65 return v;
66 }
67
68 std::vector<std::string> to_vector(const DynArray<std::string> &a)
69 {
70 std::vector<std::string> v;
71 a.for_each([&v](const std::string &s) { v.push_back(s); });
72 return v;
73 }
74
75 struct Throwing_Copy
76 {
77 static bool should_throw;
78 int value;
79
80 explicit Throwing_Copy(int v) : value(v) {}
81 Throwing_Copy(const Throwing_Copy &other) : value(other.value)
82 {
83 if (should_throw)
84 throw std::runtime_error("Throwing_Copy: copy failed");
85 }
86 Throwing_Copy(Throwing_Copy &&) = default;
87 Throwing_Copy &operator=(const Throwing_Copy &) = default;
88 Throwing_Copy &operator=(Throwing_Copy &&) = default;
89 };
90
91 bool Throwing_Copy::should_throw = false;
92} // namespace
93
95{
98 EXPECT_EQ(t.size(), 0u);
99 EXPECT_FALSE(t.contains("anything"));
100 EXPECT_EQ(t.find("anything"), nullptr);
101}
102
108
110{
112 EXPECT_TRUE(t.insert("apple", 1));
113 EXPECT_EQ(t.size(), 1u);
114 ASSERT_NE(t.find("apple"), nullptr);
115 EXPECT_EQ(*t.find("apple"), 1);
116 EXPECT_TRUE(t.contains("apple"));
117 EXPECT_FALSE(t.contains("app"));
118 EXPECT_FALSE(t.contains("applesauce"));
119}
120
122{
124 EXPECT_TRUE(t.insert("apple", 1));
125 EXPECT_FALSE(t.insert("apple", 2));
126 ASSERT_NE(t.find("apple"), nullptr);
127 EXPECT_EQ(*t.find("apple"), 1); // unchanged
128 EXPECT_EQ(t.size(), 1u);
129}
130
132{
134 EXPECT_TRUE(t.insert("k", std::make_unique<int>(42)));
135 ASSERT_NE(t.find("k"), nullptr);
136 ASSERT_NE(*t.find("k"), nullptr);
137 EXPECT_EQ(**t.find("k"), 42);
138}
139
141{
143 t.insert_or_assign("k", 1);
144 ASSERT_NE(t.find("k"), nullptr);
145 EXPECT_EQ(*t.find("k"), 1);
146 EXPECT_EQ(t.size(), 1u);
147
148 t.insert_or_assign("k", 2);
149 ASSERT_NE(t.find("k"), nullptr);
150 EXPECT_EQ(*t.find("k"), 2);
151 EXPECT_EQ(t.size(), 1u); // still one key
152}
153
155{
157 EXPECT_TRUE(t.insert("", 7));
158 EXPECT_TRUE(t.contains(""));
159 ASSERT_NE(t.find(""), nullptr);
160 EXPECT_EQ(*t.find(""), 7);
161 EXPECT_EQ(t.size(), 1u);
162
163 EXPECT_TRUE(t.insert("a", 1));
164 EXPECT_EQ(t.size(), 2u);
165 EXPECT_TRUE(t.contains(""));
166 EXPECT_TRUE(t.contains("a"));
167
168 EXPECT_TRUE(t.erase(""));
169 EXPECT_FALSE(t.contains(""));
170 EXPECT_TRUE(t.contains("a"));
171}
172
174{
176 t.insert("apple", 1);
177 EXPECT_FALSE(t.erase("banana"));
178 EXPECT_FALSE(t.erase("app")); // prefix of a key, not a key itself
179 EXPECT_FALSE(t.erase("applesauce")); // key extends past a stored one
180 EXPECT_EQ(t.size(), 1u);
181}
182
184{
186 t.insert("apple", 1);
187 t.insert("app", 2);
188 EXPECT_TRUE(t.erase("apple"));
189 EXPECT_FALSE(t.contains("apple"));
190 EXPECT_TRUE(t.contains("app"));
191 EXPECT_EQ(t.size(), 1u);
192 EXPECT_TRUE(t.verify());
193}
194
195// --- Edge-splitting / compression-specific scenarios -----------------------
196
198{
200 ASSERT_TRUE(t.insert("romane", 1));
201 ASSERT_TRUE(t.insert("romanus", 2));
202 ASSERT_TRUE(t.insert("romulus", 3));
203 ASSERT_TRUE(t.insert("rubens", 4));
204 ASSERT_TRUE(t.insert("ruber", 5));
205 ASSERT_TRUE(t.insert("rubicon", 6));
206 ASSERT_TRUE(t.insert("rubicundus", 7));
207
208 EXPECT_EQ(t.size(), 7u);
209 for (const auto &[key, expected] :
210 std::vector<std::pair<std::string, int>>{
211 {"romane", 1}, {"romanus", 2}, {"romulus", 3}, {"rubens", 4},
212 {"ruber", 5}, {"rubicon", 6}, {"rubicundus", 7}})
213 {
214 ASSERT_NE(t.find(key), nullptr) << "missing key: " << key;
215 EXPECT_EQ(*t.find(key), expected) << "wrong value for key: " << key;
216 }
217
218 // Prefixes that were never inserted must not be considered present.
219 EXPECT_FALSE(t.contains("rom"));
220 EXPECT_FALSE(t.contains("rub"));
221 EXPECT_FALSE(t.contains("ru"));
222 EXPECT_TRUE(t.verify());
223}
224
226{
228 ASSERT_TRUE(t.insert("romane", 1));
229 ASSERT_TRUE(t.insert("roman", 2)); // prefix of "romane"
230
231 EXPECT_EQ(t.size(), 2u);
232 ASSERT_NE(t.find("roman"), nullptr);
233 EXPECT_EQ(*t.find("roman"), 2);
234 ASSERT_NE(t.find("romane"), nullptr);
235 EXPECT_EQ(*t.find("romane"), 1);
236 EXPECT_FALSE(t.contains("roma"));
237 EXPECT_TRUE(t.verify());
238}
239
241{
243 ASSERT_TRUE(t.insert("roman", 1));
244 ASSERT_TRUE(t.insert("romane", 2)); // extends "roman"
245
246 EXPECT_EQ(t.size(), 2u);
247 ASSERT_NE(t.find("roman"), nullptr);
248 EXPECT_EQ(*t.find("roman"), 1);
249 ASSERT_NE(t.find("romane"), nullptr);
250 EXPECT_EQ(*t.find("romane"), 2);
251 EXPECT_TRUE(t.verify());
252}
253
255{
257 ASSERT_TRUE(t.insert("test", 1));
258 ASSERT_TRUE(t.insert("team", 2)); // splits "te" | "st"/"am"
259 ASSERT_TRUE(t.insert("toast", 3)); // splits "t" | "e.."/"oast"
260
261 ASSERT_EQ(t.size(), 3u);
262
263 // Removing "team" should leave "test" reachable and merge any now-single-
264 // child, valueless intermediate node the deletion exposes.
265 EXPECT_TRUE(t.erase("team"));
266 EXPECT_TRUE(t.verify());
267 EXPECT_FALSE(t.contains("team"));
268 ASSERT_NE(t.find("test"), nullptr);
269 EXPECT_EQ(*t.find("test"), 1);
270 ASSERT_NE(t.find("toast"), nullptr);
271 EXPECT_EQ(*t.find("toast"), 3);
272 EXPECT_EQ(t.size(), 2u);
273
274 // Continue removing until the tree is empty, confirming compression
275 // merges never corrupt subsequent lookups.
276 EXPECT_TRUE(t.erase("test"));
277 EXPECT_TRUE(t.verify());
278 ASSERT_NE(t.find("toast"), nullptr);
279 EXPECT_EQ(*t.find("toast"), 3);
280 EXPECT_TRUE(t.erase("toast"));
281 EXPECT_TRUE(t.verify());
283 EXPECT_EQ(t.size(), 0u);
284}
285
287{
289 ASSERT_TRUE(t.insert("roman", 1));
290 ASSERT_TRUE(t.insert("romane", 2));
291 ASSERT_TRUE(t.insert("romanus", 3));
292
293 // "roman" is an internal node (has children "e" and "us") that also
294 // holds a value; erasing it must not disturb its children.
295 EXPECT_TRUE(t.erase("roman"));
296 EXPECT_TRUE(t.verify());
297 EXPECT_FALSE(t.contains("roman"));
298 ASSERT_NE(t.find("romane"), nullptr);
299 EXPECT_EQ(*t.find("romane"), 2);
300 ASSERT_NE(t.find("romanus"), nullptr);
301 EXPECT_EQ(*t.find("romanus"), 3);
302 EXPECT_EQ(t.size(), 2u);
303}
304
305// --- longest_prefix ----------------------------------------------------
306
308{
310 t.insert("a", 1);
311 t.insert("ab", 2);
312 t.insert("abc", 3);
313
314 EXPECT_EQ(t.longest_prefix("abcd"), std::optional<std::string>("abc"));
315 EXPECT_EQ(t.longest_prefix("abc"), std::optional<std::string>("abc"));
316 EXPECT_EQ(t.longest_prefix("ab"), std::optional<std::string>("ab"));
317 EXPECT_EQ(t.longest_prefix("a"), std::optional<std::string>("a"));
318 EXPECT_EQ(t.longest_prefix(""), std::nullopt);
319 EXPECT_EQ(t.longest_prefix("xyz"), std::nullopt);
320}
321
323{
325 t.insert("", 0);
326 t.insert("ab", 2);
327
328 EXPECT_EQ(t.longest_prefix("abc"), std::optional<std::string>("ab"));
329 EXPECT_EQ(t.longest_prefix("xyz"), std::optional<std::string>(""));
330 EXPECT_EQ(t.longest_prefix(""), std::optional<std::string>(""));
331}
332
333// --- keys_with_prefix ----------------------------------------------------
334
336{
338 for (const auto &k :
339 {"romane", "romanus", "romulus", "rubens", "ruber", "rubicon"})
340 t.insert(k, 0);
341
343 sorted({"romane", "romanus", "romulus"}));
345 sorted({"rubens", "ruber", "rubicon"}));
347 sorted({"romane", "romanus", "romulus", "rubens", "ruber",
348 "rubicon"}));
349 EXPECT_TRUE(to_vector(t.keys_with_prefix("z")).empty());
350 EXPECT_TRUE(to_vector(t.keys_with_prefix("rom-nope")).empty());
351}
352
354{
356 t.insert("a", 1);
357 t.insert("b", 2);
358 t.insert("", 3);
359
360 EXPECT_EQ(sorted(to_vector(t.keys_with_prefix(""))), sorted({"", "a", "b"}));
361}
362
364{
366 t.insert("roman", 1);
367 t.insert("romane", 2);
368
370 sorted({"roman", "romane"}));
371}
372
373// --- Copy / move semantics ------------------------------------------------
374
376{
378 a.insert("x", 1);
379 a.insert("y", 2);
380
381 RadixTree<int> b(std::move(a));
382 EXPECT_EQ(b.size(), 2u);
383 ASSERT_NE(b.find("x"), nullptr);
384 EXPECT_EQ(*b.find("x"), 1);
385
386 EXPECT_TRUE(a.is_empty()); // NOLINT(bugprone-use-after-move): documented
387 EXPECT_EQ(a.size(), 0u);
388 EXPECT_TRUE(a.insert("z", 3)); // moved-from tree stays usable
389}
390
392{
393 RadixTree<int> source;
394 ASSERT_TRUE(source.insert("x", 1));
395 ASSERT_TRUE(source.insert("xy", 2));
396
397 RadixTree<int> target;
398 ASSERT_TRUE(target.insert("old", 99));
399
400 target = std::move(source);
401
402 EXPECT_EQ(target.size(), 2u);
403 EXPECT_TRUE(target.contains("x"));
404 EXPECT_TRUE(target.contains("xy"));
405 EXPECT_FALSE(target.contains("old"));
406 ASSERT_NE(target.find("xy"), nullptr);
407 EXPECT_EQ(*target.find("xy"), 2);
408 EXPECT_TRUE(target.verify());
409
410 EXPECT_TRUE(source.is_empty()); // NOLINT(bugprone-use-after-move): documented
411 EXPECT_EQ(source.size(), 0u);
412 EXPECT_TRUE(source.verify());
413 EXPECT_TRUE(source.insert("z", 3)); // moved-from tree stays usable
414 EXPECT_TRUE(source.contains("z"));
415}
416
418{
420 a.insert("x", 1);
421 a.insert("xy", 2);
422
423 RadixTree<int> b(a);
424 EXPECT_EQ(b.size(), a.size());
425 ASSERT_NE(b.find("xy"), nullptr);
426 EXPECT_EQ(*b.find("xy"), 2);
427
428 // Mutating the copy must not affect the original, and vice versa.
429 b.insert_or_assign("xy", 99);
430 EXPECT_EQ(*b.find("xy"), 99);
431 EXPECT_EQ(*a.find("xy"), 2);
432
433 a.erase("x");
434 EXPECT_FALSE(a.contains("x"));
435 EXPECT_TRUE(b.contains("x"));
436}
437
439{
440 RadixTree<int> source;
441 ASSERT_TRUE(source.insert("x", 1));
442 ASSERT_TRUE(source.insert("xy", 2));
443
444 RadixTree<int> target;
445 ASSERT_TRUE(target.insert("old", 99));
446
447 target = source;
448
449 EXPECT_EQ(target.size(), source.size());
450 EXPECT_FALSE(target.contains("old"));
451 ASSERT_NE(target.find("xy"), nullptr);
452 EXPECT_EQ(*target.find("xy"), 2);
453 EXPECT_TRUE(target.verify());
454 EXPECT_TRUE(source.verify());
455
456 target.insert_or_assign("xy", 99);
457 EXPECT_EQ(*target.find("xy"), 99);
458 EXPECT_EQ(*source.find("xy"), 2);
459
460 source.erase("x");
461 EXPECT_FALSE(source.contains("x"));
462 EXPECT_TRUE(target.contains("x"));
463}
464
466{
468 t.insert("a", Throwing_Copy(1));
469 EXPECT_EQ(t.size(), 1u);
470
471 Throwing_Copy::should_throw = true;
472 const Throwing_Copy value(2);
473 EXPECT_THROW(t.insert("b", value), std::runtime_error);
474 Throwing_Copy::should_throw = false;
475
476 EXPECT_EQ(t.size(), 1u);
477 EXPECT_FALSE(t.contains("b"));
478 ASSERT_NE(t.find("a"), nullptr);
479 EXPECT_EQ(t.find("a")->value, 1);
480}
481
483{
484 // Unlike FailedInsertCopyLeavesTreeUnchanged (which inserts "a" then "b",
485 // sharing no common prefix and so only exercising the "brand new leaf"
486 // path), "ab" then "ac" share a one-character common prefix with "ab"'s
487 // full edge label, forcing insert_impl's edge-SPLIT branch -- the one
488 // most at risk of leaving the tree corrupted (a detached-then-lost
489 // subtree, or a dangling nullptr child slot) if T's constructor throws
490 // partway through.
492 ASSERT_TRUE(t.insert("ab", Throwing_Copy(1)));
493 ASSERT_EQ(t.size(), 1u);
494 ASSERT_TRUE(t.verify());
495
496 Throwing_Copy::should_throw = true;
497 const Throwing_Copy value(2);
498 EXPECT_THROW(t.insert("ac", value), std::runtime_error);
499 Throwing_Copy::should_throw = false;
500
501 // The tree must be exactly as it was before the failed insert: "ab"
502 // still present with its original value, "ac" absent, size unchanged,
503 // and no structural corruption (no lost subtree, no dangling child).
504 EXPECT_EQ(t.size(), 1u);
505 EXPECT_FALSE(t.contains("ac"));
506 ASSERT_NE(t.find("ab"), nullptr);
507 EXPECT_EQ(t.find("ab")->value, 1);
508 EXPECT_TRUE(t.verify());
509}
510
512{
513 // Same idea, but for the OTHER split sub-case: the new key ends exactly
514 // at the split point ("ab" then "a"), so the throwing emplace() happens
515 // on `split->value` directly rather than on a new leaf under `split`.
517 ASSERT_TRUE(t.insert("ab", Throwing_Copy(1)));
518 ASSERT_EQ(t.size(), 1u);
519
520 Throwing_Copy::should_throw = true;
521 const Throwing_Copy value(2);
522 EXPECT_THROW(t.insert("a", value), std::runtime_error);
523 Throwing_Copy::should_throw = false;
524
525 EXPECT_EQ(t.size(), 1u);
526 EXPECT_FALSE(t.contains("a"));
527 ASSERT_NE(t.find("ab"), nullptr);
528 EXPECT_EQ(t.find("ab")->value, 1);
529 EXPECT_TRUE(t.verify());
530}
531
532// --- Randomized parity tests -----------------------------------------------
533
535{
536 std::mt19937 rng(0xC0FFEEu);
537 std::uniform_int_distribution<int> op_dist(0, 2); // insert / erase / find
538 std::uniform_int_distribution<int> key_len_dist(0, 4);
539 std::uniform_int_distribution<int> char_dist('a', 'd'); // small alphabet
540 // to force lots
541 // of shared
542 // prefixes.
543 std::uniform_int_distribution<int> value_dist(0, 1'000'000);
544
546 std::map<std::string, int> reference;
547
548 const auto random_key = [&]
549 {
550 std::string k;
551 const int len = key_len_dist(rng);
552 for (int i = 0; i < len; ++i)
553 k.push_back(static_cast<char>(char_dist(rng)));
554 return k;
555 };
556
557 for (int iter = 0; iter < 20000; ++iter)
558 {
559 const int op = op_dist(rng);
560 const std::string key = random_key();
561
562 if (op == 0)
563 {
564 const int value = value_dist(rng);
565 const bool subject_inserted = subject.insert(key, value);
566 const bool reference_inserted =
567 reference.emplace(key, value).second;
569 << "insert(\"" << key << "\") disagreement at iter " << iter;
570 }
571 else if (op == 1)
572 {
573 const bool subject_erased = subject.erase(key);
574 const bool reference_erased = reference.erase(key) > 0;
576 << "erase(\"" << key << "\") disagreement at iter " << iter;
577 }
578 else
579 {
580 const int * subject_value = subject.find(key);
581 const auto reference_it = reference.find(key);
582 ASSERT_EQ(subject_value != nullptr, reference_it != reference.end())
583 << "find(\"" << key << "\") presence disagreement at iter "
584 << iter;
585 if (subject_value != nullptr)
587 << "find(\"" << key << "\") value disagreement at iter "
588 << iter;
589 }
590
591 ASSERT_EQ(subject.size(), reference.size())
592 << "size disagreement at iter " << iter;
593 ASSERT_TRUE(subject.verify())
594 << "structural invariant violation at iter " << iter;
595 }
596
597 // Full final-state cross-check.
598 ASSERT_EQ(subject.size(), reference.size());
599 for (const auto & [key, value] : reference)
600 {
601 const int * found = subject.find(key);
602 ASSERT_NE(found, nullptr) << "missing key in final check: " << key;
603 EXPECT_EQ(*found, value) << "value mismatch in final check: " << key;
604 }
605}
606
608{
609 std::mt19937 rng(0xBADC0FFEu);
610 std::uniform_int_distribution<int> key_len_dist(1, 5);
611 std::uniform_int_distribution<int> char_dist('a', 'c'); // tiny alphabet:
612 // forces heavy
613 // prefix sharing.
614
616 std::set<std::string> reference;
617
618 const auto random_string = [&](const int len)
619 {
620 std::string s;
621 for (int i = 0; i < len; ++i)
622 s.push_back(static_cast<char>(char_dist(rng)));
623 return s;
624 };
625
626 for (int i = 0; i < 500; ++i)
627 {
628 const std::string key = random_string(key_len_dist(rng));
629 subject.insert(key, i);
630 reference.insert(key);
631 ASSERT_TRUE(subject.verify())
632 << "structural invariant violation after inserting: " << key;
633 }
634
635 for (int i = 0; i < 200; ++i)
636 {
637 const std::string prefix = random_string(key_len_dist(rng));
638
639 std::vector<std::string> expected;
640 for (const auto & key : reference)
641 if (key.compare(0, prefix.size(), prefix) == 0)
642 expected.push_back(key);
643
644 const auto actual = sorted(to_vector(subject.keys_with_prefix(prefix)));
646 << "prefix query disagreement for prefix: \"" << prefix << "\"";
647 }
648}
649
650// --- Cross-implementation parity against Aleph::Prefix_Tree ---------------
651//
652// `Prefix_Tree` (prefix-tree.H) is Aleph's existing uncompressed
653// character-per-edge trie. It has no `erase`, so this cannot cover
654// removal, but insert/contains/prefix-query parity between two
655// *independently implemented* tries is a materially stronger signal than
656// parity against std::map alone: it cross-checks RadixTree's edge-
657// splitting logic against a conceptually different (uncompressed)
658// implementation of the same abstract structure, per the plan's own
659// "compare prefix-tree.H vs RadixTree" documentation requirement.
660
662{
663 std::mt19937 rng(0xFACEFEEDu);
664 std::uniform_int_distribution<int> word_len_dist(0, 6);
665 std::uniform_int_distribution<int> char_dist('a', 'e'); // small alphabet:
666 // forces heavy
667 // prefix sharing,
668 // the scenario
669 // that most
670 // exercises
671 // RadixTree's
672 // edge-splitting
673 // and Prefix_
674 // Tree's node
675 // chains alike.
676
677 RadixTree<char> subject; // value type is irrelevant; used purely as a set
678 Prefix_Tree reference;
679
680 const auto random_word = [&]
681 {
682 std::string w;
683 const int len = word_len_dist(rng);
684 for (int i = 0; i < len; ++i)
685 w.push_back(static_cast<char>(char_dist(rng)));
686 return w;
687 };
688
689 for (int iter = 0; iter < 3000; ++iter)
690 {
691 const std::string word = random_word();
692 const bool subject_inserted = subject.insert(word, 'x');
693 const bool reference_inserted = reference.insert_word(word);
695 << "insert(\"" << word << "\") disagreement at iter " << iter;
696 ASSERT_EQ(subject.size(), reference.count())
697 << "count disagreement at iter " << iter;
698 ASSERT_TRUE(subject.verify())
699 << "structural invariant violation at iter " << iter;
700 }
701
702 // Presence parity, including words that were never inserted.
703 for (int i = 0; i < 1000; ++i)
704 {
705 const std::string word = random_word();
706 ASSERT_EQ(subject.contains(word), reference.contains(word))
707 << "contains(\"" << word << "\") disagreement for: " << word;
708 }
709
710 // Full word-set parity (RadixTree's "all keys" is keys_with_prefix("")).
711 EXPECT_EQ(sorted(to_vector(subject.keys_with_prefix(""))),
712 sorted(to_vector(reference.words())));
713
714 // Prefix-query parity across many random prefixes (including prefixes
715 // that were never inserted as standalone words).
716 for (int i = 0; i < 300; ++i)
717 {
718 const std::string prefix = random_word();
719 const auto subject_matches = sorted(to_vector(subject.keys_with_prefix(prefix)));
720 const auto reference_matches =
723 << "prefix query disagreement for prefix: \"" << prefix << "\"";
724 }
725}
static string random_string(std::mt19937 &rng, size_t len)
long double w
Definition btreepic.C:153
size_t size_t int32_t value
Definition ca-c-api.h:116
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:138
Owning prefix tree wrapper.
bool insert_word(const std::string &word)
Insert a word into the tree.
DynArray< std::string > words(const size_t max_word_length=2048) const
Get all words stored in the tree.
DynArray< std::string > words_with_prefix(const std::string &prefix, const size_t max_word_length=2048) const
Get all words with a given prefix.
size_t count() const noexcept
Count the words stored in the tree.
bool contains(const std::string &word) const noexcept
Check whether a word exists in the tree.
Compressed prefix tree mapping std::basic_string<Char> keys to values of type T.
bool is_empty() const noexcept
Check whether the tree holds no keys.
const T * find(const Key &key) const noexcept
Look up key.
bool insert(const Key &key, const T &value)
Insert key with a copy of value, only if key is absent.
size_t size() const noexcept
Return the number of keys currently stored.
bool verify() const
Recursively verify the tree's structural invariants.
std::optional< Key > longest_prefix(const Key &key) const
Find the longest stored key that is a prefix of key.
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.
Array< Key > keys_with_prefix(const Key &prefix) const
Return every stored key that starts with prefix.
bool erase(const Key &key)
Remove key if present, merging any resulting single-child, valueless node back into a compressed edge...
Minimal std::expected-style result type for C++20.
void for_each(Operation &operation)
Traverse all the container and performs an operation on each element.
Definition ah-dry.H:796
#define TEST(name)
static mt19937 rng
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
static void prefix(Node *root, DynList< Node * > &acc)
std::vector< typename C::Item_Type > to_vector(const C &c)
Convert a container to a std::vector.
Definition ah-convert.H:238
Trie (prefix tree) implementation.
static int * k
Compressed prefix tree (Aleph::RadixTree) mapping string keys to values.