Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
odhash.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
37# include <gtest/gtest.h>
38
39# include <random>
40# include <set>
41# include <chrono>
42
43# include <tpl_odhash.H>
44
45using namespace std;
46using namespace testing;
47using namespace Aleph;
48
50{
52
53 EXPECT_TRUE(tbl.is_empty());
54 EXPECT_EQ(tbl.size(), 0);
55
56 const size_t cap = tbl.capacity();
57 for (size_t i = 0; i < cap; ++i)
58 {
59 EXPECT_EQ(tbl.size(), i);
60 EXPECT_NE(tbl.insert(i), nullptr);
61 EXPECT_EQ(tbl.size(), i + 1);
62 EXPECT_FALSE(tbl.is_empty());
63 }
64
65 for (size_t i = 0; i < tbl.size(); ++i)
66 {
67 ASSERT_NE(tbl.search(i), nullptr);
68 auto ptr = tbl.search(i);
69 EXPECT_EQ(*ptr, i);
70 EXPECT_FALSE(tbl.is_empty());
71 }
72
73 for (size_t i = 0, n = tbl.size(); i < n; ++i)
74 {
75 auto ptr = tbl.search(i);
76 EXPECT_EQ(*ptr, i);
77 tbl.remove(*ptr);
78 EXPECT_EQ(tbl.size(), n - i - 1);
79 EXPECT_EQ(tbl.search(i), nullptr);
80 EXPECT_FALSE(tbl.contains(i));
81 }
82
83 EXPECT_EQ(tbl.size(), 0);
84 EXPECT_TRUE(tbl.is_empty());
85}
86
88{
89 size_t key;
90 string value;
92 MyRecord(size_t k, const string & v) : key(k), value(v) {}
93 MyRecord(size_t k) : key(k) {}
94 struct Eq
95 {
96 bool operator () (const MyRecord & r1, const MyRecord & r2) const noexcept
97 {
98 return r1.key == r2.key;
99 }
100 };
101 bool operator == (const MyRecord & r) const noexcept { return Eq()(*this, r); }
102};
103
104inline size_t fst_hast(const MyRecord & r) noexcept
105{
106 return dft_hash_fct(r.key);
107}
108
109inline size_t snd_hash(const MyRecord & r) noexcept
110{
111 return snd_hash_fct(r.key);
112}
113
114
116{
118
119 EXPECT_EQ(tbl.size(), 0);
120 EXPECT_TRUE(tbl.is_empty());
121
122 for (size_t i = 0; i < 100; ++i)
123 {
124 EXPECT_EQ(tbl.size(), i);
125 tbl.emplace(i, to_string(i));
126 EXPECT_EQ(tbl.size(), i + 1);
127 auto ptr = tbl.search(MyRecord(i));
128 EXPECT_NE(ptr, nullptr);
129 EXPECT_EQ(ptr->key, i);
130 EXPECT_EQ(ptr->value, to_string(i));
131 }
132
133 for (size_t i = 0, n = tbl.size(); i < n; ++i)
134 {
135 auto ptr = tbl.search(i);
136 EXPECT_EQ(*ptr, i);
137 tbl.remove(MyRecord(ptr->key));
138 EXPECT_EQ(tbl.size(), n - i - 1);
139 EXPECT_EQ(tbl.search(i), nullptr);
140 EXPECT_FALSE(tbl.contains(MyRecord(i)));
141 }
142}
143
145{
147 auto *ptr = tbl.insert(5);
148 ASSERT_NE(ptr, nullptr);
149
150 auto *bucket = decltype(tbl)::key_to_bucket(ptr);
151 ASSERT_NE(bucket, nullptr);
152 EXPECT_EQ(bucket->key, 5);
153 EXPECT_EQ(bucket->status, decltype(tbl)::BUSY);
154
155 EXPECT_NO_THROW(tbl.remove(5));
156 EXPECT_EQ(tbl.search(5), nullptr);
157}
158
159// Test that removing a non-existent key does NOT corrupt the table state.
160// This test exposes a bug where the old remove() would decrement N and
161// corrupt probe_counters even when the key was not found.
163{
165
166 // Insert some elements
167 const int num_elements = 50;
168 for (int i = 0; i < num_elements; ++i)
169 EXPECT_NE(tbl.insert(i * 2), nullptr); // Insert even numbers: 0, 2, 4, ..., 98
170
171 EXPECT_EQ(tbl.size(), num_elements);
172
173 // Try to remove keys that don't exist (odd numbers)
174 // This should throw domain_error BUT NOT corrupt the table
175 for (int i = 0; i < 10; ++i)
176 {
177 int non_existent_key = i * 2 + 1; // Odd numbers: 1, 3, 5, ..., 19
178 EXPECT_THROW(tbl.remove(non_existent_key), std::domain_error);
179 }
180
181 // Verify table size is unchanged
182 EXPECT_EQ(tbl.size(), num_elements)
183 << "Table size should not change after failed remove attempts";
184
185 // Verify all original elements are still findable
186 for (int i = 0; i < num_elements; ++i)
187 {
188 auto ptr = tbl.search(i * 2);
189 EXPECT_NE(ptr, nullptr)
190 << "Element " << i * 2 << " should still be in the table";
191 if (ptr)
192 EXPECT_EQ(*ptr, i * 2);
193 }
194
195 // Verify we can still remove elements normally
196 for (int i = 0; i < num_elements; ++i)
197 {
198 auto ptr = tbl.search(i * 2);
199 ASSERT_NE(ptr, nullptr);
200 EXPECT_NO_THROW(tbl.remove(*ptr));
201 EXPECT_EQ(tbl.size(), num_elements - i - 1);
202 }
203
204 EXPECT_TRUE(tbl.is_empty());
205}
206
207// Test remove with external key (key not from a bucket in the table)
209{
211
212 // Insert elements
213 for (int i = 0; i < 20; ++i)
214 EXPECT_NE(tbl.insert(i), nullptr);
215
216 EXPECT_EQ(tbl.size(), 20);
217
218 // Remove using external key (not a reference from the table)
219 int external_key = 10;
221 EXPECT_EQ(tbl.size(), 19);
222 EXPECT_EQ(tbl.search(10), nullptr);
223
224 // Verify other elements are intact
225 for (int i = 0; i < 20; ++i)
226 {
227 if (i == 10) continue;
228 EXPECT_NE(tbl.search(i), nullptr) << "Element " << i << " should still exist";
229 }
230}
231
232// Test remove with internal key (reference from the table bucket)
234{
236
237 // Insert elements
238 for (int i = 0; i < 20; ++i)
239 EXPECT_NE(tbl.insert(i), nullptr);
240
241 EXPECT_EQ(tbl.size(), 20);
242
243 // Remove using internal key (reference from search result)
244 auto ptr = tbl.search(10);
245 ASSERT_NE(ptr, nullptr);
246 EXPECT_NO_THROW(tbl.remove(*ptr)); // *ptr is internal reference
247 EXPECT_EQ(tbl.size(), 19);
248 EXPECT_EQ(tbl.search(10), nullptr);
249}
250
251// Test that verifies capacity doesn't change after failed remove.
252// The old buggy code would call rehash() on every failed remove,
253// potentially changing capacity. The new code doesn't rehash.
255{
257
258 // Insert elements to create some collision chains
259 for (int i = 0; i < 50; ++i)
260 EXPECT_NE(tbl.insert(i * 2), nullptr); // Even numbers
261
262 const size_t original_capacity = tbl.capacity();
263 const size_t original_size = tbl.size();
264
265 // Try to remove many non-existent keys
266 // Old buggy code would rehash on each failed remove
267 for (int attempt = 0; attempt < 100; ++attempt)
268 {
269 int non_existent = attempt * 2 + 1; // Odd numbers don't exist
270 EXPECT_THROW(tbl.remove(non_existent), std::domain_error);
271 }
272
273 // Capacity should be unchanged (no rehash occurred)
274 EXPECT_EQ(tbl.capacity(), original_capacity)
275 << "Capacity changed - possible unnecessary rehash on failed remove";
276
277 // Size should be unchanged
278 EXPECT_EQ(tbl.size(), original_size);
279
280 // All elements should still be findable
281 for (int i = 0; i < 50; ++i)
282 EXPECT_NE(tbl.search(i * 2), nullptr) << "Element " << i * 2 << " not found";
283}
284
285// ============================================================================
286// STRESS TESTS / FUZZING
287// ============================================================================
288
289// Fuzzing test: random operations with oracle verification
291{
292 // Use large table to avoid resize during test
293 ODhashTable<int> tbl(20000);
295
296 mt19937 rng(42);
299
300 const int num_operations = 8000;
301
302 for (int i = 0; i < num_operations; ++i)
303 {
304 int key = key_dist(rng);
305 int op = op_dist(rng);
306
307 switch (op)
308 {
309 case 0: // Insert
310 {
311 bool in_oracle = oracle.count(key) > 0;
312 auto ptr = tbl.insert(key);
313 if (ptr != nullptr)
314 {
315 EXPECT_FALSE(in_oracle) << "Insert succeeded for key " << key
316 << " but oracle already had it";
317 oracle.insert(key);
318 }
319 else
320 {
321 EXPECT_TRUE(in_oracle) << "Insert failed for key " << key
322 << " but oracle didn't have it";
323 }
324 break;
325 }
326 case 1: // Remove
327 {
328 bool in_oracle = oracle.count(key) > 0;
329 if (in_oracle)
330 {
331 try
332 {
333 tbl.remove(key);
334 oracle.erase(key); // Only erase if remove succeeded
335 }
336 catch (const domain_error &)
337 {
338 FAIL() << "Remove threw for key " << key << " that was in oracle";
339 }
340 }
341 else
342 {
343 EXPECT_THROW(tbl.remove(key), domain_error);
344 }
345 break;
346 }
347 case 2: // Search
348 {
349 auto ptr = tbl.search(key);
350 bool in_oracle = oracle.count(key) > 0;
351 EXPECT_EQ(ptr != nullptr, in_oracle)
352 << "Search mismatch for key " << key;
353 if (ptr)
354 EXPECT_EQ(*ptr, key);
355 break;
356 }
357 }
358
359 ASSERT_EQ(tbl.size(), oracle.size())
360 << "Size mismatch at operation " << i << ", key=" << key;
361 }
362
363 // Final verification
364 for (int key : oracle)
365 {
366 auto ptr = tbl.search(key);
367 ASSERT_NE(ptr, nullptr) << "Final check: key " << key << " missing";
368 }
369}
370
371// Stress test: fill table to near capacity then empty it completely
373{
374 ODhashTable<int> tbl(1000);
375 const size_t target = tbl.capacity() - 1; // Leave one empty as sentinel
376
377 // Fill the table
378 for (size_t i = 0; i < target; ++i)
379 {
380 auto ptr = tbl.insert(static_cast<int>(i));
381 ASSERT_NE(ptr, nullptr) << "Insert failed at i=" << i;
382 }
383
384 EXPECT_EQ(tbl.size(), target);
385
386 // Verify all elements
387 for (size_t i = 0; i < target; ++i)
388 {
389 ASSERT_NE(tbl.search(static_cast<int>(i)), nullptr)
390 << "Element " << i << " not found after fill";
391 }
392
393 // Empty the table in random order
394 vector<int> keys(target);
395 iota(keys.begin(), keys.end(), 0);
396
397 mt19937 rng(123);
398 shuffle(keys.begin(), keys.end(), rng);
399
400 for (size_t i = 0; i < target; ++i)
401 {
402 EXPECT_NO_THROW(tbl.remove(keys[i])) << "Remove failed for key " << keys[i];
403 EXPECT_EQ(tbl.size(), target - i - 1);
404 }
405
406 EXPECT_TRUE(tbl.is_empty());
407}
408
409// Stress test: many collisions (all keys hash to same bucket)
411{
412 // Custom hash that always returns the same value - forces maximum collisions
413 auto bad_hash = [](const int &) -> size_t { return 42; };
414
416
417 // Insert elements - all will collide
418 const int num_elements = 50;
419 for (int i = 0; i < num_elements; ++i)
420 {
421 auto ptr = tbl.insert(i);
422 ASSERT_NE(ptr, nullptr) << "Insert failed at i=" << i << " with bad hash";
423 }
424
425 EXPECT_EQ(tbl.size(), num_elements);
426
427 // Verify all elements are findable despite collisions
428 for (int i = 0; i < num_elements; ++i)
429 {
430 auto ptr = tbl.search(i);
431 ASSERT_NE(ptr, nullptr) << "Element " << i << " not found with collision";
432 EXPECT_EQ(*ptr, i);
433 }
434
435 // Remove in reverse order
436 for (int i = num_elements - 1; i >= 0; --i)
437 {
438 EXPECT_NO_THROW(tbl.remove(i));
439 EXPECT_EQ(tbl.search(i), nullptr);
440 }
441
442 EXPECT_TRUE(tbl.is_empty());
443}
444
445// Stress test: repeated insert/remove cycles
447{
449
450 const int cycles = 100;
451 const int elements_per_cycle = 50;
452
453 for (int cycle = 0; cycle < cycles; ++cycle)
454 {
455 // Insert phase
456 for (int i = 0; i < elements_per_cycle; ++i)
457 {
458 int key = cycle * elements_per_cycle + i;
459 auto ptr = tbl.insert(key);
460 ASSERT_NE(ptr, nullptr) << "Insert failed at cycle " << cycle << ", i=" << i;
461 }
462
464
465 // Remove phase - remove all
466 for (int i = 0; i < elements_per_cycle; ++i)
467 {
468 int key = cycle * elements_per_cycle + i;
469 EXPECT_NO_THROW(tbl.remove(key));
470 }
471
472 EXPECT_TRUE(tbl.is_empty());
473 }
474}
475
476// Stress test: trigger resize operations
478{
479 // Start with small table that will need to resize
481
483 mt19937 rng(999);
485
486 // Insert many elements to trigger multiple resizes
487 const int num_inserts = 5000;
488 for (int i = 0; i < num_inserts; ++i)
489 {
490 int key = key_dist(rng);
491 auto ptr = tbl.insert(key);
492 if (oracle.count(key) == 0 && ptr != nullptr)
493 oracle.insert(key);
494 }
495
496 EXPECT_EQ(tbl.size(), oracle.size());
497
498 // Verify all elements survived resizes
499 for (int key : oracle)
500 {
501 auto ptr = tbl.search(key);
502 ASSERT_NE(ptr, nullptr) << "Key " << key << " lost after resize";
503 }
504
505 // Remove half and verify
506 size_t to_remove = oracle.size() / 2;
507 auto it = oracle.begin();
508 for (size_t i = 0; i < to_remove; ++i, ++it)
509 {
510 EXPECT_NO_THROW(tbl.remove(*it));
511 }
512 oracle.erase(oracle.begin(), it);
513
514 EXPECT_EQ(tbl.size(), oracle.size());
515
516 // Verify remaining elements
517 for (int key : oracle)
518 {
519 ASSERT_NE(tbl.search(key), nullptr) << "Key " << key << " missing after partial remove";
520 }
521}
522
523// Fuzzing: interleaved search during insert/remove
525{
526 // Use large table to avoid resize
527 ODhashTable<int> tbl(5000);
529
530 mt19937 rng(7777);
532 uniform_real_distribution<double> prob_dist(0.0, 1.0);
533
534 const int num_ops = 5000;
535
536 for (int i = 0; i < num_ops; ++i)
537 {
538 int key = key_dist(rng);
539 double prob = prob_dist(rng);
540
541 if (prob < 0.4) // 40% insert
542 {
543 auto ptr = tbl.insert(key);
544 if (ptr != nullptr)
545 oracle.insert(key);
546 }
547 else if (prob < 0.6) // 20% remove
548 {
549 if (oracle.count(key))
550 {
551 try
552 {
553 tbl.remove(key);
554 oracle.erase(key); // Only erase if remove succeeded
555 }
556 catch (const domain_error &)
557 {
558 FAIL() << "Remove threw for key " << key << " that was in oracle";
559 }
560 }
561 }
562 else // 40% search
563 {
564 auto ptr = tbl.search(key);
565 bool in_oracle = oracle.count(key) > 0;
566 EXPECT_EQ(ptr != nullptr, in_oracle)
567 << "Search mismatch for key " << key;
568 }
569
570 // Periodic consistency check
571 if (i % 500 == 0)
572 ASSERT_EQ(tbl.size(), oracle.size()) << "Size mismatch at i=" << i;
573 }
574
575 // Final check
576 EXPECT_EQ(tbl.size(), oracle.size());
577}
578
579// Stress test: with auto-resize enabled
581{
582 // Enable auto-resize
583 ODhashTable<int> tbl(10); // Small initial size
585
586 mt19937 rng(333);
588
589 // Insert many elements, triggering multiple resizes
590 const int num_inserts = 3000;
591 for (int i = 0; i < num_inserts; ++i)
592 {
593 int key = key_dist(rng);
594 auto ptr = tbl.insert(key);
595 if (ptr != nullptr)
596 oracle.insert(key);
597 }
598
599 // Verify size matches
600 EXPECT_EQ(tbl.size(), oracle.size());
601
602 // Verify all elements survived resizes
603 for (int key : oracle)
604 {
605 auto ptr = tbl.search(key);
606 ASSERT_NE(ptr, nullptr) << "Key " << key << " lost during resize";
607 EXPECT_EQ(*ptr, key);
608 }
609
610 // Now remove some elements
611 auto it = oracle.begin();
612 size_t to_remove = oracle.size() / 3;
613 for (size_t i = 0; i < to_remove && it != oracle.end(); ++i)
614 {
615 int key = *it;
616 ++it;
617 tbl.remove(key);
618 oracle.erase(key);
619 }
620
621 EXPECT_EQ(tbl.size(), oracle.size());
622
623 // Verify remaining elements
624 for (int key : oracle)
625 ASSERT_NE(tbl.search(key), nullptr);
626}
627
628// Stress test with string keys
630{
633
634 mt19937 rng(54321);
637
638 auto random_string = [&]() {
639 int len = len_dist(rng);
640 string s;
641 s.reserve(len);
642 for (int i = 0; i < len; ++i)
643 s += static_cast<char>(char_dist(rng));
644 return s;
645 };
646
647 const int num_ops = 2000;
648
649 for (int i = 0; i < num_ops; ++i)
650 {
651 string key = random_string();
652
653 if (oracle.count(key) == 0)
654 {
655 auto ptr = tbl.insert(key);
656 if (ptr != nullptr)
657 oracle.insert(key);
658 }
659 else
660 {
661 // Remove existing key
662 tbl.remove(key);
663 oracle.erase(key);
664 }
665 }
666
667 EXPECT_EQ(tbl.size(), oracle.size());
668
669 // Verify all oracle keys are present
670 for (const auto & key : oracle)
671 {
672 auto ptr = tbl.search(key);
673 ASSERT_NE(ptr, nullptr) << "String key missing: " << key;
674 }
675}
676
677// Test search_or_insert with DELETED entries (exercises hard_allocate_bucket)
679{
681
682 // Insert some elements
683 for (int i = 0; i < 30; ++i)
684 EXPECT_NE(tbl.insert(i), nullptr);
685
686 // Remove some to create DELETED entries
687 for (int i = 0; i < 30; i += 2)
688 tbl.remove(i); // Remove even numbers
689
690 const size_t size_after_removes = tbl.size();
691
692 // search_or_insert for existing keys should return existing
693 for (int i = 1; i < 30; i += 2)
694 {
695 auto ptr = tbl.search_or_insert(i);
696 ASSERT_NE(ptr, nullptr);
697 EXPECT_EQ(*ptr, i);
698 }
699 EXPECT_EQ(tbl.size(), size_after_removes); // Size unchanged
700
701 // search_or_insert for removed keys should insert them
702 for (int i = 0; i < 30; i += 2)
703 {
704 const size_t old_size = tbl.size();
705 auto ptr = tbl.search_or_insert(i);
706 ASSERT_NE(ptr, nullptr);
707 EXPECT_EQ(*ptr, i);
708 EXPECT_EQ(tbl.size(), old_size + 1); // Size increased
709 }
710
711 // Verify all elements are searchable
712 for (int i = 0; i < 30; ++i)
713 {
714 auto ptr = tbl.search(i);
715 ASSERT_NE(ptr, nullptr) << "Key " << i << " not found";
716 }
717}
718
719// Test contains_or_insert (uses hard_allocate_bucket)
721{
722 // Bad hash to force collisions
723 auto bad_hash = [](const int &) -> size_t { return 7; };
724
726
727 // Insert and remove to create DELETED entries with collisions
728 for (int i = 0; i < 20; ++i)
729 EXPECT_NE(tbl.insert(i), nullptr);
730
731 for (int i = 0; i < 20; i += 3)
732 tbl.remove(i);
733
734 // contains_or_insert for new keys
735 for (int i = 20; i < 30; ++i)
736 {
737 auto [ptr, existed] = tbl.contains_or_insert(i);
738 ASSERT_NE(ptr, nullptr);
739 EXPECT_FALSE(existed) << "Key " << i << " should not have existed";
740 EXPECT_EQ(*ptr, i);
741 }
742
743 // contains_or_insert for existing keys
744 for (int i = 20; i < 30; ++i)
745 {
746 auto [ptr, existed] = tbl.contains_or_insert(i);
747 ASSERT_NE(ptr, nullptr);
748 EXPECT_TRUE(existed) << "Key " << i << " should have existed";
749 EXPECT_EQ(*ptr, i);
750 }
751}
752
753// Stress test for search_or_insert with many DELETED entries
755{
757 std::set<int> oracle;
758 std::mt19937 gen(54321);
759 std::uniform_int_distribution<> key_dist(0, 500);
760 std::uniform_int_distribution<> op_dist(0, 2);
761
762 for (int iter = 0; iter < 5000; ++iter)
763 {
764 int key = key_dist(gen);
765 int op = op_dist(gen);
766
767 if (op == 0) // search_or_insert
768 {
769 auto ptr = tbl.search_or_insert(key);
770 ASSERT_NE(ptr, nullptr) << "search_or_insert returned nullptr for key " << key;
771 EXPECT_EQ(*ptr, key);
772 oracle.insert(key);
773 // Verify immediately after insert
774 auto verify_ptr = tbl.search(key);
775 ASSERT_NE(verify_ptr, nullptr)
776 << "Key " << key << " not found immediately after search_or_insert at iter " << iter;
777 }
778 else if (op == 1 && oracle.count(key)) // remove existing
779 {
780 // Verify key exists before removal
781 auto pre_ptr = tbl.search(key);
782 ASSERT_NE(pre_ptr, nullptr)
783 << "Key " << key << " should exist before removal at iter " << iter
784 << ", oracle.count=" << oracle.count(key) << ", tbl.size=" << tbl.size();
785 tbl.remove(key);
786 oracle.erase(key);
787 }
788 else // search
789 {
790 auto ptr = tbl.search(key);
791 if (oracle.count(key))
792 ASSERT_NE(ptr, nullptr) << "Key " << key << " should exist at iter " << iter;
793 else
794 EXPECT_EQ(ptr, nullptr);
795 }
796
797 EXPECT_EQ(tbl.size(), oracle.size())
798 << "Size mismatch at iter " << iter << ": tbl=" << tbl.size() << ", oracle=" << oracle.size();
799 }
800
801 // Final verification
802 for (int key : oracle)
803 {
804 auto ptr = tbl.search(key);
805 ASSERT_NE(ptr, nullptr) << "Key " << key << " missing";
806 }
807}
808
809// Focused debug test to find the exact bug
811{
813 std::set<int> oracle;
814 std::mt19937 gen(54321);
815 std::uniform_int_distribution<> key_dist(0, 500);
816 std::uniform_int_distribution<> op_dist(0, 2);
817
818 // Reproduce up to iteration 701 where the bug occurs
819 for (int iter = 0; iter <= 705; ++iter)
820 {
821 int key = key_dist(gen);
822 int op = op_dist(gen);
823
824 // Verify oracle consistency before operation
825 for (int k : oracle)
826 {
827 auto p = tbl.search(k);
828 ASSERT_NE(p, nullptr)
829 << "Pre-op check: Key " << k << " missing at iter " << iter
830 << " (about to do op " << op << " on key " << key << ")";
831 }
832
833 if (op == 0) // search_or_insert
834 {
835 bool existed_before = oracle.count(key) > 0;
836 size_t size_before = tbl.size();
837 auto ptr = tbl.search_or_insert(key);
838 ASSERT_NE(ptr, nullptr) << "search_or_insert returned nullptr at iter " << iter;
839 EXPECT_EQ(*ptr, key);
840 oracle.insert(key);
841
842 if (!existed_before)
843 EXPECT_EQ(tbl.size(), size_before + 1)
844 << "Size should increase for new key at iter " << iter;
845 }
846 else if (op == 1 && oracle.count(key)) // remove existing
847 {
848 tbl.remove(key);
849 oracle.erase(key);
850 }
851
852 EXPECT_EQ(tbl.size(), oracle.size())
853 << "Size mismatch at iter " << iter;
854 }
855}
856
857// ============================================================================
858// COPY/MOVE SEMANTICS TESTS
859// ============================================================================
860
862{
864 for (int i = 0; i < 50; ++i)
865 original.insert(i);
866
868
869 EXPECT_EQ(copy.size(), original.size());
870 EXPECT_EQ(copy.capacity(), original.capacity());
871
872 for (int i = 0; i < 50; ++i)
873 {
874 EXPECT_NE(original.search(i), nullptr);
875 EXPECT_NE(copy.search(i), nullptr);
876 }
877
878 copy.remove(25);
879 EXPECT_EQ(copy.search(25), nullptr);
880 EXPECT_NE(original.search(25), nullptr);
881}
882
884{
886 for (int i = 0; i < 50; ++i)
887 original.insert(i);
888
889 const size_t orig_size = original.size();
890 const size_t orig_cap = original.capacity();
891
892 ODhashTable<int> moved(std::move(original));
893
895 EXPECT_EQ(moved.capacity(), orig_cap);
896
897 for (int i = 0; i < 50; ++i)
898 EXPECT_NE(moved.search(i), nullptr);
899}
900
902{
904 for (int i = 0; i < 50; ++i)
905 original.insert(i);
906
908 copy.insert(999);
909
910 copy = original;
911
912 EXPECT_EQ(copy.size(), original.size());
913
914 for (int i = 0; i < 50; ++i)
915 EXPECT_NE(copy.search(i), nullptr);
916
917 EXPECT_EQ(copy.search(999), nullptr);
918}
919
921{
923 for (int i = 0; i < 50; ++i)
924 original.insert(i);
925
926 const size_t orig_size = original.size();
927
929 target.insert(999);
930
931 target = std::move(original);
932
934
935 for (int i = 0; i < 50; ++i)
936 EXPECT_NE(target.search(i), nullptr);
937}
938
940{
942 for (int i = 0; i < 50; ++i)
943 tbl.insert(i);
944
945 tbl = tbl;
946
947 EXPECT_EQ(tbl.size(), 50);
948 for (int i = 0; i < 50; ++i)
949 EXPECT_NE(tbl.search(i), nullptr);
950}
951
952// ============================================================================
953// EDGE CASES
954// ============================================================================
955
957{
959
960 EXPECT_TRUE(tbl.is_empty());
961 EXPECT_EQ(tbl.size(), 0);
962 EXPECT_EQ(tbl.search(42), nullptr);
963 EXPECT_FALSE(tbl.has(42));
964 EXPECT_FALSE(tbl.contains(42));
965 EXPECT_THROW(tbl.remove(42), std::domain_error);
966}
967
969{
971
972 tbl.insert(42);
973 EXPECT_EQ(tbl.size(), 1);
974 EXPECT_NE(tbl.search(42), nullptr);
975
976 tbl.remove(42);
977 EXPECT_EQ(tbl.size(), 0);
978 EXPECT_TRUE(tbl.is_empty());
979 EXPECT_EQ(tbl.search(42), nullptr);
980}
981
983{
985
986 auto first = tbl.insert(42);
987 ASSERT_NE(first, nullptr);
988
989 auto second = tbl.insert(42);
990 EXPECT_EQ(second, nullptr);
991
992 EXPECT_EQ(tbl.size(), 1);
993}
994
996{
998
999 EXPECT_FALSE(tbl.has(42));
1000 EXPECT_FALSE(tbl.contains(42));
1001
1002 tbl.insert(42);
1003
1004 EXPECT_TRUE(tbl.has(42));
1005 EXPECT_TRUE(tbl.contains(42));
1006 EXPECT_FALSE(tbl.has(43));
1007 EXPECT_FALSE(tbl.contains(43));
1008}
1009
1011{
1012 ODhashTable<int> tbl(100);
1013 tbl.insert(42);
1014
1016 int& ref = tbl.find(42);
1017 EXPECT_EQ(ref, 42);
1018 });
1019
1020 EXPECT_THROW(tbl.find(999), std::domain_error);
1021}
1022
1023// ============================================================================
1024// REHASH/RESIZE TESTS
1025// ============================================================================
1026
1028{
1029 ODhashTable<int> tbl(100);
1031
1032 for (int i = 0; i < 50; ++i)
1033 {
1034 tbl.insert(i);
1035 oracle.insert(i);
1036 }
1037
1038 for (int i = 0; i < 50; i += 2)
1039 {
1040 tbl.remove(i);
1041 oracle.erase(i);
1042 }
1043
1044 tbl.rehash();
1045
1046 EXPECT_EQ(tbl.size(), oracle.size());
1047
1048 for (int key : oracle)
1049 EXPECT_NE(tbl.search(key), nullptr);
1050}
1051
1053{
1055
1056 for (int i = 0; i < 30; ++i)
1057 tbl.insert(i);
1058
1059 const size_t old_cap = tbl.capacity();
1060 tbl.resize(200);
1061
1062 EXPECT_GT(tbl.capacity(), old_cap);
1063 EXPECT_EQ(tbl.size(), 30);
1064
1065 for (int i = 0; i < 30; ++i)
1066 EXPECT_NE(tbl.search(i), nullptr);
1067}
1068
1070{
1071 ODhashTable<int> tbl(200);
1072
1073 for (int i = 0; i < 30; ++i)
1074 tbl.insert(i);
1075
1076 tbl.resize(50);
1077
1078 EXPECT_EQ(tbl.size(), 30);
1079
1080 for (int i = 0; i < 30; ++i)
1081 EXPECT_NE(tbl.search(i), nullptr);
1082}
1083
1084// ============================================================================
1085// ITERATOR TESTS
1086// ============================================================================
1087
1089{
1090 ODhashTable<int> tbl(100);
1092
1093 for (int i = 0; i < 50; ++i)
1094 {
1095 tbl.insert(i);
1096 oracle.insert(i);
1097 }
1098
1100 for (auto it = tbl.get_it(); it.has_curr(); it.next())
1101 visited.insert(it.get_curr());
1102
1104}
1105
1107{
1108 ODhashTable<int> tbl(100);
1109
1110 auto it = tbl.get_it();
1111 EXPECT_FALSE(it.has_curr());
1112}
1113
1115{
1116 ODhashTable<int> tbl(100);
1117 tbl.insert(42);
1118
1119 auto it = tbl.get_it();
1120 ASSERT_TRUE(it.has_curr());
1121 EXPECT_EQ(it.get_curr(), 42);
1122
1123 it.next();
1124 EXPECT_FALSE(it.has_curr());
1125}
1126
1128{
1129 ODhashTable<int> tbl(100);
1130
1131 for (int i = 0; i < 10; ++i)
1132 tbl.insert(i);
1133
1134 auto it = tbl.get_it();
1135 while (it.has_curr())
1136 it.del();
1137
1138 EXPECT_TRUE(tbl.is_empty());
1139}
1140
1141// ============================================================================
1142// PROBE_COUNTER CLEANUP TESTS (ODhashTable specific)
1143// ============================================================================
1144
1145// Helper to count bucket states for ODhashTable
1147{
1148 size_t empty = 0;
1149 size_t busy = 0;
1150 size_t deleted = 0;
1151};
1152
1153template <typename HashTable>
1155{
1156 ODhashBucketStats stats;
1157 for (size_t i = 0; i < tbl.capacity(); ++i)
1158 {
1159 switch (tbl.table[i].status)
1160 {
1161 case HashTable::EMPTY: ++stats.empty; break;
1162 case HashTable::BUSY: ++stats.busy; break;
1163 case HashTable::DELETED: ++stats.deleted; break;
1164 }
1165 }
1166 return stats;
1167}
1168
1169// ODhashTable uses probe_counter to convert DELETED->EMPTY when counter reaches 0
1171{
1172 auto bad_hash = [](const int &) -> size_t { return 0; };
1174
1175 // Insert chain
1176 for (int i = 0; i < 5; ++i)
1177 tbl.insert(i);
1178
1180 EXPECT_EQ(before.busy, 5);
1181 EXPECT_EQ(before.deleted, 0);
1182
1183 // Remove last element - should become EMPTY due to probe_counter
1184 tbl.remove(4);
1185
1187 EXPECT_EQ(after.busy, 4);
1188 // With probe_counter, DELETED becomes EMPTY when no one depends on it
1189 EXPECT_EQ(after.deleted, 0) << "Last element should become EMPTY via probe_counter";
1190
1191 for (int i = 0; i < 4; ++i)
1192 EXPECT_NE(tbl.search(i), nullptr);
1193 EXPECT_EQ(tbl.search(4), nullptr);
1194}
1195
1197{
1198 auto bad_hash = [](const int &) -> size_t { return 0; };
1200
1201 for (int i = 0; i < 5; ++i)
1202 tbl.insert(i);
1203
1204 // Remove middle - should stay DELETED because others depend on it
1205 tbl.remove(2);
1206
1207 auto stats = count_odhash_bucket_states(tbl);
1208 EXPECT_EQ(stats.busy, 4);
1209 EXPECT_EQ(stats.deleted, 1) << "Middle should stay DELETED";
1210
1211 for (int i = 0; i < 5; ++i)
1212 {
1213 if (i == 2)
1214 EXPECT_EQ(tbl.search(i), nullptr);
1215 else
1216 EXPECT_NE(tbl.search(i), nullptr);
1217 }
1218}
1219
1221{
1222 auto bad_hash = [](const int &) -> size_t { return 0; };
1224
1225 for (int i = 0; i < 5; ++i)
1226 tbl.insert(i);
1227
1228 // Remove in reverse order - each should become EMPTY
1229 for (int i = 4; i >= 0; --i)
1230 tbl.remove(i);
1231
1232 auto stats = count_odhash_bucket_states(tbl);
1233 EXPECT_EQ(stats.busy, 0);
1234 EXPECT_EQ(stats.deleted, 0) << "All should become EMPTY when removed in reverse";
1235 EXPECT_TRUE(tbl.is_empty());
1236}
1237
1238// ============================================================================
1239// FUNCTIONAL METHODS TEST
1240// ============================================================================
1241
1243{
1244 ODhashTable<int> tbl(100);
1245 for (int i = 0; i < 10; ++i)
1246 tbl.insert(i);
1247
1248 int sum = 0;
1249 tbl.for_each([&sum](int x) { sum += x; });
1250
1251 EXPECT_EQ(sum, 45);
1252}
1253
1255{
1256 ODhashTable<int> tbl(100);
1257 for (int i = 0; i < 10; ++i)
1258 tbl.insert(i * 2);
1259
1260 EXPECT_TRUE(tbl.all([](int x) { return x % 2 == 0; }));
1261 EXPECT_FALSE(tbl.all([](int x) { return x > 5; }));
1262}
1263
1265{
1266 ODhashTable<int> tbl(100);
1267 for (int i = 0; i < 10; ++i)
1268 tbl.insert(i);
1269
1270 EXPECT_TRUE(tbl.exists([](int x) { return x == 5; }));
1271 EXPECT_FALSE(tbl.exists([](int x) { return x == 100; }));
1272}
1273
1275{
1276 ODhashTable<int> tbl(100);
1277 for (int i = 0; i < 10; ++i)
1278 tbl.insert(i);
1279
1280 auto evens = tbl.filter([](int x) { return x % 2 == 0; });
1281
1282 EXPECT_EQ(evens.size(), 5);
1283}
1284
static string random_string(std::mt19937 &rng, size_t len)
T & insert(const T &item)
Insert a new item by copy.
Definition htlist.H:1502
T remove()
Remove the first item of the list.
Definition htlist.H:1611
size_t size() const noexcept
Count the number of elements of the list.
Definition htlist.H:1319
Open addressing hash table with double hashing collision resolution.
Definition tpl_odhash.H:180
Aleph::DynList< T > filter(Operation &operation) const
Filter the elements of a container according to a matching criteria.
Definition ah-dry.H:1135
Key * insert(const Key &key)
Inserts a key into the hash table (copy version).
Definition hashDry.H:203
iterator end() noexcept
Return an STL-compatible end iterator.
iterator begin() noexcept
Return an STL-compatible iterator to the first element.
#define FAIL(msg)
#define TEST(name)
static mt19937 rng
MapOLhash< int, Foo > tbl
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
size_t snd_hash_fct(const Key &key) noexcept
Definition hash-fct.H:937
Itor2 copy(Itor1 sourceBeg, const Itor1 &sourceEnd, Itor2 destBeg)
Copy elements from one range to another.
Definition ahAlgo.H:584
size_t dft_hash_fct(const Key &key) noexcept
Definition hash-fct.H:931
auto shuffle(const C< T > &c)
Randomly shuffle a sequence.
std::string to_string(const time_t t, const std::string &format)
Format a time_t value into a string using format.
Definition ah-date.H:140
DynList< T > maps(const C &c, Op op)
Classic map operation.
T sum(const Container &container, const T &init=T{})
Compute sum of all elements.
STL namespace.
size_t snd_hash(const MyRecord &r) noexcept
Definition odhash.cc:109
size_t fst_hast(const MyRecord &r) noexcept
Definition odhash.cc:104
ODhashBucketStats count_odhash_bucket_states(const HashTable &tbl)
Definition odhash.cc:1154
bool operator()(const MyRecord &r1, const MyRecord &r2) const noexcept
Definition odhash.cc:96
MyRecord()
Definition odhash.cc:91
string value
Definition odhash.cc:90
MyRecord(size_t k)
Definition odhash.cc:93
size_t key
Definition odhash.cc:89
bool operator==(const MyRecord &r) const noexcept
Definition odhash.cc:101
MyRecord(size_t k, const string &v)
Definition odhash.cc:92
Open addressing hash table with double hashing.