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