Procházet zdrojové kódy

Correct invalid key handling for probing hash map

Kajetan Johannes Hammerle před 2 týdny
rodič
revize
9a03850d5d

+ 1 - 1
include/core/data/Array.hpp

@@ -50,7 +50,7 @@ namespace Core {
         }
 
         template<typename String>
-        check_return Error toString(String& s) const {
+        CError toString(String& s) const {
             return Core::toString(s, *this);
         }
     };

+ 3 - 3
include/core/data/ArrayList.hpp

@@ -63,7 +63,7 @@ namespace Core {
         }
 
         template<typename... Args>
-        check_return Error put(T*& t, Args&&... args) {
+        CError put(T*& t, Args&&... args) {
             if(length >= N) {
                 return ErrorCode::CAPACITY_REACHED;
             }
@@ -72,7 +72,7 @@ namespace Core {
         }
 
         template<typename... Args>
-        check_return Error add(Args&&... args) {
+        CError add(Args&&... args) {
             T* t = nullptr;
             return put(t, Core::forward<Args>(args)...);
         }
@@ -110,7 +110,7 @@ namespace Core {
         }
 
         template<typename String>
-        check_return Error toString(String& s) const {
+        CError toString(String& s) const {
             return Core::toString(s, *this);
         }
 

+ 3 - 3
include/core/data/BitArray.hpp

@@ -16,7 +16,7 @@ namespace Core {
         BitArray& operator=(const BitArray& other) = delete;
         BitArray& operator=(BitArray&& other);
 
-        check_return Error copyFrom(const BitArray& other);
+        CError copyFrom(const BitArray& other);
 
         BitArray& set(u64 index, u64 value);
         u64 get(u64 index) const;
@@ -27,12 +27,12 @@ namespace Core {
 
         i64 select(u64 index) const;
 
-        check_return Error resize(u64 newLength, u64 newBits);
+        CError resize(u64 newLength, u64 newBits);
 
         void fill(u64 value);
 
         template<typename String>
-        check_return Error toString(String& s) const {
+        CError toString(String& s) const {
             CORE_RETURN_ERROR(s.append("["));
             u64 length = getLength();
             if(length > 0) {

+ 2 - 2
include/core/data/Components.hpp

@@ -60,7 +60,7 @@ namespace Core {
         };
 
         template<typename... Args>
-        check_return Error put(T*& t, Entity ent, Args&&... args) {
+        CError put(T*& t, Entity ent, Args&&... args) {
             size_t index = components.getLength();
             size_t* indexP = nullptr;
             CORE_RETURN_ERROR(entityToIndex.tryEmplace(indexP, ent, index));
@@ -70,7 +70,7 @@ namespace Core {
         }
 
         template<typename... Args>
-        check_return Error add(Entity e, Args&&... args) {
+        CError add(Entity e, Args&&... args) {
             T* t = nullptr;
             return put(t, e, Core::forward<Args>(args)...);
         }

+ 4 - 4
include/core/data/HashMap.hpp

@@ -22,7 +22,7 @@ namespace Core {
             }
 
             template<typename String>
-            check_return Error toString(String& s) const {
+            CError toString(String& s) const {
                 CORE_RETURN_ERROR(s.append(key));
                 CORE_RETURN_ERROR(s.append(" = "));
                 return s.append(value);
@@ -117,7 +117,7 @@ namespace Core {
         List<NodePointerList> nodePointers{};
 
     public:
-        check_return Error copyFrom(const HashMap& other) {
+        CError copyFrom(const HashMap& other) {
             HashMap copy;
             for(const auto& en : other) {
                 copy.add(en.getKey(), en.value);
@@ -145,7 +145,7 @@ namespace Core {
         }
 
         template<typename... Args>
-        check_return Error tryEmplace(V*& v, const K& key, Args&&... args) {
+        CError tryEmplace(V*& v, const K& key, Args&&... args) {
             rehash(nodes.getLength() + 1);
             size_t h = hashIndex(key);
             v = searchList(key, h);
@@ -239,7 +239,7 @@ namespace Core {
         }
 
         template<typename String>
-        check_return Error toString(String& s) const {
+        CError toString(String& s) const {
             return Core::toString(s, *this);
         }
 

+ 1 - 1
include/core/data/LinkedList.hpp

@@ -127,7 +127,7 @@ namespace Core {
         }
 
         template<typename String>
-        check_return Error toString(String& s) const {
+        CError toString(String& s) const {
             return Core::toString(s, *this);
         }
 

+ 1 - 1
include/core/data/List.hpp

@@ -159,7 +159,7 @@ namespace Core {
         }
 
         template<typename String>
-        check_return Error toString(String& s) const {
+        CError toString(String& s) const {
             return Core::toString(s, *this);
         }
 

+ 44 - 39
include/core/data/ProbingHashMap.hpp

@@ -1,6 +1,8 @@
 #ifndef CORE_PROBING_HPPASHMAP_HPP
 #define CORE_PROBING_HPPASHMAP_HPP
 
+#include <stdio.h>
+
 #include "core/data/List.hpp"
 #include "core/utils/AlignedData.hpp"
 #include "core/utils/ArrayString.hpp"
@@ -40,6 +42,8 @@ namespace Core {
         };
 
     private:
+        static constexpr K INVALID = emptyValue<K>();
+
         template<typename Value, typename R, R (*A)(const K&, Value&)>
         class Iterator final {
             const K* currentKey;
@@ -69,7 +73,8 @@ namespace Core {
 
         private:
             void skip() {
-                while(currentKey != endKey && *currentKey == emptyValue<K>()) {
+                while(currentKey != endKey && !((*currentKey != INVALID) !=
+                                                (currentKey + 1 == endKey))) {
                     ++currentKey;
                     ++currentValue;
                 }
@@ -126,7 +131,6 @@ namespace Core {
     private:
         List<K> keys{};
         V* values = nullptr;
-        V* invalidKeyValue = nullptr;
         size_t entries = 0;
 
     public:
@@ -143,13 +147,19 @@ namespace Core {
         }
 
         ~ProbingHashMap() {
-            for(size_t i = 0; i < keys.getLength(); i++) {
-                if(keys[i] != emptyValue<K>()) {
-                    values[i].~V();
+            size_t length = keys.getLength();
+            if(length > 0) {
+                length--;
+                for(size_t i = 0; i < length; i++) {
+                    if(keys[i] != INVALID) {
+                        values[i].~V();
+                    }
+                }
+                if(keys[length] == INVALID) {
+                    values[length].~V();
                 }
             }
             delete[] reinterpret_cast<AlignedType<V>*>(values);
-            delete invalidKeyValue;
         }
 
         ProbingHashMap& operator=(ProbingHashMap other) {
@@ -163,12 +173,20 @@ namespace Core {
             }
             ProbingHashMap<K, V> map;
             size_t l =
-                1lu << Math::roundUpLog2(Core::Math::max(minCapacity, 8lu));
-            map.keys.resize(l, emptyValue<K>());
+                (1lu << Math::roundUpLog2(Math::max(minCapacity, 8lu))) + 1;
+            map.keys.resize(l, INVALID);
+            map.keys[map.keys.getLength() - 1] = K();
             map.values = reinterpret_cast<V*>(new(noThrow) AlignedType<V>[l]);
-            for(size_t i = 0; i < keys.getLength(); i++) {
-                if(keys[i] != emptyValue<K>()) {
-                    map.add(keys[i], values[i]);
+            size_t length = keys.getLength();
+            if(length > 0) {
+                length--;
+                for(size_t i = 0; i < length; i++) {
+                    if(keys[i] != INVALID) {
+                        map.add(keys[i], values[i]);
+                    }
+                }
+                if(keys[length] == INVALID) {
+                    map.add(keys[length], values[length]);
                 }
             }
             swap(map);
@@ -176,14 +194,6 @@ namespace Core {
 
         template<typename... Args>
         bool tryEmplace(V*& v, const K& key, Args&&... args) {
-            if(key == emptyValue<K>()) {
-                if(invalidKeyValue != nullptr) {
-                    return false;
-                }
-                invalidKeyValue = new(noThrow) V(Core::forward<Args>(args)...);
-                v = invalidKeyValue;
-                return true;
-            }
             size_t index = searchSlot(key);
             if(keys[index] == key) {
                 return false;
@@ -196,21 +206,12 @@ namespace Core {
 
         template<typename VA>
         V& put(const K& key, VA&& value) {
-            if(key == emptyValue<K>()) {
-                if(invalidKeyValue != nullptr) {
-                    *invalidKeyValue = Core::forward<VA>(value);
-                } else {
-                    invalidKeyValue = new(noThrow) V(Core::forward<VA>(value));
-                }
-                return *invalidKeyValue;
-            }
             size_t index = searchSlot(key);
             if(keys[index] == key) {
-                values[index] = Core::forward<VA>(value);
-            } else {
-                new(values + index) V(Core::forward<VA>(value));
-                entries++;
+                return (values[index] = Core::forward<VA>(value));
             }
+            new(values + index) V(Core::forward<VA>(value));
+            entries++;
             keys[index] = key;
             return values[index];
         }
@@ -268,7 +269,7 @@ namespace Core {
         }
 
         template<typename String>
-        check_return Error toString(String& s) const {
+        CError toString(String& s) const {
             return Core::toString(s, *this);
         }
 
@@ -283,12 +284,15 @@ namespace Core {
             size_t rehashFactor = 2;
             while(true) {
                 rehash(entries * rehashFactor + 1);
+                if(key == INVALID) {
+                    return keys.getLength() - 1;
+                }
                 size_t baseHash = hashCode(key) * 514685581u;
-                size_t end = keys.getLength() - 1;
+                size_t end = keys.getLength() - 2;
                 // rehash on bad clustering
                 for(size_t i = 0; i <= 5; i++) {
                     size_t hash = (baseHash + i) & end;
-                    if(keys[hash] == emptyValue<K>() || keys[hash] == key) {
+                    if(keys[hash] == INVALID || keys[hash] == key) {
                         return hash;
                     }
                 }
@@ -298,17 +302,18 @@ namespace Core {
 
         template<typename Value>
         Value* searchValue(const K& key) const {
-            if(key == Core::emptyValue<K>()) {
-                return invalidKeyValue;
-            }
             if(keys.getLength() != 0) {
+                if(key == INVALID) {
+                    size_t i = keys.getLength() - 1;
+                    return keys[i] == INVALID ? values + i : nullptr;
+                }
                 size_t baseHash = hashCode(key) * 514685581u;
-                size_t end = keys.getLength() - 1;
+                size_t end = keys.getLength() - 2;
                 for(size_t i = 0; i <= end; i++) [[unlikely]] {
                     size_t hash = (baseHash + i) & end;
                     if(keys[hash] == key) [[likely]] {
                         return values + hash;
-                    } else if(keys[hash] == emptyValue<K>()) {
+                    } else if(keys[hash] == INVALID) {
                         return nullptr;
                     }
                 }

+ 5 - 7
include/core/data/RingBuffer.hpp

@@ -7,9 +7,7 @@
 namespace Core {
     template<typename T, size_t N>
     class RingBuffer final {
-        static_assert(N > 0, "RingBuffer size must be positive");
-
-        AlignedType<T> data[static_cast<u64>(N)];
+        AlignedType<T> data[N];
         size_t writeIndex;
         size_t readIndex;
         size_t values;
@@ -49,7 +47,7 @@ namespace Core {
         }
 
         template<typename... Args>
-        check_return Error put(T*& t, Args&&... args) {
+        CError put(T*& t, Args&&... args) {
             if(getLength() >= N) {
                 return ErrorCode::CAPACITY_REACHED;
             }
@@ -60,7 +58,7 @@ namespace Core {
         }
 
         template<typename... Args>
-        check_return Error add(Args&&... args) {
+        CError add(Args&&... args) {
             T* t = nullptr;
             return put(t, Core::forward<Args>(args)...);
         }
@@ -90,7 +88,7 @@ namespace Core {
             values = 0;
         }
 
-        check_return Error remove() {
+        CError remove() {
             if(!canRemove()) {
                 return ErrorCode::INVALID_STATE;
             }
@@ -101,7 +99,7 @@ namespace Core {
         }
 
         template<typename String>
-        check_return Error toString(String& s) const {
+        CError toString(String& s) const {
             CORE_RETURN_ERROR(s.append("["));
             size_t end = getLength();
             if(end > 0) {

+ 2 - 2
include/core/data/Stack.hpp

@@ -14,7 +14,7 @@ namespace Core {
 
         public:
             template<typename... Args>
-            check_return Error push(Args&&... args) {
+            CError push(Args&&... args) {
                 if constexpr(Core::IsSame<S, List<T>>) {
                     data.add(Core::forward<Args>(args)...);
                     return ErrorCode::NONE;
@@ -47,7 +47,7 @@ namespace Core {
             }
 
             template<typename String>
-            check_return Error toString(String& s) const {
+            CError toString(String& s) const {
                 return s.append(data);
             }
         };

+ 2 - 2
src/BitArray.cpp

@@ -53,7 +53,7 @@ static u64 getArrayLength(u64 length, u64 bits) {
 Core::BitArray::BitArray() : lengthBits(0), data(nullptr) {
 }
 
-check_return Core::Error Core::BitArray::copyFrom(const BitArray& other) {
+CError Core::BitArray::copyFrom(const BitArray& other) {
     CORE_RETURN_ERROR(resize(other.getLength(), other.getBits()));
     u64 length = getLength();
     for(u64 i = 0; i < length; i++) {
@@ -136,7 +136,7 @@ void Core::BitArray::fill(u64 value) {
     }
 }
 
-check_return Core::Error Core::BitArray::resize(u64 newLength, u64 newBits) {
+CError Core::BitArray::resize(u64 newLength, u64 newBits) {
     if(newLength == 0 || newBits == 0 || newBits > 64) {
         return ErrorCode::INVALID_ARGUMENT;
     }

+ 15 - 0
test/modules/ListTests.cpp

@@ -142,6 +142,20 @@ static void testRemove() {
     CORE_TEST_EQUAL(0, list.getLength());
 }
 
+static void testRemoveLast() {
+    IntList list;
+    list.add(4u).add(3u).add(2u);
+    list.removeLast();
+    CORE_TEST_EQUAL(4, list[0]);
+    CORE_TEST_EQUAL(3, list[1]);
+    CORE_TEST_EQUAL(2, list.getLength());
+    list.removeLast();
+    CORE_TEST_EQUAL(4, list[0]);
+    CORE_TEST_EQUAL(1, list.getLength());
+    list.removeLast();
+    CORE_TEST_EQUAL(0, list.getLength());
+}
+
 static void testResize() {
     IntList list;
     list.resize(5, 10);
@@ -198,6 +212,7 @@ void Core::testList(bool light) {
     testToString3();
     testRemoveBySwap();
     testRemove();
+    testRemoveLast();
     testResize();
     testDefaultResize();
     testInvalidReserve();

+ 91 - 108
test/modules/ProbingHashMapTests.cpp

@@ -2,10 +2,33 @@
 #include "../Tests.hpp"
 #include "core/data/ProbingHashMap.hpp"
 #include "core/utils/Error.hpp"
+#include "core/utils/HashCode.hpp"
 
 template struct Core::ProbingHashMap<int, int>;
 using IntMap = Core::ProbingHashMap<int, int>;
 
+constexpr int INVALID = Core::emptyValue<int>();
+
+static IntMap getTestIntMap() {
+    IntMap map;
+    map.add(1, 3).add(2, 4).add(3, 5).add(INVALID, 20);
+    return map;
+}
+
+static void checkIntMap(IntMap& map) {
+    int* a = map.search(1);
+    int* b = map.search(2);
+    int* c = map.search(3);
+    int* d = map.search(INVALID);
+    if(CORE_TEST_NOT_NULL(a) && CORE_TEST_NOT_NULL(b) &&
+       CORE_TEST_NOT_NULL(c) && CORE_TEST_NOT_NULL(d)) {
+        CORE_TEST_EQUAL(3, *a);
+        CORE_TEST_EQUAL(4, *b);
+        CORE_TEST_EQUAL(5, *c);
+        CORE_TEST_EQUAL(20, *d);
+    }
+}
+
 static void testAdd() {
     IntMap map;
     map.add(5, 4);
@@ -17,22 +40,11 @@ static void testAdd() {
 }
 
 static void testMultipleAdd() {
-    IntMap map;
-    map.add(5, 4).add(10, 3).add(15, 2);
-    CORE_TEST_TRUE(map.contains(5));
-    CORE_TEST_TRUE(map.contains(10));
-    CORE_TEST_TRUE(map.contains(15));
-    int* a = map.search(5);
-    int* b = map.search(10);
-    int* c = map.search(15);
-    CORE_TEST_NOT_NULL(a);
-    CORE_TEST_NOT_NULL(b);
-    CORE_TEST_NOT_NULL(c);
-    if(a != nullptr && b != nullptr && c != nullptr) {
-        CORE_TEST_EQUAL(4, *a);
-        CORE_TEST_EQUAL(3, *b);
-        CORE_TEST_EQUAL(2, *c);
-    }
+    IntMap map = getTestIntMap();
+    CORE_TEST_TRUE(map.contains(1));
+    CORE_TEST_TRUE(map.contains(2));
+    CORE_TEST_TRUE(map.contains(3));
+    checkIntMap(map);
 }
 
 static void testSearch() {
@@ -47,8 +59,7 @@ static void testAddReplace() {
     map.add(5, 4).add(5, 10);
     CORE_TEST_TRUE(map.contains(5));
     int* a = map.search(5);
-    CORE_TEST_NOT_NULL(a);
-    if(a != nullptr) {
+    if(CORE_TEST_NOT_NULL(a)) {
         CORE_TEST_EQUAL(10, *a);
     }
 }
@@ -64,42 +75,42 @@ static void testClear() {
 static void testOverflow(bool light) {
     int limit = light ? 10000 : 100000;
     IntMap map;
+    map.add(INVALID, 42);
     for(int i = 0; i < limit; i++) {
         map.add(i, i);
     }
     for(int i = 0; i < limit; i++) {
         CORE_TEST_TRUE(map.contains(i));
     }
+    CORE_TEST_TRUE(map.contains(INVALID));
 }
 
 static int aInstances = 0;
 
-struct ProbingHashMapTestStruct final {
+struct ProbingTest final {
     int a;
     int b;
 
-    ProbingHashMapTestStruct(int a_, int b_) : a(a_), b(b_) {
+    ProbingTest(int a_, int b_) : a(a_), b(b_) {
         aInstances++;
     }
 
-    ProbingHashMapTestStruct(const ProbingHashMapTestStruct& o)
-        : a(o.a), b(o.b) {
+    ProbingTest(const ProbingTest& o) : a(o.a), b(o.b) {
         aInstances++;
     }
 
-    ProbingHashMapTestStruct(ProbingHashMapTestStruct&& o) : a(o.a), b(o.b) {
+    ProbingTest(ProbingTest&& o) : a(o.a), b(o.b) {
         aInstances++;
     }
 
-    ~ProbingHashMapTestStruct() {
+    ~ProbingTest() {
         aInstances--;
     }
 
-    ProbingHashMapTestStruct&
-    operator=(const ProbingHashMapTestStruct& o) = default;
-    ProbingHashMapTestStruct& operator=(ProbingHashMapTestStruct&& o) = default;
+    ProbingTest& operator=(const ProbingTest& o) = default;
+    ProbingTest& operator=(ProbingTest&& o) = default;
 
-    bool operator==(const ProbingHashMapTestStruct& other) const {
+    bool operator==(const ProbingTest& other) const {
         return a == other.a && b == other.b;
     }
 
@@ -116,105 +127,56 @@ struct ProbingHashMapTestStruct final {
 
 static void testEmplace() {
     {
-        Core::ProbingHashMap<int, ProbingHashMapTestStruct> map;
+        Core::ProbingHashMap<int, ProbingTest> map;
 
-        ProbingHashMapTestStruct* ar = nullptr;
+        ProbingTest* ar = nullptr;
         CORE_TEST_TRUE(map.tryEmplace(ar, 0, 3, 4));
         CORE_TEST_TRUE(map.tryEmplace(ar, 3, 4, 5));
         CORE_TEST_TRUE(map.tryEmplace(ar, 20, 5, 6));
         CORE_TEST_FALSE(map.tryEmplace(ar, 3, 6, 7));
         CORE_TEST_FALSE(map.tryEmplace(ar, 20, 7, 8));
 
-        ProbingHashMapTestStruct* a = map.search(0);
-        ProbingHashMapTestStruct* b = map.search(3);
-        ProbingHashMapTestStruct* c = map.search(20);
+        ProbingTest* a = map.search(0);
+        ProbingTest* b = map.search(3);
+        ProbingTest* c = map.search(20);
 
-        CORE_TEST_NOT_NULL(a);
-        CORE_TEST_NOT_NULL(b);
-        CORE_TEST_NOT_NULL(c);
-
-        if(a != nullptr && b != nullptr && c != nullptr) {
-            CORE_TEST_EQUAL(ProbingHashMapTestStruct(3, 4), *a);
-            CORE_TEST_EQUAL(ProbingHashMapTestStruct(4, 5), *b);
-            CORE_TEST_EQUAL(ProbingHashMapTestStruct(5, 6), *c);
+        if(CORE_TEST_NOT_NULL(a) && CORE_TEST_NOT_NULL(b) &&
+           CORE_TEST_NOT_NULL(c)) {
+            CORE_TEST_EQUAL(ProbingTest(3, 4), *a);
+            CORE_TEST_EQUAL(ProbingTest(4, 5), *b);
+            CORE_TEST_EQUAL(ProbingTest(5, 6), *c);
         }
     }
     CORE_TEST_EQUAL(0, aInstances);
 }
 
-static void testToString1() {
-    IntMap map;
-    map.add(1, 3).add(2, 4).add(3, 5);
-    CORE_TEST_STRING("[2 = 4, 1 = 3, 3 = 5]", map);
-}
-
-static void testToString2() {
-    IntMap map;
-    map.add(1, 3);
-    CORE_TEST_STRING("[1 = 3]", map);
-}
-
-static void testToString3() {
-    IntMap map;
-    CORE_TEST_STRING("[]", map);
+static void testToString() {
+    CORE_TEST_STRING("[2 = 4, 1 = 3, 3 = 5, 2147483647 = 20]", getTestIntMap());
+    CORE_TEST_STRING("[1 = 3]", IntMap().add(1, 3));
+    CORE_TEST_STRING("[]", IntMap());
 }
 
 static void testCopy() {
-    IntMap map;
-    map.add(1, 3).add(2, 4).add(3, 5);
+    IntMap map = getTestIntMap();
     IntMap copy = map;
-
-    int* a[6] = {map.search(1),  map.search(2),  map.search(3),
-                 copy.search(1), copy.search(2), copy.search(3)};
-    for(int i = 0; i < 3; i++) {
-        CORE_TEST_NOT_NULL(a[i]);
-        CORE_TEST_NOT_NULL(a[i + 3]);
-        if(a[i] != nullptr && a[i + 3] != nullptr) {
-            CORE_TEST_EQUAL(*(a[i]), *(a[i + 3]));
-        }
-    }
+    IntMap copyA;
+    copyA = map;
+    checkIntMap(map);
+    checkIntMap(copy);
+    checkIntMap(copyA);
 }
 
 static void testMove() {
-    IntMap map;
-    map.add(1, 3).add(2, 4).add(3, 5);
+    IntMap map = getTestIntMap();
     IntMap move(Core::move(map));
-
-    int* a = move.search(1);
-    int* b = move.search(2);
-    int* c = move.search(3);
-
-    CORE_TEST_NOT_NULL(a);
-    CORE_TEST_NOT_NULL(b);
-    CORE_TEST_NOT_NULL(c);
-
-    if(a != nullptr && b != nullptr && c != nullptr) {
-        CORE_TEST_EQUAL(3, *a);
-        CORE_TEST_EQUAL(4, *b);
-        CORE_TEST_EQUAL(5, *c);
-    }
+    checkIntMap(move);
 }
 
 static void testMoveAssignment() {
-    IntMap map;
-    map.add(1, 3).add(2, 4).add(3, 5);
-
+    IntMap map = getTestIntMap();
     IntMap move;
     move = Core::move(map);
-
-    int* a = move.search(1);
-    int* b = move.search(2);
-    int* c = move.search(3);
-
-    CORE_TEST_NOT_NULL(a);
-    CORE_TEST_NOT_NULL(b);
-    CORE_TEST_NOT_NULL(c);
-
-    if(a != nullptr && b != nullptr && c != nullptr) {
-        CORE_TEST_EQUAL(3, *a);
-        CORE_TEST_EQUAL(4, *b);
-        CORE_TEST_EQUAL(5, *c);
-    }
+    checkIntMap(move);
 }
 
 static void testEntryForEach() {
@@ -291,11 +253,33 @@ static void testTypes() {
     testType<unsigned long long>();
 }
 
-static void testInsertInvalid() {
+static void testInvalid() {
     IntMap map;
     int* v;
-    CORE_TEST_TRUE(map.tryEmplace(v, Core::emptyValue<int>(), 2));
-    CORE_TEST_EQUAL(3, map.put(Core::emptyValue<int>(), 3));
+    CORE_TEST_TRUE(map.tryEmplace(v, INVALID, 2));
+    if(CORE_TEST_NOT_NULL(v)) {
+        CORE_TEST_EQUAL(2, *v);
+    }
+    CORE_TEST_FALSE(map.tryEmplace(v, INVALID, 6));
+    if(CORE_TEST_NOT_NULL(v)) {
+        CORE_TEST_EQUAL(2, *v);
+    }
+    CORE_TEST_EQUAL(3, map.put(INVALID, 3));
+    v = map.search(INVALID);
+    if(CORE_TEST_NOT_NULL(v)) {
+        CORE_TEST_EQUAL(3, *v);
+    }
+    map.clear();
+    CORE_TEST_NULL(map.search(INVALID));
+}
+
+static void testInvalidPut() {
+    IntMap map;
+    CORE_TEST_EQUAL(3, map.put(INVALID, 3));
+    int* v = map.search(INVALID);
+    if(CORE_TEST_NOT_NULL(v)) {
+        CORE_TEST_EQUAL(3, *v);
+    }
 }
 
 static void testAddCollisions() {
@@ -313,9 +297,7 @@ void Core::testProbingHashMap(bool light) {
     testClear();
     testOverflow(light);
     testEmplace();
-    testToString1();
-    testToString2();
-    testToString3();
+    testToString();
     testCopy();
     testMove();
     testMoveAssignment();
@@ -323,6 +305,7 @@ void Core::testProbingHashMap(bool light) {
     testKeyForEach();
     testValueForEach();
     testTypes();
-    testInsertInvalid();
+    testInvalid();
+    testInvalidPut();
     testAddCollisions();
 }