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