Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
olhash.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
43# include <tpl_olhash.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
87struct MyRecord
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 my_hash(const MyRecord & r) noexcept
105{
106 return dft_hash_fct(r.key);
107}
108
110{
112
113 EXPECT_EQ(tbl.size(), 0);
114 EXPECT_TRUE(tbl.is_empty());
115
116 for (size_t i = 0; i < 100; ++i)
117 {
118 EXPECT_EQ(tbl.size(), i);
119 tbl.emplace(i, to_string(i));
120 EXPECT_EQ(tbl.size(), i + 1);
121 auto ptr = tbl.search(MyRecord(i));
122 EXPECT_NE(ptr, nullptr);
123 EXPECT_EQ(ptr->key, i);
124 EXPECT_EQ(ptr->value, to_string(i));
125 }
126
127 for (size_t i = 0, n = tbl.size(); i < n; ++i)
128 {
129 auto ptr = tbl.search(i);
130 EXPECT_EQ(*ptr, i);
131 tbl.remove(MyRecord(ptr->key));
132 EXPECT_EQ(tbl.size(), n - i - 1);
133 EXPECT_EQ(tbl.search(i), nullptr);
134 EXPECT_FALSE(tbl.contains(MyRecord(i)));
135 }
136}
137
139{
141 auto *ptr = tbl.insert(5);
142 ASSERT_NE(ptr, nullptr);
143
144 auto *bucket = decltype(tbl)::key_to_bucket(ptr);
145 ASSERT_NE(bucket, nullptr);
146 EXPECT_EQ(bucket->key, 5);
147 EXPECT_EQ(bucket->status, decltype(tbl)::BUSY);
148
149 EXPECT_NO_THROW(tbl.remove(5));
150 EXPECT_EQ(tbl.search(5), nullptr);
151}
152
153// Test that removing a non-existent key throws and preserves table integrity
155{
157
158 // Insert some elements
159 const int num_elements = 50;
160 for (int i = 0; i < num_elements; ++i)
161 EXPECT_NE(tbl.insert(i * 2), nullptr); // Insert even numbers: 0, 2, 4, ..., 98
162
163 EXPECT_EQ(tbl.size(), num_elements);
164
165 // Try to remove keys that don't exist (odd numbers)
166 // This should throw domain_error BUT NOT corrupt the table
167 for (int i = 0; i < 10; ++i)
168 {
169 int non_existent_key = i * 2 + 1; // Odd numbers: 1, 3, 5, ..., 19
170 EXPECT_THROW(tbl.remove(non_existent_key), std::domain_error);
171 }
172
173 // Verify table size is unchanged
174 EXPECT_EQ(tbl.size(), num_elements)
175 << "Table size should not change after failed remove attempts";
176
177 // Verify all original elements are still findable
178 for (int i = 0; i < num_elements; ++i)
179 {
180 auto ptr = tbl.search(i * 2);
181 EXPECT_NE(ptr, nullptr)
182 << "Element " << i * 2 << " should still be in the table";
183 if (ptr)
184 EXPECT_EQ(*ptr, i * 2);
185 }
186
187 // Verify we can still remove elements normally
188 for (int i = 0; i < num_elements; ++i)
189 {
190 auto ptr = tbl.search(i * 2);
191 ASSERT_NE(ptr, nullptr);
192 EXPECT_NO_THROW(tbl.remove(*ptr));
193 EXPECT_EQ(tbl.size(), num_elements - i - 1);
194 }
195
196 EXPECT_TRUE(tbl.is_empty());
197}
198
199// Test remove with external key (key not from a bucket in the table)
201{
203
204 // Insert elements
205 for (int i = 0; i < 20; ++i)
206 EXPECT_NE(tbl.insert(i), nullptr);
207
208 EXPECT_EQ(tbl.size(), 20);
209
210 // Remove using external key (not a reference from the table)
211 int external_key = 10;
213 EXPECT_EQ(tbl.size(), 19);
214 EXPECT_EQ(tbl.search(10), nullptr);
215
216 // Verify other elements are intact
217 for (int i = 0; i < 20; ++i)
218 {
219 if (i == 10) continue;
220 EXPECT_NE(tbl.search(i), nullptr) << "Element " << i << " should still exist";
221 }
222}
223
224// Test remove with internal key (reference from the table bucket)
226{
228
229 // Insert elements
230 for (int i = 0; i < 20; ++i)
231 EXPECT_NE(tbl.insert(i), nullptr);
232
233 EXPECT_EQ(tbl.size(), 20);
234
235 // Remove using internal key (reference from search result)
236 auto ptr = tbl.search(10);
237 ASSERT_NE(ptr, nullptr);
238 EXPECT_NO_THROW(tbl.remove(*ptr)); // *ptr is internal reference
239 EXPECT_EQ(tbl.size(), 19);
240 EXPECT_EQ(tbl.search(10), nullptr);
241}
242
243// Test behavior with many collisions (stress test linear probing)
245{
246 // Use a small table to force many collisions
247 OLhashTable<int> tbl(17); // Small prime
248
249 // Insert enough elements to cause collisions
250 const int num_elements = 15;
251 for (int i = 0; i < num_elements; ++i)
252 EXPECT_NE(tbl.insert(i), nullptr);
253
254 EXPECT_EQ(tbl.size(), num_elements);
255
256 // Verify all elements are findable
257 for (int i = 0; i < num_elements; ++i)
258 EXPECT_NE(tbl.search(i), nullptr) << "Element " << i << " not found";
259
260 // Remove every other element
261 for (int i = 0; i < num_elements; i += 2)
262 {
263 auto ptr = tbl.search(i);
264 ASSERT_NE(ptr, nullptr);
265 tbl.remove(*ptr);
266 }
267
268 // Verify remaining elements are still findable
269 for (int i = 1; i < num_elements; i += 2)
270 EXPECT_NE(tbl.search(i), nullptr) << "Element " << i << " not found after removals";
271
272 // Verify removed elements are gone
273 for (int i = 0; i < num_elements; i += 2)
274 EXPECT_EQ(tbl.search(i), nullptr) << "Element " << i << " should be removed";
275}
276
277// Test that capacity doesn't change after failed removes
279{
281
282 // Insert elements
283 for (int i = 0; i < 50; ++i)
284 EXPECT_NE(tbl.insert(i * 2), nullptr); // Even numbers
285
286 const size_t original_capacity = tbl.capacity();
287 const size_t original_size = tbl.size();
288
289 // Try to remove many non-existent keys
290 for (int attempt = 0; attempt < 100; ++attempt)
291 {
292 int non_existent = attempt * 2 + 1; // Odd numbers don't exist
293 EXPECT_THROW(tbl.remove(non_existent), std::domain_error);
294 }
295
296 // Capacity should be unchanged
297 EXPECT_EQ(tbl.capacity(), original_capacity)
298 << "Capacity changed after failed remove attempts";
299
300 // Size should be unchanged
301 EXPECT_EQ(tbl.size(), original_size);
302
303 // All elements should still be findable
304 for (int i = 0; i < 50; ++i)
305 EXPECT_NE(tbl.search(i * 2), nullptr) << "Element " << i * 2 << " not found";
306}
307
308// ============================================================================
309// STRESS TESTS / FUZZING
310// ============================================================================
311
312// Fuzzing test: random operations with oracle verification
314{
315 // Use large table to avoid resize
316 OLhashTable<int> tbl(20000);
318
319 mt19937 rng(42);
322
323 const int num_operations = 8000;
324
325 for (int i = 0; i < num_operations; ++i)
326 {
327 int key = key_dist(rng);
328 int op = op_dist(rng);
329
330 switch (op)
331 {
332 case 0: // Insert
333 {
334 bool in_oracle = oracle.count(key) > 0;
335 auto ptr = tbl.insert(key);
336 if (ptr != nullptr)
337 {
338 EXPECT_FALSE(in_oracle) << "Insert succeeded but oracle had key " << key;
339 oracle.insert(key);
340 }
341 else
342 {
343 EXPECT_TRUE(in_oracle) << "Insert failed but oracle didn't have key " << key;
344 }
345 break;
346 }
347 case 1: // Remove
348 {
349 bool in_oracle = oracle.count(key) > 0;
350 if (in_oracle)
351 {
352 try
353 {
354 tbl.remove(key);
355 oracle.erase(key);
356 }
357 catch (const domain_error &)
358 {
359 FAIL() << "Remove threw for key " << key << " that was in oracle";
360 }
361 }
362 else
363 {
364 EXPECT_THROW(tbl.remove(key), domain_error);
365 }
366 break;
367 }
368 case 2: // Search
369 {
370 auto ptr = tbl.search(key);
371 bool in_oracle = oracle.count(key) > 0;
372 EXPECT_EQ(ptr != nullptr, in_oracle) << "Search mismatch for key " << key;
373 if (ptr)
374 EXPECT_EQ(*ptr, key);
375 break;
376 }
377 }
378
379 ASSERT_EQ(tbl.size(), oracle.size()) << "Size mismatch at operation " << i;
380 }
381
382 // Final verification
383 for (int key : oracle)
384 ASSERT_NE(tbl.search(key), nullptr) << "Final: key " << key << " missing";
385}
386
387// Stress test: fill and empty completely
389{
390 OLhashTable<int> tbl(1000);
391 const size_t target = tbl.capacity() - 1;
392
393 // Fill
394 for (size_t i = 0; i < target; ++i)
395 {
396 auto ptr = tbl.insert(static_cast<int>(i));
397 ASSERT_NE(ptr, nullptr) << "Insert failed at i=" << i;
398 }
399
400 EXPECT_EQ(tbl.size(), target);
401
402 // Verify
403 for (size_t i = 0; i < target; ++i)
404 ASSERT_NE(tbl.search(static_cast<int>(i)), nullptr);
405
406 // Empty in random order
407 vector<int> keys(target);
408 iota(keys.begin(), keys.end(), 0);
409
410 mt19937 rng(123);
411 shuffle(keys.begin(), keys.end(), rng);
412
413 for (size_t i = 0; i < target; ++i)
414 {
415 EXPECT_NO_THROW(tbl.remove(keys[i]));
416 EXPECT_EQ(tbl.size(), target - i - 1);
417 }
418
419 EXPECT_TRUE(tbl.is_empty());
420}
421
422// Stress test: linear probing with forced collisions
424{
425 // Bad hash that causes collisions
426 auto bad_hash = [](const int &) -> size_t { return 0; };
427
429
430 const int num_elements = 50;
431 for (int i = 0; i < num_elements; ++i)
432 {
433 auto ptr = tbl.insert(i);
434 ASSERT_NE(ptr, nullptr) << "Insert failed at i=" << i;
435 }
436
437 EXPECT_EQ(tbl.size(), num_elements);
438
439 // All elements should be findable
440 for (int i = 0; i < num_elements; ++i)
441 {
442 auto ptr = tbl.search(i);
443 ASSERT_NE(ptr, nullptr) << "Element " << i << " not found";
444 EXPECT_EQ(*ptr, i);
445 }
446
447 // Remove in order
448 for (int i = 0; i < num_elements; ++i)
449 {
450 EXPECT_NO_THROW(tbl.remove(i));
451 EXPECT_EQ(tbl.search(i), nullptr);
452 }
453
454 EXPECT_TRUE(tbl.is_empty());
455}
456
457// Stress test: insert/remove cycles
459{
461
462 const int cycles = 100;
463 const int elements_per_cycle = 50;
464
465 for (int cycle = 0; cycle < cycles; ++cycle)
466 {
467 for (int i = 0; i < elements_per_cycle; ++i)
468 {
469 int key = cycle * elements_per_cycle + i;
470 ASSERT_NE(tbl.insert(key), nullptr);
471 }
472
474
475 for (int i = 0; i < elements_per_cycle; ++i)
476 {
477 int key = cycle * elements_per_cycle + i;
478 EXPECT_NO_THROW(tbl.remove(key));
479 }
480
481 EXPECT_TRUE(tbl.is_empty());
482 }
483}
484
485// Stress test: resize operations
487{
489
491 mt19937 rng(999);
493
494 const int num_inserts = 5000;
495 for (int i = 0; i < num_inserts; ++i)
496 {
497 int key = key_dist(rng);
498 auto ptr = tbl.insert(key);
499 if (oracle.count(key) == 0 && ptr != nullptr)
500 oracle.insert(key);
501 }
502
503 EXPECT_EQ(tbl.size(), oracle.size());
504
505 // Verify all survived
506 for (int key : oracle)
507 ASSERT_NE(tbl.search(key), nullptr) << "Key " << key << " lost after resize";
508}
509
510// Fuzz test: interleaved operations
512{
513 // Use large table to avoid resize
514 OLhashTable<int> tbl(5000);
516
517 mt19937 rng(7777);
519 uniform_real_distribution<double> prob_dist(0.0, 1.0);
520
521 const int num_ops = 5000;
522
523 for (int i = 0; i < num_ops; ++i)
524 {
525 int key = key_dist(rng);
526 double prob = prob_dist(rng);
527
528 if (prob < 0.4)
529 {
530 auto ptr = tbl.insert(key);
531 if (ptr != nullptr)
532 oracle.insert(key);
533 }
534 else if (prob < 0.6)
535 {
536 if (oracle.count(key))
537 {
538 try
539 {
540 tbl.remove(key);
541 oracle.erase(key);
542 }
543 catch (const domain_error &)
544 {
545 FAIL() << "Remove threw for key " << key << " that was in oracle";
546 }
547 }
548 }
549 else
550 {
551 auto ptr = tbl.search(key);
552 EXPECT_EQ(ptr != nullptr, oracle.count(key) > 0);
553 }
554
555 if (i % 500 == 0)
556 ASSERT_EQ(tbl.size(), oracle.size());
557 }
558
559 EXPECT_EQ(tbl.size(), oracle.size());
560}
561
562// Stress test: with auto-resize enabled
564{
565 OLhashTable<int> tbl(10); // Small initial size, auto-resize enabled
567
568 mt19937 rng(333);
570
571 const int num_inserts = 3000;
572 for (int i = 0; i < num_inserts; ++i)
573 {
574 int key = key_dist(rng);
575 auto ptr = tbl.insert(key);
576 if (ptr != nullptr)
577 oracle.insert(key);
578 }
579
580 EXPECT_EQ(tbl.size(), oracle.size());
581
582 for (int key : oracle)
583 {
584 auto ptr = tbl.search(key);
585 ASSERT_NE(ptr, nullptr) << "Key " << key << " lost during resize";
586 }
587}
588
589// ============================================================================
590// DELETED CLEANUP TESTS (Knuth's optimization)
591// ============================================================================
592
593// Helper to count bucket states
595{
596 size_t empty = 0;
597 size_t busy = 0;
598 size_t deleted = 0;
599};
600
601template <typename HashTable>
603{
604 BucketStats stats;
605 for (size_t i = 0; i < tbl.capacity(); ++i)
606 {
607 switch (tbl.table[i].status)
608 {
609 case HashTable::EMPTY: ++stats.empty; break;
610 case HashTable::BUSY: ++stats.busy; break;
611 case HashTable::DELETED: ++stats.deleted; break;
612 }
613 }
614 return stats;
615}
616
617// Test: removing last element in chain should mark as EMPTY, not DELETED
619{
620 // Use bad hash to force all elements into same chain
621 auto bad_hash = [](const int &) -> size_t { return 0; };
623
624 // Insert elements: they will all collide at index 0
625 // Chain: [0] -> [1] -> [2] -> [3] -> [4]
626 for (int i = 0; i < 5; ++i)
627 ASSERT_NE(tbl.insert(i), nullptr);
628
630 EXPECT_EQ(before.busy, 5);
631 EXPECT_EQ(before.deleted, 0);
632
633 // Remove last element (4) - should become EMPTY since next is EMPTY
634 tbl.remove(4);
635
637 EXPECT_EQ(after.busy, 4);
638 EXPECT_EQ(after.deleted, 0) << "Last element should become EMPTY, not DELETED";
639 EXPECT_EQ(after.empty, before.empty + 1);
640
641 // Verify remaining elements are still findable
642 for (int i = 0; i < 4; ++i)
643 EXPECT_NE(tbl.search(i), nullptr) << "Element " << i << " should still exist";
644
645 EXPECT_EQ(tbl.search(4), nullptr);
646}
647
648// Test: backward propagation of EMPTY status
650{
651 // Use bad hash to force chain
652 auto bad_hash = [](const int &) -> size_t { return 0; };
654
655 // Insert chain: positions 0,1,2,3,4
656 for (int i = 0; i < 5; ++i)
657 ASSERT_NE(tbl.insert(i), nullptr);
658
659 // Remove middle elements first (they become DELETED)
660 tbl.remove(3); // position 3 -> DELETED (next is BUSY at 4)
661 tbl.remove(2); // position 2 -> DELETED (next is DELETED at 3)
662
664 EXPECT_EQ(mid_stats.busy, 3); // 0, 1, 4 are BUSY
665 EXPECT_EQ(mid_stats.deleted, 2); // 2, 3 are DELETED
666
667 // Now remove element 4 (last in chain)
668 // This should trigger backward cleanup: 4->EMPTY, 3->EMPTY, 2->EMPTY
669 tbl.remove(4);
670
672 EXPECT_EQ(final_stats.busy, 2); // 0, 1 are BUSY
673 EXPECT_EQ(final_stats.deleted, 0) << "All trailing DELETED should become EMPTY";
674
675 // Verify remaining elements
676 EXPECT_NE(tbl.search(0), nullptr);
677 EXPECT_NE(tbl.search(1), nullptr);
678 EXPECT_EQ(tbl.search(2), nullptr);
679 EXPECT_EQ(tbl.search(3), nullptr);
680 EXPECT_EQ(tbl.search(4), nullptr);
681}
682
683// Test: DELETED in middle of chain stays DELETED
685{
686 auto bad_hash = [](const int &) -> size_t { return 0; };
688
689 // Insert chain: 0,1,2,3,4
690 for (int i = 0; i < 5; ++i)
691 ASSERT_NE(tbl.insert(i), nullptr);
692
693 // Remove element in middle (2) - should stay DELETED because 3,4 follow
694 tbl.remove(2);
695
696 auto stats = count_bucket_states(tbl);
697 EXPECT_EQ(stats.busy, 4);
698 EXPECT_EQ(stats.deleted, 1) << "Middle element should stay DELETED";
699
700 // All other elements still findable
701 for (int i = 0; i < 5; ++i)
702 {
703 if (i == 2)
704 EXPECT_EQ(tbl.search(i), nullptr);
705 else
706 EXPECT_NE(tbl.search(i), nullptr);
707 }
708}
709
710// Test: no DELETED accumulation after many insert/remove cycles
712{
714
715 // Perform many insert/remove cycles
716 const int cycles = 50;
717 const int elements = 30;
718
719 for (int cycle = 0; cycle < cycles; ++cycle)
720 {
721 // Insert
722 for (int i = 0; i < elements; ++i)
723 tbl.insert(cycle * 1000 + i);
724
725 // Remove all
726 for (int i = 0; i < elements; ++i)
727 tbl.remove(cycle * 1000 + i);
728 }
729
730 // After all cycles, table should be mostly EMPTY with no DELETED
731 auto stats = count_bucket_states(tbl);
732 EXPECT_EQ(stats.busy, 0);
733 EXPECT_EQ(stats.deleted, 0) << "Should have no DELETED after complete removal";
734 EXPECT_TRUE(tbl.is_empty());
735}
736
737// Test: cleanup with wrap-around at table boundary
739{
740 // Hash function that puts elements near end of table
741 OLhashTable<int> tbl(17); // Prime size
742
743 // Force insertion near end by using specific values
744 // that hash near len-1
745 auto near_end_hash = [](const int &k) -> size_t {
746 return static_cast<size_t>(k + 15); // Will wrap around
747 };
748
750
751 // Insert elements that will wrap around
752 for (int i = 0; i < 5; ++i)
753 ASSERT_NE(tbl2.insert(i), nullptr);
754
755 EXPECT_EQ(tbl2.size(), 5);
756
757 // Remove all - should handle wrap-around correctly
758 for (int i = 4; i >= 0; --i)
759 {
760 EXPECT_NO_THROW(tbl2.remove(i));
761 }
762
763 auto stats = count_bucket_states(tbl2);
764 EXPECT_EQ(stats.deleted, 0) << "Wrap-around cleanup should leave no DELETED";
765 EXPECT_TRUE(tbl2.is_empty());
766}
767
768// Stress test: verify no DELETED accumulation with random operations
770{
773
774 mt19937 rng(12345);
777
778 const int num_ops = 5000;
779
780 for (int i = 0; i < num_ops; ++i)
781 {
782 int key = key_dist(rng);
783 double op = op_dist(rng);
784
785 if (op < 0.5)
786 {
787 auto ptr = tbl.insert(key);
788 if (ptr) oracle.insert(key);
789 }
790 else if (oracle.count(key))
791 {
792 tbl.remove(key);
793 oracle.erase(key);
794 }
795 }
796
797 auto stats = count_bucket_states(tbl);
798
799 // The number of DELETED should be minimal compared to capacity
800 // With Knuth's cleanup, DELETED only accumulates in middle of chains
801 double deleted_ratio = static_cast<double>(stats.deleted) / tbl.capacity();
803 << "DELETED ratio should be low with cleanup. Got "
804 << stats.deleted << "/" << tbl.capacity();
805
806 // Verify integrity
807 EXPECT_EQ(tbl.size(), oracle.size());
808 for (int key : oracle)
809 EXPECT_NE(tbl.search(key), nullptr);
810}
811
812// ============================================================================
813// COPY/MOVE SEMANTICS TESTS
814// ============================================================================
815
817{
819 for (int i = 0; i < 50; ++i)
820 original.insert(i);
821
823
824 EXPECT_EQ(copy.size(), original.size());
825 EXPECT_EQ(copy.capacity(), original.capacity());
826
827 // Verify all elements exist in both
828 for (int i = 0; i < 50; ++i)
829 {
830 EXPECT_NE(original.search(i), nullptr);
831 EXPECT_NE(copy.search(i), nullptr);
832 }
833
834 // Modify copy, original should be unchanged
835 copy.remove(25);
836 EXPECT_EQ(copy.search(25), nullptr);
837 EXPECT_NE(original.search(25), nullptr);
838}
839
841{
843 for (int i = 0; i < 50; ++i)
844 original.insert(i);
845
846 const size_t orig_size = original.size();
847 const size_t orig_cap = original.capacity();
848
849 OLhashTable<int> moved(std::move(original));
850
851 EXPECT_EQ(moved.size(), orig_size);
852 EXPECT_EQ(moved.capacity(), orig_cap);
853
854 // Verify all elements exist in moved
855 for (int i = 0; i < 50; ++i)
856 EXPECT_NE(moved.search(i), nullptr);
857}
858
860{
862 for (int i = 0; i < 50; ++i)
863 original.insert(i);
864
866 copy.insert(999);
867
868 copy = original;
869
870 EXPECT_EQ(copy.size(), original.size());
871
872 for (int i = 0; i < 50; ++i)
873 EXPECT_NE(copy.search(i), nullptr);
874
875 EXPECT_EQ(copy.search(999), nullptr); // Old element gone
876}
877
879{
881 for (int i = 0; i < 50; ++i)
882 original.insert(i);
883
884 const size_t orig_size = original.size();
885
886 OLhashTable<int> target(10);
887 target.insert(999);
888
889 target = std::move(original);
890
891 EXPECT_EQ(target.size(), orig_size);
892
893 for (int i = 0; i < 50; ++i)
894 EXPECT_NE(target.search(i), nullptr);
895}
896
898{
900 for (int i = 0; i < 50; ++i)
901 tbl.insert(i);
902
903 tbl = tbl; // Self-assignment
904
905 EXPECT_EQ(tbl.size(), 50);
906 for (int i = 0; i < 50; ++i)
907 EXPECT_NE(tbl.search(i), nullptr);
908}
909
910// ============================================================================
911// DELETED SLOT REUSE TESTS
912// ============================================================================
913
915{
916 auto bad_hash = [](const int &) -> size_t { return 0; };
918
919 // Insert chain: 0,1,2,3,4 at positions 0,1,2,3,4
920 for (int i = 0; i < 5; ++i)
921 tbl.insert(i);
922
923 // Remove middle element (2) - becomes DELETED
924 tbl.remove(2);
925
927 EXPECT_EQ(before.deleted, 1);
928
929 // Insert new element - should reuse DELETED slot at position 2
930 tbl.insert(100);
931
933 EXPECT_EQ(after.deleted, 0) << "DELETED slot should be reused";
934 EXPECT_EQ(after.busy, 5);
935
936 // All elements should be findable
937 EXPECT_NE(tbl.search(0), nullptr);
938 EXPECT_NE(tbl.search(1), nullptr);
939 EXPECT_EQ(tbl.search(2), nullptr); // Was removed
940 EXPECT_NE(tbl.search(3), nullptr);
941 EXPECT_NE(tbl.search(4), nullptr);
942 EXPECT_NE(tbl.search(100), nullptr); // New element
943}
944
945// ============================================================================
946// SEARCH_OR_INSERT AND CONTAINS_OR_INSERT TESTS
947// ============================================================================
948
950{
952
953 auto ptr = tbl.search_or_insert(42);
954 ASSERT_NE(ptr, nullptr);
955 EXPECT_EQ(*ptr, 42);
956 EXPECT_EQ(tbl.size(), 1);
957}
958
960{
962 tbl.insert(42);
963
964 auto ptr = tbl.search_or_insert(42);
965 ASSERT_NE(ptr, nullptr);
966 EXPECT_EQ(*ptr, 42);
967 EXPECT_EQ(tbl.size(), 1); // No duplicate
968}
969
971{
973
974 auto [ptr, existed] = tbl.contains_or_insert(42);
975 ASSERT_NE(ptr, nullptr);
976 EXPECT_EQ(*ptr, 42);
978 EXPECT_EQ(tbl.size(), 1);
979}
980
982{
984 tbl.insert(42);
985
986 auto [ptr, existed] = tbl.contains_or_insert(42);
987 ASSERT_NE(ptr, nullptr);
988 EXPECT_EQ(*ptr, 42);
990 EXPECT_EQ(tbl.size(), 1);
991}
992
993// ============================================================================
994// REHASH TESTS
995// ============================================================================
996
998{
1001
1002 for (int i = 0; i < 50; ++i)
1003 {
1004 tbl.insert(i);
1005 oracle.insert(i);
1006 }
1007
1008 // Remove half to create DELETED slots
1009 for (int i = 0; i < 50; i += 2)
1010 {
1011 tbl.remove(i);
1012 oracle.erase(i);
1013 }
1014
1016 (void)before;
1017 // Some DELETED may exist (those in middle of chains)
1018
1019 // Manual rehash should eliminate all DELETED
1020 tbl.rehash();
1021
1023 EXPECT_EQ(after.deleted, 0) << "Rehash should eliminate all DELETED";
1024 EXPECT_EQ(after.busy, oracle.size());
1025
1026 // All remaining elements should be findable
1027 for (int key : oracle)
1028 EXPECT_NE(tbl.search(key), nullptr);
1029}
1030
1032{
1034
1035 for (int i = 0; i < 30; ++i)
1036 tbl.insert(i);
1037
1038 const size_t old_cap = tbl.capacity();
1039 tbl.resize(200);
1040
1041 EXPECT_GT(tbl.capacity(), old_cap);
1042 EXPECT_EQ(tbl.size(), 30);
1043
1044 for (int i = 0; i < 30; ++i)
1045 EXPECT_NE(tbl.search(i), nullptr);
1046}
1047
1049{
1050 OLhashTable<int> tbl(200);
1051
1052 for (int i = 0; i < 30; ++i)
1053 tbl.insert(i);
1054
1055 tbl.resize(50);
1056
1057 EXPECT_EQ(tbl.size(), 30);
1058
1059 for (int i = 0; i < 30; ++i)
1060 EXPECT_NE(tbl.search(i), nullptr);
1061}
1062
1063// ============================================================================
1064// EDGE CASES
1065// ============================================================================
1066
1068{
1069 OLhashTable<int> tbl(100);
1070
1071 EXPECT_TRUE(tbl.is_empty());
1072 EXPECT_EQ(tbl.size(), 0);
1073 EXPECT_EQ(tbl.search(42), nullptr);
1074 EXPECT_FALSE(tbl.has(42));
1075 EXPECT_FALSE(tbl.contains(42));
1076 EXPECT_THROW(tbl.remove(42), std::domain_error);
1077}
1078
1080{
1081 OLhashTable<int> tbl(100);
1082
1083 tbl.insert(42);
1084 EXPECT_EQ(tbl.size(), 1);
1085 EXPECT_NE(tbl.search(42), nullptr);
1086
1087 tbl.remove(42);
1088 EXPECT_EQ(tbl.size(), 0);
1089 EXPECT_TRUE(tbl.is_empty());
1090 EXPECT_EQ(tbl.search(42), nullptr);
1091
1092 // Table should be clean (no DELETED for single element at end)
1093 auto stats = count_bucket_states(tbl);
1094 EXPECT_EQ(stats.deleted, 0);
1095}
1096
1098{
1099 OLhashTable<int> tbl(100);
1100
1101 auto first = tbl.insert(42);
1102 ASSERT_NE(first, nullptr);
1103
1104 auto second = tbl.insert(42);
1105 EXPECT_EQ(second, nullptr) << "Duplicate insert should return nullptr";
1106
1107 EXPECT_EQ(tbl.size(), 1);
1108}
1109
1111{
1112 OLhashTable<int> tbl(100);
1113
1114 EXPECT_FALSE(tbl.has(42));
1115 EXPECT_FALSE(tbl.contains(42));
1116
1117 tbl.insert(42);
1118
1119 EXPECT_TRUE(tbl.has(42));
1120 EXPECT_TRUE(tbl.contains(42));
1121 EXPECT_FALSE(tbl.has(43));
1122 EXPECT_FALSE(tbl.contains(43));
1123}
1124
1126{
1127 OLhashTable<int> tbl(100);
1128 tbl.insert(42);
1129
1131 int& ref = tbl.find(42);
1132 EXPECT_EQ(ref, 42);
1133 });
1134
1135 EXPECT_THROW(tbl.find(999), std::domain_error);
1136}
1137
1138// ============================================================================
1139// ITERATOR TESTS
1140// ============================================================================
1141
1143{
1144 OLhashTable<int> tbl(100);
1146
1147 for (int i = 0; i < 50; ++i)
1148 {
1149 tbl.insert(i);
1150 oracle.insert(i);
1151 }
1152
1153 set<int> visited;
1154 for (auto it = tbl.get_it(); it.has_curr(); it.next())
1155 visited.insert(it.get_curr());
1156
1157 EXPECT_EQ(visited, oracle);
1158}
1159
1161{
1162 OLhashTable<int> tbl(100);
1163
1164 auto it = tbl.get_it();
1165 EXPECT_FALSE(it.has_curr());
1166}
1167
1169{
1170 OLhashTable<int> tbl(100);
1171 tbl.insert(42);
1172
1173 auto it = tbl.get_it();
1174 ASSERT_TRUE(it.has_curr());
1175 EXPECT_EQ(it.get_curr(), 42);
1176
1177 it.next();
1178 EXPECT_FALSE(it.has_curr());
1179}
1180
1182{
1183 OLhashTable<int> tbl(100);
1184
1185 for (int i = 0; i < 10; ++i)
1186 tbl.insert(i);
1187
1188 // Delete all elements via iterator
1189 auto it = tbl.get_it();
1190 while (it.has_curr())
1191 it.del();
1192
1193 EXPECT_TRUE(tbl.is_empty());
1194}
1195
1196// ============================================================================
1197// STATS TEST
1198// ============================================================================
1199
1201{
1202 auto bad_hash = [](const int &) -> size_t { return 0; };
1204
1205 // Insert chain
1206 for (int i = 0; i < 10; ++i)
1207 tbl.insert(i);
1208
1209 // Remove some in middle
1210 tbl.remove(3);
1211 tbl.remove(5);
1212 tbl.remove(7);
1213
1214 auto stats = tbl.stats();
1215
1216 EXPECT_EQ(stats.num_busy, 7);
1217 // Some DELETED may exist depending on cleanup
1218 EXPECT_EQ(stats.num_busy + stats.num_deleted + stats.num_empty, tbl.capacity());
1219}
1220
1221// ============================================================================
1222// FUNCTIONAL METHODS TEST
1223// ============================================================================
1224
1226{
1227 OLhashTable<int> tbl(100);
1228 for (int i = 0; i < 10; ++i)
1229 tbl.insert(i);
1230
1231 int sum = 0;
1232 tbl.for_each([&sum](int x) { sum += x; });
1233
1234 EXPECT_EQ(sum, 45); // 0+1+2+...+9
1235}
1236
1238{
1239 OLhashTable<int> tbl(100);
1240 for (int i = 0; i < 10; ++i)
1241 tbl.insert(i * 2); // Even numbers
1242
1243 EXPECT_TRUE(tbl.all([](int x) { return x % 2 == 0; }));
1244 EXPECT_FALSE(tbl.all([](int x) { return x > 5; }));
1245}
1246
1248{
1249 OLhashTable<int> tbl(100);
1250 for (int i = 0; i < 10; ++i)
1251 tbl.insert(i);
1252
1253 EXPECT_TRUE(tbl.exists([](int x) { return x == 5; }));
1254 EXPECT_FALSE(tbl.exists([](int x) { return x == 100; }));
1255}
1256
1258{
1259 OLhashTable<int> tbl(100);
1260 for (int i = 0; i < 10; ++i)
1261 tbl.insert(i);
1262
1263 auto evens = tbl.filter([](int x) { return x % 2 == 0; });
1264
1265 EXPECT_EQ(evens.size(), 5);
1266}
Open addressing hash table with linear probing collision resolution.
Definition tpl_olhash.H:170
Key * search(const Key &key) const noexcept
Finds the key and returns the associated record if key is find inside the table; otherwise,...
Definition tpl_olhash.H:353
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
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 my_hash(const MyRecord &r) noexcept
Definition olhash.cc:104
BucketStats count_bucket_states(const HashTable &tbl)
Definition olhash.cc:602
size_t deleted
Definition olhash.cc:598
size_t empty
Definition olhash.cc:596
size_t busy
Definition olhash.cc:597
bool operator()(const MyRecord &r1, const MyRecord &r2) const noexcept
Definition odhash.cc:97
MyRecord()
Definition olhash.cc:91
string value
Definition odhash.cc:91
MyRecord(size_t k)
Definition olhash.cc:93
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 olhash.cc:92
int keys[]
static int * k
gsl_rng * r
Open addressing hash table with linear probing.