Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
prefix_tree_test.cc
Go to the documentation of this file.
1
2/*
3 Aleph_w
4
5 Data structures & Algorithms
6 version 2.0.0b
7 https://github.com/lrleon/Aleph-w
8
9 This file is part of Aleph-w library
10
11 Copyright (c) 2002-2026 Leandro Rabindranath Leon
12
13 Permission is hereby granted, free of charge, to any person obtaining a copy
14 of this software and associated documentation files (the "Software"), to deal
15 in the Software without restriction, including without limitation the rights
16 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 copies of the Software, and to permit persons to whom the Software is
18 furnished to do so, subject to the following conditions:
19
20 The above copyright notice and this permission notice shall be included in all
21 copies or substantial portions of the Software.
22
23 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 SOFTWARE.
30*/
31
32
51#include <gtest/gtest.h>
52#include <prefix-tree.H>
53#include <tpl_radix_tree.H>
54#include <algorithm>
55#include <atomic>
56#include <cstddef>
57#include <cstdlib>
58#include <map>
59#include <memory>
60#include <new>
61#include <tuple>
62#include <type_traits>
63#include <utility>
64#include <vector>
65
66// ThreadSanitizer ships its own strong global operator new/delete
67// replacements (tsan_new_delete.cpp.o). Defining our own below -- needed to
68// inject allocation failures for the tests exercising prefix-tree.H's
69// exception-safety paths -- collides with those at link time ("multiple
70// definition") under -fsanitize=thread. ASan/UBSan/plain Debug builds are
71// unaffected; only skip the override (and the tests relying on it) for TSan.
72#if defined(__SANITIZE_THREAD__)
73# define ALEPH_PREFIX_TREE_TEST_UNDER_TSAN 1
74#elif defined(__has_feature)
75# if __has_feature(thread_sanitizer)
76# define ALEPH_PREFIX_TREE_TEST_UNDER_TSAN 1
77# endif
78#endif
79#ifndef ALEPH_PREFIX_TREE_TEST_UNDER_TSAN
80# define ALEPH_PREFIX_TREE_TEST_UNDER_TSAN 0
81#endif
82
83namespace
84{
85 std::atomic<int> allocation_countdown{-1};
86 std::atomic<bool> track_allocations{false};
87 std::atomic<int> tracked_balance{0};
88
89#if !ALEPH_PREFIX_TREE_TEST_UNDER_TSAN
90 bool should_fail_allocation() noexcept
91 {
92 int remaining = allocation_countdown.load(std::memory_order_relaxed);
93 while (remaining >= 0)
94 {
95 if (remaining == 0)
96 return true;
97 if (allocation_countdown.compare_exchange_weak(
98 remaining, remaining - 1, std::memory_order_relaxed))
99 return false;
100 }
101 return false;
102 }
103
104
105 void * allocate_or_throw(std::size_t size)
106 {
108 throw std::bad_alloc();
109
110 if (size == 0)
111 size = 1;
112
113 void *ptr = std::malloc(size);
114 if (ptr == nullptr)
115 throw std::bad_alloc();
116
117 if (track_allocations.load(std::memory_order_relaxed))
118 tracked_balance.fetch_add(1, std::memory_order_relaxed);
119
120 return ptr;
121 }
122
123 void release_allocation(void *ptr) noexcept
124 {
125 if (ptr == nullptr)
126 return;
127
128 if (track_allocations.load(std::memory_order_relaxed))
129 tracked_balance.fetch_sub(1, std::memory_order_relaxed);
130
131 std::free(ptr);
132 }
133#endif // !ALEPH_PREFIX_TREE_TEST_UNDER_TSAN
134
135 class AllocationFailureScope
136 {
137 public:
138 explicit AllocationFailureScope(const int successful_allocations_before_failure)
139 {
140 tracked_balance.store(0, std::memory_order_relaxed);
141 track_allocations.store(true, std::memory_order_relaxed);
143 std::memory_order_relaxed);
144 }
145
147 {
148 allocation_countdown.store(-1, std::memory_order_relaxed);
149 track_allocations.store(false, std::memory_order_relaxed);
150 }
151
152 [[nodiscard]] static int balance() noexcept
153 {
154 return tracked_balance.load(std::memory_order_relaxed);
155 }
156 };
157}
158
159#if !ALEPH_PREFIX_TREE_TEST_UNDER_TSAN
160
161void *operator new(std::size_t size)
162{
163 return allocate_or_throw(size);
164}
165
166void *operator new[](std::size_t size)
167{
168 return allocate_or_throw(size);
169}
170
171void *operator new(std::size_t size, const std::nothrow_t &) noexcept
172{
173 try
174 {
175 return allocate_or_throw(size);
176 }
177 catch (...)
178 {
179 return nullptr;
180 }
181}
182
183void *operator new[](std::size_t size, const std::nothrow_t &) noexcept
184{
185 try
186 {
187 return allocate_or_throw(size);
188 }
189 catch (...)
190 {
191 return nullptr;
192 }
193}
194
195void operator delete(void *ptr) noexcept
196{
198}
199
200void operator delete[](void *ptr) noexcept
201{
203}
204
205void operator delete(void *ptr, std::size_t) noexcept
206{
208}
209
210void operator delete[](void *ptr, std::size_t) noexcept
211{
213}
214
215void operator delete(void *ptr, const std::nothrow_t &) noexcept
216{
218}
219
220void operator delete[](void *ptr, const std::nothrow_t &) noexcept
221{
223}
224
225#endif // !ALEPH_PREFIX_TREE_TEST_UNDER_TSAN
226
227using namespace Aleph;
228
229static_assert(std::is_same_v<decltype(std::declval<Cnode &>().search_child('a')),
230 Cnode *>);
231static_assert(std::is_same_v<decltype(std::declval<const Cnode &>().search_child('a')),
232 const Cnode *>);
233static_assert(std::is_same_v<decltype(std::declval<Cnode &>().greater_child('a')),
234 Cnode *>);
235static_assert(std::is_same_v<decltype(std::declval<const Cnode &>().greater_child('a')),
236 const Cnode *>);
237static_assert(std::is_same_v<decltype(std::declval<Cnode &>().search_prefix("a")),
238 std::tuple<Cnode *, const char *>>);
239static_assert(std::is_same_v<decltype(std::declval<const Cnode &>().search_prefix("a")),
240 std::tuple<const Cnode *, const char *>>);
241static_assert(std::is_same_v<decltype(std::declval<Prefix_Tree &>().root()),
242 Cnode *>);
243static_assert(std::is_same_v<decltype(std::declval<const Prefix_Tree &>().root()),
244 const Cnode *>);
245static_assert(std::is_move_constructible_v<Prefix_Tree_Map<int>>);
246static_assert(std::is_copy_constructible_v<Prefix_Tree_Map<int>>);
247static_assert(std::is_move_constructible_v<Prefix_Tree_Map<std::unique_ptr<int>>>);
248static_assert(not std::is_copy_constructible_v<Prefix_Tree_Map<std::unique_ptr<int>>>);
249
250static std::vector<std::string> to_sorted_vector(const DynArray<std::string> & words)
251{
252 std::vector<std::string> ret;
253 words.for_each([&ret](const std::string & w) { ret.push_back(w); });
254 std::sort(ret.begin(), ret.end());
255 return ret;
256}
257
258static std::vector<std::string> to_sorted_vector(const Array<std::string> & words)
259{
260 std::vector<std::string> ret;
261 ret.reserve(words.size());
262 for (const auto & w : words)
263 ret.push_back(w);
264 std::sort(ret.begin(), ret.end());
265 return ret;
266}
267
268//==============================================================================
269// Test Fixture
270//==============================================================================
271
272class PrefixTreeTest : public ::testing::Test
273{
274protected:
275 Cnode * root = nullptr;
276
277 void SetUp() override
278 {
279 root = new Cnode('\0'); // Root with sentinel character
280 }
281
282 void TearDown() override
283 {
284 if (root)
285 {
286 root->destroy();
287 delete root;
288 }
289 }
290};
291
292//==============================================================================
293// Basic Node Tests
294//==============================================================================
295
297{
298 Cnode node('a');
299 EXPECT_EQ(node.symbol(), 'a');
300}
301
303{
304 Cnode node('x');
305 EXPECT_EQ(node.symbol(), 'x');
306
307 Cnode node2('$');
308 EXPECT_EQ(node2.symbol(), '$');
309}
310
312{
313 Cnode node('a');
314 EXPECT_TRUE(node.children().is_empty());
315}
316
322
324{
325 Cnode node('a');
327
328 node.mark_end_word();
329 EXPECT_TRUE(node.is_end_word());
330 EXPECT_TRUE(node.children().is_empty());
331}
332
333//==============================================================================
334// Child Operations Tests
335//==============================================================================
336
338{
339 EXPECT_EQ(root->search_child('a'), nullptr);
340}
341
343{
344 auto * child = new Cnode('a');
345 root->insert_child(child);
346
347 EXPECT_EQ(root->search_child('a'), child);
348 EXPECT_EQ(root->search_child('b'), nullptr);
349}
350
352{
353 root->insert_child(new Cnode('c'));
354 root->insert_child(new Cnode('a'));
355 root->insert_child(new Cnode('b'));
356
357 auto kids = root->children();
358 ASSERT_EQ(kids.size(), 3);
359
360 // Verify sorted order
361 auto it = kids.get_it();
362 EXPECT_EQ(it.get_curr()->symbol(), 'a');
363 it.next();
364 EXPECT_EQ(it.get_curr()->symbol(), 'b');
365 it.next();
366 EXPECT_EQ(it.get_curr()->symbol(), 'c');
367}
368
370{
371 root->insert_child(new Cnode('a'));
372 root->insert_child(new Cnode('c'));
373 root->insert_child(new Cnode('e'));
374
375 EXPECT_EQ(root->greater_child('b')->symbol(), 'c');
376 EXPECT_EQ(root->greater_child('d')->symbol(), 'e');
377 EXPECT_EQ(root->greater_child('e'), nullptr);
378}
379
380//==============================================================================
381// Word Insertion Tests
382//==============================================================================
383
385{
386 EXPECT_TRUE(root->insert_word("hello"));
387 EXPECT_TRUE(root->contains("hello"));
388}
389
391{
392 EXPECT_TRUE(root->insert_word("hello"));
393 EXPECT_FALSE(root->insert_word("hello")); // Already exists
394}
395
397{
398 EXPECT_TRUE(root->insert_word("hello"));
399 EXPECT_TRUE(root->insert_word("help"));
400 EXPECT_TRUE(root->insert_word("world"));
401
402 EXPECT_TRUE(root->contains("hello"));
403 EXPECT_TRUE(root->contains("help"));
404 EXPECT_TRUE(root->contains("world"));
405}
406
408{
409 EXPECT_TRUE(root->insert_word("test"));
410 EXPECT_TRUE(root->insert_word("testing"));
411 EXPECT_TRUE(root->insert_word("tester"));
412
413 EXPECT_TRUE(root->contains("test"));
414 EXPECT_TRUE(root->contains("testing"));
415 EXPECT_TRUE(root->contains("tester"));
416}
417
419{
420 EXPECT_TRUE(root->insert_word(""));
421 EXPECT_TRUE(root->contains(""));
422 EXPECT_FALSE(root->insert_word("")); // Already exists
423}
424
426{
427 EXPECT_TRUE(root->insert_word("a"));
428 EXPECT_TRUE(root->contains("a"));
429}
430
432{
433 EXPECT_TRUE(root->insert_word("a"));
434 EXPECT_TRUE(root->insert_word("a!"));
435
436 EXPECT_TRUE(root->contains("a"));
437 EXPECT_TRUE(root->contains("a!"));
438 EXPECT_EQ(root->count(), 2);
439
440 EXPECT_EQ(to_sorted_vector(root->words()), (std::vector<std::string>{"a", "a!"}));
441 EXPECT_EQ(to_sorted_vector(root->words_with_prefix("a")),
442 (std::vector<std::string>{"a", "a!"}));
443}
444
446{
447#if ALEPH_PREFIX_TREE_TEST_UNDER_TSAN
448 GTEST_SKIP() << "AllocationFailureScope needs a custom global operator "
449 "new/delete, which conflicts with TSan's own at link time.";
450#endif
451 EXPECT_TRUE(root->insert_word("mango"));
452 const auto before = to_sorted_vector(root->words());
453
454 bool threw = false;
455 int balance = 0;
456 {
457 AllocationFailureScope fail_after_one_node(2);
458 try
459 {
460 root->insert_word("abc");
461 }
462 catch (const std::bad_alloc &)
463 {
464 threw = true;
465 }
466 balance = fail_after_one_node.balance();
467 }
468
470 EXPECT_EQ(balance, 0);
472 EXPECT_TRUE(root->contains("mango"));
473 EXPECT_FALSE(root->contains("a"));
474 EXPECT_FALSE(root->contains("ab"));
475 EXPECT_FALSE(root->contains("abc"));
476 EXPECT_EQ(root->count(), 1);
477}
478
480{
481 EXPECT_TRUE(root->insert_word("testing"));
482
483 EXPECT_TRUE(root->contains("testing"));
484 EXPECT_FALSE(root->contains("test"));
485 EXPECT_FALSE(root->contains("tes"));
486 EXPECT_FALSE(root->contains("te"));
487 EXPECT_FALSE(root->contains("t"));
488}
489
491{
492 EXPECT_TRUE(root->insert_word("test"));
493 EXPECT_TRUE(root->insert_word("testing"));
494
495 EXPECT_TRUE(root->contains("test"));
496 EXPECT_TRUE(root->contains("testing"));
497}
498
500{
501 EXPECT_TRUE(root->insert_word("testing"));
502 EXPECT_TRUE(root->insert_word("test"));
503
504 EXPECT_TRUE(root->contains("test"));
505 EXPECT_TRUE(root->contains("testing"));
506}
507
508//==============================================================================
509// Search Tests
510//==============================================================================
511
513{
514 root->insert_word("hello");
515
516 EXPECT_EQ(root->search_word("world"), nullptr);
517 EXPECT_EQ(root->search_word("hel"), nullptr);
518}
519
521{
522 root->insert_word("hello");
523
524 auto result = root->search_word("hello");
525 ASSERT_NE(result, nullptr);
526 EXPECT_EQ(result->symbol(), 'o');
527}
528
530{
531 root->insert_word("hello");
532
533 EXPECT_FALSE(root->contains("world"));
534 EXPECT_FALSE(root->contains("helloworld"));
535 EXPECT_FALSE(root->contains("hell"));
536}
537
538//==============================================================================
539// Prefix Search Tests
540//==============================================================================
541
543{
544 auto [node, remaining] = root->search_prefix("");
545
546 EXPECT_EQ(node, root);
547 EXPECT_STREQ(remaining, "");
548}
549
551{
552 root->insert_word("hello");
553
554 auto [node, remaining] = root->search_prefix("hel");
555
556 EXPECT_EQ(node->symbol(), 'l');
557 EXPECT_STREQ(remaining, "");
558}
559
561{
562 root->insert_word("hello");
563
564 auto [node, remaining] = root->search_prefix("helping");
565
566 EXPECT_EQ(node->symbol(), 'l');
567 EXPECT_STREQ(remaining, "ping");
568}
569
571{
572 root->insert_word("hello");
573
574 auto [node, remaining] = root->search_prefix("world");
575
576 EXPECT_EQ(node, root);
577 EXPECT_STREQ(remaining, "world");
578}
579
580//==============================================================================
581// Words Extraction Tests
582//==============================================================================
583
585{
586 auto words = root->words();
587 EXPECT_TRUE(words.is_empty());
588}
589
591{
592 root->insert_word("hello");
593
594 auto words = root->words();
595 ASSERT_EQ(words.size(), 1);
596 EXPECT_EQ(std::string(words[0]), "hello");
597}
598
600{
601 root->insert_word("hello");
602 root->insert_word("help");
603 root->insert_word("world");
604
605 auto words = root->words();
606 ASSERT_EQ(words.size(), 3);
607
608 // Convert to vector for easier checking
609 std::vector<std::string> v;
610 words.for_each([&v](const std::string& w) { v.push_back(w); });
611 std::sort(v.begin(), v.end());
612
613 EXPECT_EQ(v[0], std::string("hello"));
614 EXPECT_EQ(v[1], std::string("help"));
615 EXPECT_EQ(v[2], std::string("world"));
616}
617
619{
620 root->insert_word("a");
621 root->insert_word("ab");
622 root->insert_word("abc");
623 root->insert_word("abcd");
624
625 auto words = root->words();
626 ASSERT_EQ(words.size(), 4);
627}
628
630{
631 EXPECT_TRUE(root->insert_word("$"));
632 EXPECT_TRUE(root->insert_word("a$"));
633 EXPECT_TRUE(root->insert_word("a$b"));
634
635 EXPECT_TRUE(root->contains("$"));
636 EXPECT_TRUE(root->contains("a$"));
637 EXPECT_TRUE(root->contains("a$b"));
638
640 (std::vector<std::string>{"$", "a$", "a$b"}));
641}
642
644{
645 EXPECT_TRUE(root->insert_word(""));
646 EXPECT_TRUE(root->insert_word("!"));
647
648 EXPECT_TRUE(root->contains(""));
649 EXPECT_TRUE(root->contains("!"));
651 (std::vector<std::string>{"", "!"}));
652}
653
654//==============================================================================
655// Clone Tests
656//==============================================================================
657
659{
660 Cnode * cloned = root->clone();
661
662 EXPECT_EQ(cloned->symbol(), root->symbol());
663 EXPECT_TRUE(cloned->children().is_empty());
664
665 cloned->destroy();
666 delete cloned;
667}
668
670{
671 root->insert_word("hello");
672 root->insert_word("help");
673 root->insert_word("world");
674
675 Cnode * cloned = root->clone();
676
677 // Verify cloned tree has same words
678 EXPECT_TRUE(cloned->contains("hello"));
679 EXPECT_TRUE(cloned->contains("help"));
680 EXPECT_TRUE(cloned->contains("world"));
681
682 // Verify independence - modify original
683 root->insert_word("test");
684 EXPECT_TRUE(root->contains("test"));
685 EXPECT_FALSE(cloned->contains("test"));
686
687 cloned->destroy();
688 delete cloned;
689}
690
692{
693 root->insert_word("");
694 root->insert_word("$");
695 root->insert_word("a!");
696 root->insert_word("a");
697
698 Cnode * cloned = root->clone();
699
700 EXPECT_TRUE(cloned->contains(""));
701 EXPECT_TRUE(cloned->contains("$"));
702 EXPECT_TRUE(cloned->contains("a!"));
703 EXPECT_TRUE(cloned->contains("a"));
705 (std::vector<std::string>{"", "$", "a", "a!"}));
706
707 cloned->destroy();
708 delete cloned;
709}
710
712{
713#if ALEPH_PREFIX_TREE_TEST_UNDER_TSAN
714 GTEST_SKIP() << "AllocationFailureScope needs a custom global operator "
715 "new/delete, which conflicts with TSan's own at link time.";
716#endif
717 root->insert_word("alpha");
718 root->insert_word("beta");
719 root->insert_word("$");
720 const auto before = to_sorted_vector(root->words());
721
722 bool threw = false;
723 int balance = 0;
724 {
725 AllocationFailureScope fail_after_two_nodes(2);
726 try
727 {
728 Cnode *cloned = root->clone();
729 cloned->destroy();
730 delete cloned;
731 }
732 catch (const std::bad_alloc &)
733 {
734 threw = true;
735 }
736 balance = fail_after_two_nodes.balance();
737 }
738
740 EXPECT_EQ(balance, 0);
742 EXPECT_TRUE(root->contains("alpha"));
743 EXPECT_TRUE(root->contains("beta"));
744 EXPECT_TRUE(root->contains("$"));
745}
746
747//==============================================================================
748// To String Tests
749//==============================================================================
750
752{
753 std::string str = root->to_str();
754 // Root has null character
755 EXPECT_FALSE(str.empty());
756}
757
759{
760 root->insert_word("ab");
761 std::string str = root->to_str();
762
763 // Should contain 'a' and 'b' somewhere
764 EXPECT_NE(str.find('a'), std::string::npos);
765 EXPECT_NE(str.find('b'), std::string::npos);
766}
767
768//==============================================================================
769// Edge Cases
770//==============================================================================
771
773{
774 std::string longWord(20, 'a'); // Keep short to avoid stack overflow in destroy_tree
775 EXPECT_TRUE(root->insert_word(longWord));
776 EXPECT_TRUE(root->contains(longWord));
777}
778
780{
781 const int N = 50; // Reduced to avoid stack issues
782 for (int i = 0; i < N; ++i)
783 root->insert_word("w" + std::to_string(i));
784
785 for (int i = 0; i < N; ++i)
786 EXPECT_TRUE(root->contains("w" + std::to_string(i)));
787
788 auto words = root->words();
789 EXPECT_EQ(words.size(), N);
790}
791
793{
794 EXPECT_TRUE(root->insert_word("hello-world"));
795 EXPECT_TRUE(root->insert_word("test_case"));
796 EXPECT_TRUE(root->insert_word("foo.bar"));
797
798 EXPECT_TRUE(root->contains("hello-world"));
799 EXPECT_TRUE(root->contains("test_case"));
800 EXPECT_TRUE(root->contains("foo.bar"));
801}
802
804{
805 EXPECT_TRUE(root->insert_word("123"));
806 EXPECT_TRUE(root->insert_word("456"));
807
808 EXPECT_TRUE(root->contains("123"));
809 EXPECT_TRUE(root->contains("456"));
810 EXPECT_FALSE(root->contains("789"));
811}
812
813//==============================================================================
814// Count Tests
815//==============================================================================
816
818{
819 EXPECT_EQ(root->count(), 0);
820}
821
823{
824 root->insert_word("hello");
825 EXPECT_EQ(root->count(), 1);
826}
827
829{
830 root->insert_word("hello");
831 root->insert_word("help");
832 root->insert_word("world");
833 EXPECT_EQ(root->count(), 3);
834}
835
837{
838 root->insert_word("a");
839 root->insert_word("ab");
840 root->insert_word("abc");
841 EXPECT_EQ(root->count(), 3);
842}
843
844//==============================================================================
845// Words With Prefix Tests
846//==============================================================================
847
849{
850 root->insert_word("hello");
851 auto words = root->words_with_prefix("xyz");
852 EXPECT_TRUE(words.is_empty());
853}
854
856{
857 root->insert_word("hello");
858 root->insert_word("help");
859 root->insert_word("helicopter");
860 root->insert_word("world");
861
862 auto words = root->words_with_prefix("hel");
863 EXPECT_EQ(words.size(), 3);
864}
865
867{
868 root->insert_word("test");
869 root->insert_word("testing");
870 root->insert_word("tester");
871
872 const auto words = root->words_with_prefix("test");
873 EXPECT_EQ(words.size(), 3); // test, testing, tester
874}
875
877{
878 root->insert_word("apple");
879 root->insert_word("application");
880
881 auto words = root->words_with_prefix("ban");
882 EXPECT_TRUE(words.is_empty());
883}
884
886{
887 root->insert_word("");
888 root->insert_word("hello");
889 root->insert_word("help");
890 root->insert_word("world");
891
892 EXPECT_EQ(to_sorted_vector(root->words_with_prefix("")),
893 to_sorted_vector(root->words()));
894}
895
897{
898 // Use a separate test without the fixture to avoid double-free
899 auto * tree = new Cnode('\0');
900 tree->insert_word("hi");
901 tree->insert_word("bye");
902
903 EXPECT_TRUE(tree->contains("hi"));
904 EXPECT_TRUE(tree->contains("bye"));
905
906 // destroy() calls destroy_tree on children which deletes them
907 tree->destroy();
908 delete tree;
909 // If we get here without crashing, destroy worked
910}
911
912//==============================================================================
913// Owning Wrapper Tests
914//==============================================================================
915
917{
918 Prefix_Tree tree;
919
920 EXPECT_TRUE(tree.insert_word(""));
921 EXPECT_TRUE(tree.insert_word("alpha"));
922 EXPECT_TRUE(tree.insert_word("alphabet"));
923 EXPECT_FALSE(tree.insert_word("alpha"));
924
925 EXPECT_TRUE(tree.contains(""));
926 EXPECT_TRUE(tree.contains("alpha"));
927 EXPECT_TRUE(tree.contains("alphabet"));
928 EXPECT_FALSE(tree.contains("alpine"));
929 EXPECT_EQ(tree.count(), 3);
930 EXPECT_EQ(tree.size(), 3);
932 (std::vector<std::string>{"", "alpha", "alphabet"}));
934 (std::vector<std::string>{"alpha", "alphabet"}));
935}
936
938{
939 Prefix_Tree tree;
940
941 EXPECT_EQ(tree.count(), 0);
942 EXPECT_EQ(tree.size(), 0);
943 EXPECT_TRUE(tree.insert_word("alpha"));
944 EXPECT_TRUE(tree.insert_word("beta"));
945 EXPECT_FALSE(tree.insert_word("alpha"));
946 EXPECT_EQ(tree.count(), 2);
947 EXPECT_EQ(tree.size(), 2);
948}
949
951{
952 Prefix_Tree tree;
953 ASSERT_TRUE(tree.insert_word("alpha"));
954 EXPECT_EQ(tree.count(), 1);
955
956 Cnode *root = tree.root();
957 ASSERT_NE(root, nullptr);
958 EXPECT_TRUE(root->insert_word("beta"));
959 EXPECT_TRUE(root->insert_word("gamma"));
960 EXPECT_TRUE(tree.contains("beta"));
961 EXPECT_TRUE(tree.contains("gamma"));
962
963 EXPECT_EQ(tree.count(), 3);
964 EXPECT_EQ(tree.size(), 3);
965 EXPECT_TRUE(tree.insert_word("delta"));
966 EXPECT_EQ(tree.count(), 4);
967 EXPECT_TRUE(root->insert_word("epsilon"));
968 EXPECT_EQ(tree.count(), 5);
969 EXPECT_EQ(tree.size(), 5);
970}
971
973{
974#if ALEPH_PREFIX_TREE_TEST_UNDER_TSAN
975 GTEST_SKIP() << "AllocationFailureScope needs a custom global operator "
976 "new/delete, which conflicts with TSan's own at link time.";
977#endif
978 size_t count = 0;
979 int balance = 0;
980 {
981 AllocationFailureScope track_only(-1);
982 {
983 Prefix_Tree tree;
984 tree.insert_word("alpha");
985 tree.insert_word("beta");
986 count = tree.count();
987 }
988 balance = track_only.balance();
989 }
990
991 EXPECT_EQ(count, 2);
992 EXPECT_EQ(balance, 0);
993}
994
996{
997 Prefix_Tree tree;
998 const Prefix_Tree &const_tree = tree;
999
1000 ASSERT_NE(tree.root(), nullptr);
1001 EXPECT_EQ(tree.root()->symbol(), '\0');
1002 EXPECT_EQ(const_tree.root(), tree.root());
1003
1004 EXPECT_TRUE(tree.root()->insert_word("raw"));
1005 EXPECT_TRUE(tree.contains("raw"));
1006}
1007
1009{
1010 Prefix_Tree tree;
1011 tree.insert_word("alpha");
1012 tree.insert_word("$");
1013
1014 Prefix_Tree copy(tree);
1015 tree.insert_word("beta");
1016 copy.insert_word("gamma");
1017
1018 EXPECT_TRUE(copy.contains("alpha"));
1019 EXPECT_TRUE(copy.contains("$"));
1020 EXPECT_TRUE(copy.contains("gamma"));
1021 EXPECT_FALSE(copy.contains("beta"));
1022 EXPECT_TRUE(tree.contains("beta"));
1023 EXPECT_FALSE(tree.contains("gamma"));
1024 EXPECT_EQ(tree.count(), 3);
1025 EXPECT_EQ(copy.count(), 3);
1026}
1027
1029{
1030 Prefix_Tree source;
1031 source.insert_word("alpha");
1032 source.insert_word("beta");
1033
1034 Prefix_Tree target;
1035 target.insert_word("old");
1036
1037 target = source;
1038 source.insert_word("gamma");
1039 target.insert_word("delta");
1040
1041 EXPECT_TRUE(target.contains("alpha"));
1042 EXPECT_TRUE(target.contains("beta"));
1043 EXPECT_TRUE(target.contains("delta"));
1044 EXPECT_FALSE(target.contains("old"));
1045 EXPECT_FALSE(target.contains("gamma"));
1046 EXPECT_TRUE(source.contains("gamma"));
1047
1048 target = target;
1049 EXPECT_TRUE(target.contains("alpha"));
1050 EXPECT_TRUE(target.contains("delta"));
1051}
1052
1054{
1055 Prefix_Tree source;
1056 source.insert_word("alpha");
1057 source.insert_word("$");
1058
1059 Prefix_Tree moved(std::move(source));
1060
1061 EXPECT_TRUE(moved.contains("alpha"));
1062 EXPECT_TRUE(moved.contains("$"));
1063 EXPECT_EQ(moved.count(), 2);
1064
1065 EXPECT_EQ(source.count(), 0);
1066 EXPECT_FALSE(source.contains("alpha"));
1067 EXPECT_TRUE(source.words().is_empty());
1068 EXPECT_TRUE(source.words_with_prefix("a").is_empty());
1069 EXPECT_TRUE(source.insert_word("reused"));
1070 EXPECT_TRUE(source.contains("reused"));
1071 EXPECT_EQ(source.count(), 1);
1072}
1073
1075{
1076 Prefix_Tree source;
1077 source.insert_word("alpha");
1078 source.insert_word("beta");
1079
1080 Prefix_Tree target;
1081 target.insert_word("old");
1082
1083 target = std::move(source);
1084
1085 EXPECT_TRUE(target.contains("alpha"));
1086 EXPECT_TRUE(target.contains("beta"));
1087 EXPECT_FALSE(target.contains("old"));
1088 EXPECT_EQ(target.count(), 2);
1089
1090 EXPECT_EQ(source.count(), 0);
1091 EXPECT_FALSE(source.contains("alpha"));
1092 EXPECT_TRUE(source.words().is_empty());
1093 EXPECT_TRUE(source.words_with_prefix("a").is_empty());
1094 EXPECT_TRUE(source.insert_word("reused"));
1095 EXPECT_TRUE(source.contains("reused"));
1096 EXPECT_EQ(source.count(), 1);
1097}
1098
1100{
1101#if ALEPH_PREFIX_TREE_TEST_UNDER_TSAN
1102 GTEST_SKIP() << "AllocationFailureScope needs a custom global operator "
1103 "new/delete, which conflicts with TSan's own at link time.";
1104#endif
1105 Prefix_Tree source;
1106 source.insert_word("alpha");
1107 source.insert_word("beta");
1108 source.insert_word("$");
1109
1110 Prefix_Tree target;
1111 target.insert_word("old");
1112 const auto before = to_sorted_vector(target.words());
1113
1114 bool threw = false;
1115 int balance = 0;
1116 {
1117 AllocationFailureScope fail_after_two_nodes(2);
1118 try
1119 {
1120 target = source;
1121 }
1122 catch (const std::bad_alloc &)
1123 {
1124 threw = true;
1125 }
1126 balance = fail_after_two_nodes.balance();
1127 }
1128
1130 EXPECT_EQ(balance, 0);
1132 EXPECT_TRUE(target.contains("old"));
1133 EXPECT_FALSE(target.contains("alpha"));
1134 EXPECT_FALSE(target.contains("beta"));
1135 EXPECT_FALSE(target.contains("$"));
1136}
1137
1138//==============================================================================
1139// Prefix_Tree_Map Tests
1140//==============================================================================
1141
1143{
1145
1146 EXPECT_TRUE(map.is_empty());
1147 EXPECT_EQ(map.size(), 0);
1148 EXPECT_EQ(map.count(), 0);
1149 EXPECT_FALSE(map.contains("alpha"));
1150 EXPECT_EQ(map.find("alpha"), nullptr);
1151 EXPECT_TRUE(map.words().is_empty());
1152}
1153
1155{
1157
1158 EXPECT_TRUE(map.insert("", 0));
1159 EXPECT_TRUE(map.insert("alpha", 1));
1160 EXPECT_TRUE(map.insert("alphabet", 2));
1161
1162 ASSERT_NE(map.find(""), nullptr);
1163 ASSERT_NE(map.find("alpha"), nullptr);
1164 ASSERT_NE(map.find("alphabet"), nullptr);
1165 EXPECT_EQ(*map.find(""), 0);
1166 EXPECT_EQ(*map.find("alpha"), 1);
1167 EXPECT_EQ(*map.find("alphabet"), 2);
1168 EXPECT_TRUE(map.contains(""));
1169 EXPECT_TRUE(map.contains("alpha"));
1170 EXPECT_FALSE(map.contains("alpine"));
1171 EXPECT_EQ(map.size(), 3);
1172}
1173
1175{
1177
1178 EXPECT_TRUE(map.insert("alpha", 1));
1179 EXPECT_FALSE(map.insert("alpha", 99));
1180 ASSERT_NE(map.find("alpha"), nullptr);
1181 EXPECT_EQ(*map.find("alpha"), 1);
1182 EXPECT_EQ(map.size(), 1);
1183}
1184
1186{
1188 ASSERT_TRUE(map.insert("alpha", std::make_unique<int>(1)));
1189
1190 auto duplicate = std::make_unique<int>(2);
1191 EXPECT_FALSE(map.insert("alpha", std::move(duplicate)));
1192 ASSERT_NE(duplicate, nullptr);
1193 EXPECT_EQ(*duplicate, 2);
1194 ASSERT_NE(map.find("alpha"), nullptr);
1195 ASSERT_NE(*map.find("alpha"), nullptr);
1196 EXPECT_EQ(**map.find("alpha"), 1);
1197}
1198
1200{
1202
1203 map.insert_or_assign("alpha", "one");
1204 ASSERT_NE(map.find("alpha"), nullptr);
1205 EXPECT_EQ(*map.find("alpha"), "one");
1206
1207 map.insert_or_assign("alpha", "uno");
1208 ASSERT_NE(map.find("alpha"), nullptr);
1209 EXPECT_EQ(*map.find("alpha"), "uno");
1210
1211 map.insert_or_assign("beta", "two");
1212 ASSERT_NE(map.find("beta"), nullptr);
1213 EXPECT_EQ(*map.find("beta"), "two");
1214 EXPECT_EQ(map.size(), 2);
1215}
1216
1218{
1220 ASSERT_TRUE(map.insert("alpha", 1));
1221
1222 int *slot = map.find("alpha");
1223 ASSERT_NE(slot, nullptr);
1224 *slot = 42;
1225
1226 const auto &const_map = map;
1227 ASSERT_NE(const_map.find("alpha"), nullptr);
1228 EXPECT_EQ(*const_map.find("alpha"), 42);
1229}
1230
1232{
1234 ASSERT_TRUE(map.insert("alpha", 1));
1235 ASSERT_TRUE(map.insert("alphabet", 2));
1236
1237 EXPECT_TRUE(map.erase("alpha"));
1238 EXPECT_FALSE(map.contains("alpha"));
1239 EXPECT_TRUE(map.contains("alphabet"));
1240 EXPECT_FALSE(map.erase("alpha"));
1241 EXPECT_EQ(map.size(), 1);
1242
1243 EXPECT_TRUE(map.insert("alpha", 3));
1244 ASSERT_NE(map.find("alpha"), nullptr);
1245 EXPECT_EQ(*map.find("alpha"), 3);
1246 EXPECT_EQ(map.size(), 2);
1247}
1248
1250{
1252 ASSERT_TRUE(map.insert("", 0));
1253 ASSERT_TRUE(map.insert("app", 1));
1254 ASSERT_TRUE(map.insert("apple", 2));
1255 ASSERT_TRUE(map.insert("banana", 3));
1256
1258 (std::vector<std::string>{"", "app", "apple", "banana"}));
1260 (std::vector<std::string>{"app", "apple"}));
1261 EXPECT_TRUE(map.words_with_prefix("cat").is_empty());
1262}
1263
1265{
1267 ASSERT_TRUE(map.insert("alpha", "one"));
1268 ASSERT_TRUE(map.insert("beta", "two"));
1269
1271 copy.insert_or_assign("alpha", "uno");
1272 copy.insert_or_assign("gamma", "three");
1273
1274 ASSERT_NE(map.find("alpha"), nullptr);
1275 ASSERT_NE(copy.find("alpha"), nullptr);
1276 EXPECT_EQ(*map.find("alpha"), "one");
1277 EXPECT_EQ(*copy.find("alpha"), "uno");
1278 EXPECT_FALSE(map.contains("gamma"));
1279 EXPECT_TRUE(copy.contains("gamma"));
1280 EXPECT_EQ(map.size(), 2);
1281 EXPECT_EQ(copy.size(), 3);
1282}
1283
1285{
1286 Prefix_Tree_Map<int> source;
1287 ASSERT_TRUE(source.insert("alpha", 1));
1288 ASSERT_TRUE(source.insert("beta", 2));
1289
1290 Prefix_Tree_Map<int> moved(std::move(source));
1291
1292 EXPECT_TRUE(source.is_empty());
1293 EXPECT_TRUE(moved.contains("alpha"));
1294 EXPECT_TRUE(moved.contains("beta"));
1295 ASSERT_NE(moved.find("alpha"), nullptr);
1296 ASSERT_NE(moved.find("beta"), nullptr);
1297 EXPECT_EQ(*moved.find("alpha"), 1);
1298 EXPECT_EQ(*moved.find("beta"), 2);
1299}
1300
1302{
1304 ASSERT_TRUE(map.insert("alpha", 1));
1305 ASSERT_TRUE(map.insert("beta", 2));
1306
1307 map.clear();
1308
1309 EXPECT_TRUE(map.is_empty());
1310 EXPECT_FALSE(map.contains("alpha"));
1311 EXPECT_FALSE(map.contains("beta"));
1312 EXPECT_TRUE(map.words().is_empty());
1313}
1314
1316{
1317 const std::vector<std::string> keys =
1318 {
1319 "", "a", "app", "apple", "application", "banana", "band", "bandana",
1320 "car", "carbon", "cart", "dog", "door", "dorm"
1321 };
1322
1324 std::map<std::string, int> reference;
1325
1326 for (size_t step = 0; step < 500; ++step)
1327 {
1328 const std::string &key = keys[(step * 7 + 3) % keys.size()];
1329 const int value = static_cast<int>(step);
1330
1331 if ((step % 4) == 0)
1332 EXPECT_EQ(subject.insert(key, value),
1333 reference.emplace(key, value).second);
1334 else if ((step % 4) == 1)
1335 {
1337 reference[key] = value;
1338 }
1339 else if ((step % 4) == 2)
1340 EXPECT_EQ(subject.erase(key), reference.erase(key) != 0);
1341 else
1342 {
1343 const int *subject_value = subject.find(key);
1344 const auto it = reference.find(key);
1345 ASSERT_EQ(subject_value != nullptr, it != reference.end());
1346 if (subject_value != nullptr)
1347 EXPECT_EQ(*subject_value, it->second);
1348 }
1349
1350 ASSERT_EQ(subject.size(), reference.size()) << "step " << step;
1351 }
1352
1353 std::vector<std::string> expected;
1354 for (const auto &kv : reference)
1355 expected.push_back(kv.first);
1357 for (const auto &kv : reference)
1358 {
1359 ASSERT_NE(subject.find(kv.first), nullptr);
1360 EXPECT_EQ(*subject.find(kv.first), kv.second);
1361 }
1362}
1363
1365{
1366 const std::vector<std::string> words =
1367 {
1368 "", "app", "apple", "application", "apt", "banana", "band",
1369 "bandana", "car", "carbon", "cart"
1370 };
1371
1375
1376 for (size_t i = 0; i < words.size(); ++i)
1377 {
1378 const int value = static_cast<int>(i * 10);
1379 ASSERT_EQ(subject.insert(words[i], value), reference_set.insert_word(words[i]));
1380 ASSERT_TRUE(reference_map.insert(words[i], value));
1381 }
1382
1383 for (size_t i = 0; i < words.size(); ++i)
1384 {
1385 ASSERT_TRUE(subject.contains(words[i]));
1386 ASSERT_TRUE(reference_set.contains(words[i]));
1387 ASSERT_NE(subject.find(words[i]), nullptr);
1388 ASSERT_NE(reference_map.find(words[i]), nullptr);
1389 EXPECT_EQ(*subject.find(words[i]), *reference_map.find(words[i]));
1390 }
1391
1392 EXPECT_EQ(subject.size(), reference_set.size());
1394 EXPECT_EQ(to_sorted_vector(subject.words()), to_sorted_vector(reference_map.keys_with_prefix("")));
1395
1396 for (const auto &prefix : {"", "app", "ban", "car", "z"})
1397 {
1398 EXPECT_EQ(to_sorted_vector(subject.words_with_prefix(prefix)),
1399 to_sorted_vector(reference_set.words_with_prefix(prefix)))
1400 << "Prefix_Tree disagreement for prefix " << prefix;
1401 EXPECT_EQ(to_sorted_vector(subject.words_with_prefix(prefix)),
1402 to_sorted_vector(reference_map.keys_with_prefix(prefix)))
1403 << "RadixTree disagreement for prefix " << prefix;
1404 }
1405}
1406
1407//==============================================================================
1408// Main
1409//==============================================================================
1410
1411int main(int argc, char **argv)
1412{
1413 ::testing::InitGoogleTest(&argc, argv);
1414 return RUN_ALL_TESTS();
1415}
int main()
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
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
Definition tpl_array.H:365
Low-level prefix tree node for storing character sequences.
static void clone(const Tree_Node< char > *src, Tree_Node< char > *tgt)
Clone helper - copies children from src to tgt.
void destroy() noexcept
Destroy all children of this node.
void mark_end_word()
Mark this node as the end of a word.
bool is_end_word() const noexcept
Check if this node marks the end of a word.
char symbol() const noexcept
Return the character stored in this node.
bool insert_word(const std::string &word)
Insert a word into the tree.
DynList< Cnode * > children() const
Return a list of all child nodes.
bool is_empty() const noexcept
Return true if the array is empty.
Owning prefix tree map from strings to values.
DynArray< Key > words(const size_t max_word_length=2048) const
Get all keys stored in the map.
size_t count() const noexcept
Return the number of stored key-value pairs.
bool contains(const Key &key) const noexcept
Check whether key is stored.
const Value * find(const Key &key) const noexcept
Look up key.
bool insert(const Key &key, const Value &value)
Insert key with a copied value if absent.
DynArray< Key > words_with_prefix(const Key &prefix, const size_t max_word_length=2048) const
Get all keys with a given prefix.
void insert_or_assign(const Key &key, Value value)
Insert key or overwrite its mapped value.
bool erase(const Key &key) noexcept
Remove key if present.
void clear()
Remove every key-value pair from the map.
size_t size() const noexcept
Return the number of stored key-value pairs.
bool is_empty() const noexcept
Check whether the map has no keys.
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.
Cnode * root() noexcept
Return the mutable root node.
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.
size_t size() const noexcept
Return the number of words stored in the tree.
Compressed prefix tree mapping std::basic_string<Char> keys to values of type T.
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
void TearDown() override
void SetUp() override
#define TEST(name)
#define N
Definition fib.C:294
__gmp_expr< T, __gmp_binary_expr< __gmp_expr< T, U >, unsigned long int, __gmp_root_function > > root(const __gmp_expr< T, U > &expr, unsigned long int l)
Definition gmpfrxx.h:4071
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
size_t size(Node *root) noexcept
Itor2 copy(Itor1 sourceBeg, const Itor1 &sourceEnd, Itor2 destBeg)
Copy elements from one range to another.
Definition ahAlgo.H:584
static void prefix(Node *root, DynList< Node * > &acc)
Itor::difference_type count(const Itor &beg, const Itor &end, const T &value)
Count elements equal to a value.
Definition ahAlgo.H:127
Trie (prefix tree) implementation.
static std::vector< std::string > to_sorted_vector(const DynArray< std::string > &words)
TEST_F(PrefixTreeTest, NodeConstruction)
int keys[]
Compressed prefix tree (Aleph::RadixTree) mapping string keys to values.