Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
tpl_dynLhash_test.cc
Go to the documentation of this file.
1
2/*
3 Aleph_w
4
5 Data structures & Algorithms
6 version 2.0.0b
7 https://github.com/lrleon/Aleph-w
8
9 This file is part of Aleph-w library
10
11 Copyright (c) 2002-2026 Leandro Rabindranath Leon
12
13 Permission is hereby granted, free of charge, to any person obtaining a copy
14 of this software and associated documentation files (the "Software"), to deal
15 in the Software without restriction, including without limitation the rights
16 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 copies of the Software, and to permit persons to whom the Software is
18 furnished to do so, subject to the following conditions:
19
20 The above copyright notice and this permission notice shall be included in all
21 copies or substantial portions of the Software.
22
23 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 SOFTWARE.
30*/
31
32
38#include <gtest/gtest.h>
39#include <tpl_dynLhash.H>
40#include <string>
41#include <vector>
42#include <random>
43
44using namespace Aleph;
45
46// =============================================================================
47// Basic Functionality Tests
48// =============================================================================
49
50class DynLhashTableTest : public ::testing::Test
51{
52protected:
54};
55
57{
58 auto* ptr = table.insert(1, "one");
59
60 ASSERT_NE(ptr, nullptr);
61 EXPECT_EQ(*ptr, "one");
62
63 auto* found = table.search(1);
64 ASSERT_NE(found, nullptr);
65 EXPECT_EQ(*found, "one");
66}
67
69{
70 table.insert(1, "one");
71
72 auto* found = table.search(2);
73 EXPECT_EQ(found, nullptr);
74}
75
77{
78 table.insert(1, "one");
79 table.insert(2, "two");
80 table.insert(3, "three");
81
82 EXPECT_EQ(*table.search(1), "one");
83 EXPECT_EQ(*table.search(2), "two");
84 EXPECT_EQ(*table.search(3), "three");
85}
86
88{
89 auto* ptr = table.insert(1, "one");
90 table.insert(2, "two");
91
92 table.remove(ptr);
93
94 EXPECT_EQ(table.search(1), nullptr);
95 EXPECT_NE(table.search(2), nullptr);
96}
97
99{
100 table.insert(1, "one");
101
102 auto* ptr = table.search(1);
103 ASSERT_NE(ptr, nullptr);
104 *ptr = "ONE";
105
106 EXPECT_EQ(*table.search(1), "ONE");
107}
108
109// =============================================================================
110// Operator[] Tests
111// =============================================================================
112
114{
115 table[1] = "one";
116
117 auto* found = table.search(1);
118 ASSERT_NE(found, nullptr);
119 EXPECT_EQ(*found, "one");
120}
121
123{
124 table[1] = "one";
125 table[1] = "ONE";
126
127 EXPECT_EQ(*table.search(1), "ONE");
128}
129
131{
132 table.insert(1, "one");
133
134 std::string val = table[1];
135 EXPECT_EQ(val, "one");
136}
137
139{
140 table.insert(1, "one");
141
142 // Reading non-existent key should throw
144 std::string val = table[2];
145 }, std::invalid_argument);
146}
147
148// =============================================================================
149// Copy and Move Semantics Tests
150// =============================================================================
151
153{
154 table.insert(1, "one");
155 table.insert(2, "two");
156
158
159 EXPECT_EQ(*copy.search(1), "one");
160 EXPECT_EQ(*copy.search(2), "two");
161
162 // Modify original, copy should be independent
163 *table.search(1) = "ONE";
164 EXPECT_EQ(*copy.search(1), "one");
165}
166
168{
169 table.insert(1, "one");
170 table.insert(2, "two");
171
173 other.insert(3, "three");
174
175 other = table;
176
177 EXPECT_EQ(*other.search(1), "one");
178 EXPECT_EQ(*other.search(2), "two");
179 EXPECT_EQ(other.search(3), nullptr);
180}
181
183{
184 table.insert(1, "one");
185 table.insert(2, "two");
186
187 DynLhashTable<int, std::string> moved(std::move(table));
188
189 EXPECT_EQ(*moved.search(1), "one");
190 EXPECT_EQ(*moved.search(2), "two");
191}
192
194{
195 table.insert(1, "one");
196 table.insert(2, "two");
197
199 other = std::move(table);
200
201 EXPECT_EQ(*other.search(1), "one");
202 EXPECT_EQ(*other.search(2), "two");
203}
204
206{
207 table.insert(1, "one");
208
209 table = table; // Self-assignment
210
211 EXPECT_EQ(*table.search(1), "one");
212}
213
214// =============================================================================
215// String Key Tests
216// =============================================================================
217
219{
221
222 table.insert("one", 1);
223 table.insert("two", 2);
224 table.insert("three", 3);
225
226 EXPECT_EQ(*table.search("one"), 1);
227 EXPECT_EQ(*table.search("two"), 2);
228 EXPECT_EQ(*table.search("three"), 3);
229 EXPECT_EQ(table.search("four"), nullptr);
230}
231
233{
235
236 std::string longKey(1000, 'x');
237 table.insert(longKey, 42);
238
239 EXPECT_EQ(*table.search(longKey), 42);
240}
241
242// =============================================================================
243// Collision Handling Tests
244// =============================================================================
245
247{
248 // Use small table size to force collisions
250
251 // Insert values that will likely collide
252 for (int i = 0; i < 100; ++i)
253 table.insert(i, "val" + std::to_string(i));
254
255 // Verify all values are retrievable
256 for (int i = 0; i < 100; ++i)
257 EXPECT_EQ(*table.search(i), "val" + std::to_string(i));
258}
259
260// =============================================================================
261// Move Semantics for Values
262// =============================================================================
263
265{
267
268 std::string value = "hello";
269 table.insert(1, std::move(value));
270
271 EXPECT_EQ(*table.search(1), "hello");
272 // value should be moved-from (possibly empty)
273}
274
276{
278
279 std::string key = "key";
280 std::string value = "value";
281 table.insert(std::move(key), std::move(value));
282
283 EXPECT_EQ(*table.search("key"), "value");
284}
285
286// =============================================================================
287// Stress Tests
288// =============================================================================
289
291{
293
294 const int N = 10000;
295 for (int i = 0; i < N; ++i)
296 table.insert(i, i * 2);
297
298 for (int i = 0; i < N; ++i)
299 EXPECT_EQ(*table.search(i), i * 2);
300}
301
303{
305
306 const int N = 1000;
307 std::vector<int*> pointers;
308
309 // Insert
310 for (int i = 0; i < N; ++i)
311 pointers.push_back(table.insert(i, i));
312
313 // Remove half
314 for (int i = 0; i < N / 2; ++i)
315 table.remove(pointers[i]);
316
317 // Verify remaining
318 for (int i = N / 2; i < N; ++i)
319 EXPECT_EQ(*table.search(i), i);
320
321 // Verify removed are gone
322 for (int i = 0; i < N / 2; ++i)
323 EXPECT_EQ(table.search(i), nullptr);
324}
325
327{
329 std::map<int, int*> reference; // Track what's in the table
330
331 std::mt19937 rng(42);
332 std::uniform_int_distribution<> key_dist(0, 999);
333 std::uniform_int_distribution<> op_dist(0, 2);
334
335 for (int iter = 0; iter < 5000; ++iter)
336 {
337 int key = key_dist(rng);
338 int op = op_dist(rng);
339
340 if (op == 0) // Insert (only if not already present to avoid duplicates)
341 {
342 if (reference.count(key) == 0)
343 {
344 auto* ptr = table.insert(key, key * 2);
345 reference[key] = ptr;
346 }
347 }
348 else if (op == 1) // Search
349 {
350 auto* found = table.search(key);
351 if (reference.count(key))
352 {
353 ASSERT_NE(found, nullptr);
354 EXPECT_EQ(*found, key * 2);
355 }
356 }
357 else // Remove
358 {
359 if (reference.count(key))
360 {
361 table.remove(reference[key]);
362 reference.erase(key);
363 }
364 }
365 }
366
367 // Final verification
368 for (auto& [key, ptr] : reference)
369 {
370 auto* found = table.search(key);
371 ASSERT_NE(found, nullptr);
372 EXPECT_EQ(*found, key * 2);
373 }
374
375 // Clean up remaining elements
376 for (auto& [key, ptr] : reference)
377 table.remove(ptr);
378}
379
380// =============================================================================
381// Edge Cases
382// =============================================================================
383
385{
386 EXPECT_EQ(table.search(1), nullptr);
387 EXPECT_EQ(table.search(0), nullptr);
388 EXPECT_EQ(table.search(-1), nullptr);
389}
390
392{
393 table.insert(-1, "negative one");
394 table.insert(-100, "negative hundred");
395 table.insert(0, "zero");
396
397 EXPECT_EQ(*table.search(-1), "negative one");
398 EXPECT_EQ(*table.search(-100), "negative hundred");
399 EXPECT_EQ(*table.search(0), "zero");
400}
401
403{
404 auto* ptr1 = table.insert(1, "first");
405 auto* ptr2 = table.insert(1, "second"); // Same key, different value
406
407 // Behavior depends on implementation - usually both are stored
408 // or second replaces first. Search returns one of them.
409 auto* found = table.search(1);
410 ASSERT_NE(found, nullptr);
411 // At minimum, we should find SOMETHING for key 1
412
413 // Clean up both entries to avoid leaks
414 table.remove(ptr1);
415 table.remove(ptr2);
416}
417
418// =============================================================================
419// Custom Hash Function
420// =============================================================================
421
422size_t custom_hash(const int& key)
423{
424 return static_cast<size_t>(key * 31 + 17);
425}
426
428{
430
431 table.insert(1, "one");
432 table.insert(2, "two");
433
434 EXPECT_EQ(*table.search(1), "one");
435 EXPECT_EQ(*table.search(2), "two");
436}
437
438// =============================================================================
439// Swap Tests
440// =============================================================================
441
443{
446
447 table1.insert(1, "one");
448 table1.insert(2, "two");
449 table2.insert(10, "ten");
450
452
453 // table1 should now have table2's contents
454 EXPECT_EQ(table1.search(1), nullptr);
455 EXPECT_EQ(table1.search(2), nullptr);
456 EXPECT_EQ(*table1.search(10), "ten");
457
458 // table2 should now have table1's original contents
459 EXPECT_EQ(*table2.search(1), "one");
460 EXPECT_EQ(*table2.search(2), "two");
461 EXPECT_EQ(table2.search(10), nullptr);
462}
463
465{
468
469 table1.insert(1, "one");
470
472
473 EXPECT_EQ(table1.search(1), nullptr);
474 EXPECT_EQ(*table2.search(1), "one");
475}
476
477// =============================================================================
478// Post-Move State Tests
479// =============================================================================
480
482{
484 source.insert(1, "one");
485 source.insert(2, "two");
486
487 DynLhashTable<int, std::string> dest(std::move(source));
488
489 // Destination should have all the data
490 EXPECT_EQ(*dest.search(1), "one");
491 EXPECT_EQ(*dest.search(2), "two");
492
493 // Note: source is in moved-from state - only safe for destruction/assignment
494}
495
497{
499 source.insert(1, "one");
500 source.insert(2, "two");
501
503 dest.insert(10, "ten"); // Will be replaced
504
505 dest = std::move(source);
506
507 // Destination should have source's data
508 EXPECT_EQ(*dest.search(1), "one");
509 EXPECT_EQ(*dest.search(2), "two");
510 EXPECT_EQ(dest.search(10), nullptr); // Old data gone
511
512 // Note: source is in moved-from state - only safe for destruction/assignment
513}
514
515// =============================================================================
516// Complex Types Tests
517// =============================================================================
518
520{
521 std::string data;
523 static int copy_count;
524 static int move_count;
525
526 ComplexValue() : data(""), counter(0) {}
527 ComplexValue(const std::string& d, int c) : data(d), counter(c) {}
528
531
533 : data(std::move(other.data)), counter(other.counter) { ++move_count; }
534
536 {
537 data = other.data;
538 counter = other.counter;
539 ++copy_count;
540 return *this;
541 }
542
544 {
545 data = std::move(other.data);
546 counter = other.counter;
547 ++move_count;
548 return *this;
549 }
550
551 bool operator==(const ComplexValue& other) const
552 {
553 return data == other.data && counter == other.counter;
554 }
555};
556
559
561{
564
566
567 ComplexValue val("test", 42);
568 table.insert(1, std::move(val));
569
570 // Should have moved, not copied
572
573 auto* found = table.search(1);
574 ASSERT_NE(found, nullptr);
575 EXPECT_EQ(found->data, "test");
576 EXPECT_EQ(found->counter, 42);
577}
578
579// =============================================================================
580// Duplicate Keys Behavior Tests
581// =============================================================================
582
584{
586
587 auto* ptr1 = table.insert(1, "first");
588 auto* ptr2 = table.insert(1, "second");
589
590 // Both inserts should succeed and return different pointers
591 ASSERT_NE(ptr1, nullptr);
592 ASSERT_NE(ptr2, nullptr);
594
595 // Both values should be accessible via their pointers
596 EXPECT_EQ(*ptr1, "first");
597 EXPECT_EQ(*ptr2, "second");
598
599 // Clean up both entries
600 table.remove(ptr1);
601 table.remove(ptr2);
602}
603
605{
607
608 auto* ptr1 = table.insert(1, "first");
609 auto* ptr2 = table.insert(1, "second");
610
611 // Remove the second inserted (which search() finds first due to chain order)
612 table.remove(ptr2);
613
614 // ptr1 should still be valid and now searchable
615 EXPECT_EQ(*ptr1, "first");
616 auto* found = table.search(1);
617 ASSERT_NE(found, nullptr);
618 EXPECT_EQ(*found, "first");
619}
620
621// =============================================================================
622// Large Scale Stress Tests
623// =============================================================================
624
626{
628 const int N = 100000;
629
630 // Insert many elements
631 for (int i = 0; i < N; ++i)
632 table.insert(i, i * 3);
633
634 // Verify all elements
635 for (int i = 0; i < N; ++i)
636 {
637 auto* found = table.search(i);
638 ASSERT_NE(found, nullptr) << "Key " << i << " not found";
639 EXPECT_EQ(*found, i * 3) << "Wrong value for key " << i;
640 }
641}
642
644{
646 std::vector<int*> ptrs;
647 const int N = 10000;
648
649 // Insert first batch
650 for (int i = 0; i < N; ++i)
651 ptrs.push_back(table.insert(i, i));
652
653 // Remove odd indices, insert new values
654 for (int i = 1; i < N; i += 2)
655 {
656 table.remove(ptrs[i]);
657 ptrs[i] = nullptr;
658 }
659
660 // Insert replacement values
661 for (int i = N; i < N + N/2; ++i)
662 table.insert(i, i);
663
664 // Verify even indices remain
665 for (int i = 0; i < N; i += 2)
666 {
667 auto* found = table.search(i);
668 ASSERT_NE(found, nullptr);
669 EXPECT_EQ(*found, i);
670 }
671
672 // Verify odd indices removed
673 for (int i = 1; i < N; i += 2)
674 EXPECT_EQ(table.search(i), nullptr);
675
676 // Verify new values
677 for (int i = N; i < N + N/2; ++i)
678 {
679 auto* found = table.search(i);
680 ASSERT_NE(found, nullptr);
681 EXPECT_EQ(*found, i);
682 }
683}
684
685// =============================================================================
686// Main
687// =============================================================================
688
689int main(int argc, char **argv)
690{
691 ::testing::InitGoogleTest(&argc, argv);
692 return RUN_ALL_TESTS();
693}
int main()
Dynamic hash table mapping keys to records with separate chaining.
Record * search(const Key &key)
Search for a key in the table.
void remove(Record *record)
Remove an entry from the table.
Record * insert(const Key &key, const Record &record)
Insert a key-record pair into the table.
T & insert(const T &item)
Insert a new item by copy.
Definition htlist.H:1502
DynList & swap(DynList &l) noexcept
Swap this with l.
Definition htlist.H:1448
DynLhashTable< int, std::string > table
#define TEST(name)
static mt19937 rng
#define N
Definition fib.C:294
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
DynList< T > maps(const C &c, Op op)
Classic map operation.
ComplexValue & operator=(ComplexValue &&other) noexcept
bool operator==(const ComplexValue &other) const
static int move_count
ComplexValue(const ComplexValue &other)
ComplexValue(const std::string &d, int c)
ComplexValue(ComplexValue &&other) noexcept
ComplexValue & operator=(const ComplexValue &other)
static int copy_count
Dynamic hash table mapping keys to records with separate chaining.
TEST_F(DynLhashTableTest, InsertAndSearch)
size_t custom_hash(const int &key)