From 15f7b1470436c9074b95b0ff4ba63e24a473b27e Mon Sep 17 00:00:00 2001
From: Kruno Tomola Fabro <krunotf@memgraph.io>
Date: Wed, 10 Aug 2016 23:05:24 +0100
Subject: [PATCH] Finished remove method in RhMultiMap. Remove passes tests.
 Needs refactoring, has lot of duplicate code.

---
 poc/astar.cpp                               |  31 ++
 src/data_structures/map/rh_hashmultimap.hpp | 412 +++++++++-----------
 tests/unit/rh_hashmultimap.cpp              | 114 +++---
 3 files changed, 291 insertions(+), 266 deletions(-)

diff --git a/poc/astar.cpp b/poc/astar.cpp
index 6a4258e1d..f30f1664d 100644
--- a/poc/astar.cpp
+++ b/poc/astar.cpp
@@ -191,6 +191,19 @@ void a_star(Db &db, int64_t sys_id_start, uint max_depth, EdgeFilter e_filter[],
     t.commit();
 }
 
+// class Data
+// {
+//
+// private:
+//     size_t data = 0;
+//     int key;
+//
+// public:
+//     Data(int key) : key(key) {}
+//
+//     const int &get_key() { return key; }
+// };
+
 int main()
 {
     Db db;
@@ -209,6 +222,24 @@ int main()
     double elapsed_ms = (double(end - begin) / CLOCKS_PER_SEC) * 1000;
     std::cout << "A-star: " << elapsed_ms << " [ms]\n";
 
+    // RhHashMultiMap benchmark
+    // const int n_pow2 = 20;
+    // int n = 1 << n_pow2;
+    // RhHashMultiMap<int, Data, n_pow2 + 1> map;
+    // std::srand(time(0));
+    // auto data = std::vector<Data *>();
+    // for (int i = 0; i < n; i++) {
+    //     data.push_back(new Data(std::rand()));
+    // }
+    //
+    // begin = clock();
+    // for (auto e : data) {
+    //     map.add(e);
+    // }
+    // end = clock();
+    // elapsed_ms = (double(end - begin) / CLOCKS_PER_SEC) * 1000;
+    // std::cout << "Map: " << elapsed_ms << " [ms]\n";
+
     return 0;
 }
 
diff --git a/src/data_structures/map/rh_hashmultimap.hpp b/src/data_structures/map/rh_hashmultimap.hpp
index 111c21a3b..671f7d8a5 100644
--- a/src/data_structures/map/rh_hashmultimap.hpp
+++ b/src/data_structures/map/rh_hashmultimap.hpp
@@ -30,7 +30,14 @@ private:
 
         size_t off() { return data & 0x7; }
 
-        void decrement_off() { data--; }
+        bool decrement_off()
+        {
+            if (off() > 0) {
+                data--;
+                return true;
+            }
+            return false;
+        }
 
         bool increment_off()
         {
@@ -255,7 +262,7 @@ public:
                     break;
 
                 } else { // Else other has equal or greater
-                         // offset, so he is poor.
+                    // offset, so he is poor.
                     auto other_key = other.ptr()->get_key();
                     do {
                         now = (now + 1) & mask;
@@ -275,6 +282,9 @@ public:
         return end();
     }
 
+    // Inserts element.
+    void add(D *data) { add(data->get_key(), data); }
+
     // Inserts element with the given key.
     void add(const K &key_in, D *data)
     {
@@ -287,12 +297,12 @@ public:
             size_t start = now;
             size_t off = 0;
             size_t border = 8 <= capacity ? 8 : capacity;
-            bool multi = false;
 
             Combined other = array[now];
             while (off < border) {
                 if (other.valid()) {
                     auto other_off = other.off();
+                    bool multi = false;
                     if (other_off == off &&
                         other.ptr()->get_key() == key) { // Found the
                         do {                             // same
@@ -321,8 +331,10 @@ public:
                     }
 
                     array[now] = Combined(data, off);
-                    auto start = now;
-                    while (adjust_off(other, mask, start, now, multi)) {
+                    auto start_insert = now;
+                    while (is_off_adjusted(other, mask, start_insert, now,
+                                           multi) ||
+                           other.increment_off()) {
                         now = (now + 1) & mask;
                         auto tmp = array[now];
                         array[now] = other;
@@ -352,244 +364,204 @@ private:
         count++;
     }
 
-    bool adjust_off(Combined &com, size_t mask, size_t start, size_t now,
-                    bool multi)
+    // True if no adjusment is needed, false otherwise.
+    bool is_off_adjusted(Combined &com, size_t mask, size_t start, size_t now,
+                         bool multi)
     {
-        if (com.off() == 0) {
-            com.increment_off();
-            return true;
+        if (com.off() == 0) { // Must be adjusted
+            return false;
         }
         size_t cin = index(com.ptr()->get_key(), mask);
         if ((start <= now && (cin < start || cin > now)) ||
-            (now < start && cin < start && cin > now)) {
-            return multi || com.increment_off();
+            (now < start && cin < start &&
+             cin > now)) { // Outside [start,now] interval
+            return multi;
         }
         auto a = array[cin];
         auto b = array[(cin + 1) & mask];
-        return (a.off() == b.off() &&
-                a.ptr()->get_key() == b.ptr()->get_key()) ||
-               com.increment_off();
+        return (a.off() == b.off() && a.ptr()->get_key() == b.ptr()->get_key());
+        // Check if different key has eneterd in to
+        // range of other.
     }
 
     bool other_poor(Combined other, size_t mask, size_t start, size_t now)
     {
         auto cin = index(other.ptr()->get_key(), mask);
         return (start <= now && (cin <= start || cin > now)) ||
-               (now < start && cin <= start && cin > now);
+               (now < start && cin <= start &&
+                cin > now); // If other index is smaller then he is poorer.
+    }
+
+    // True if no adjusment is needed, false otherwise.
+    bool is_off_adjusted_rem(Combined &com, size_t mask, size_t start,
+                             size_t bef, size_t now, bool multi)
+    {
+        if (com.off() == 0) { // Must be adjusted
+            return false;
+        }
+        size_t cin = index(com.ptr()->get_key(), mask);
+        if (cin == bef) {
+            return false;
+        }
+        if ((start <= now && (cin < start || cin > now)) ||
+            (now < start && cin < start &&
+             cin > now)) { // Outside [start,now] interval
+            return multi;
+        }
+        auto a = array[cin];
+        auto b = array[before_index(cin, mask)];
+        return b.valid() &&
+               (a.off() == b.off() && a.ptr()->get_key() == b.ptr()->get_key());
+        // Check if different key has eneterd in to
+        // range of other.
     }
-    //     void skip(size_t &now, size_t mask)
-    //     {
-    //         Combined start = array[now];
-    //         size_t end = now;
-    //         auto off = start.off();
-    //         auto key = start.ptr()->get_key();
-    //         do {
-    //             now = (now + 1) & mask;
-    //             start = array[now];
-    //         } while (start.valid() && start.off() == off &&
-    //                  start.ptr()->get_key() == key && now != end);
-    //     }
-    //
-    //     void _insert(size_t now, Combined com)
-    //     {
-    //         Combined other = array[now];
-    //         array[now] = com;
-    //         if (other.valid()) {
-    //             _add(now, other.off(), other.ptr());
-    //         } else {
-    //             count++;
-    //         }
-    //     }
-    //
-    //     void _add(size_t now, size_t off, Data *data) {
-    //         size_t mask = this->mask();
-    //         auto key = std::ref(data->get_key());
-    //         size_t border = 8 <= capacity ? 8 : capacity;
-    //
-    //         skip()
-    //
-    //         while(off<border){
-    //             Combined other = array[now];
-    //             if (other.valid()) {
-    //                 _add(now, other.off(), other.ptr());
-    //             } else {
-    //                 _insert(now, RhHashMultiMap::Combined com)
-    //             }
-    //         }
-    //
-    //         increase_size();
-    //         add(data);
-    //     }
 
 public:
-    // // Inserts element with the given key.
-    // void add(const K &key_in, D *data)
-    // {
-    //     assert(key_in == data->get_key());
-    //
-    //     if (count < capacity) {
-    //         auto key = std::ref(key_in);
-    //         size_t mask = this->mask();
-    //         size_t now = index(key, mask);
-    //         size_t off = 0;
-    //
-    //         bool bef_init = false;
-    //         size_t before_off;
-    //         auto before_key = std::ref(key);
-    //         bool found_it = false;
-    //
-    //         size_t border = 8 <= capacity ? 8 : capacity;
-    //         while (off < border) {
-    //             Combined other = array[now];
-    //             if (other.valid()) {
-    //                 auto other_off = other.off();
-    //                 auto other_key = std::ref<const
-    //                 K>(other.ptr()->get_key());
-    //                 if (other_off == off && key == other_key) {
-    //                     found_it = true;
-    //                     // Proceed
-    //
-    //                 } else if (other_off < off || found_it) { // Other is
-    //                 rich
-    //                                                           // or after
-    //                                                           list
-    //                                                           // of my keys
-    //                     assert(other_off <= off);
-    //
-    //                     array[now] = Combined(data, off);
-    //                     // add(other.ptr()->get_key(), other.ptr());
-    //                     // return;
-    //
-    //                     // Hacked reusing of function
-    //                     before_off = off;
-    //                     before_key = key;
-    //                     data = other.ptr();
-    //                     key = other_key;
-    //                     off = other_off;
-    //
-    //                     if (found_it) { // Offset isn't increased
-    //                         found_it = false;
-    //                     } else {
-    //                         off++;
-    //                     }
-    //                 } else if (bef_init) { // Else other has equal or greater
-    //                                        // offset, so he is poor.
-    //                     if (before_off == other_off &&
-    //                         before_key == other_key) {
-    //                         if (count == capacity) {
-    //                             break;
-    //                         }
-    //                         // Proceed
-    //                     } else {
-    //                         before_off = other_off;
-    //                         before_key = other_key;
-    //                         off++;
-    //                     }
-    //                 } else {
-    //                     bef_init = true;
-    //                     before_off = other_off;
-    //                     before_key = other_key;
-    //                     off++;
-    //                 }
-    //
-    //             } else {
-    //                 array[now] = Combined(data, off);
-    //                 count++;
-    //                 return;
-    //             }
-    //
-    //             now = (now + 1) & mask;
-    //         }
-    //     }
-    //
-    //     increase_size();
-    //     add(data);
-    // }
-
-    // Inserts element.
-    void add(D *data) { add(data->get_key(), data); }
-
     // Removes element. Returns removed element if it existed. It doesn't
     // specify which element from same key group will be removed.
     OptionPtr<D> remove(const K &key_in)
     {
-        // auto key = std::ref(key_in);
-        // size_t mask = this->mask();
-        // size_t now = index(key, mask);
-        // size_t off = 0;
-        //
-        // bool bef_init = false;
-        // size_t before_off;
-        // auto before_key = key;
-        // bool found_it = false;
-        //
-        // size_t border = 8 <= capacity ? 8 : capacity;
-        // while (off < border) {
-        //     Combined other = array[now];
-        //     if (other.valid()) {
-        //         auto other_off = other.off();
-        //         auto other_ptr = other.ptr();
-        //         auto other_key = std::ref<const K>(other_ptr->get_key());
-        //         if (other_off == off && key == other_key) { // Found it
-        //             found_it = true;
-        //
-        //         } else if (found_it) { // Found first element after last
-        //         element
-        //             // for remove.
-        //             auto before = before_index(now, mask);
-        //             auto ret = OptionPtr<D>(array[before].ptr());
-        //             std::cout << "<-" << ret.get()->get_key() << "\n";
-        //             while (other.valid() && other.off() > 0) {
-        //                 std::cout << "<>" << other.ptr()->get_key() << "\n";
-        //                 other.decrement_off();
-        //                 array[before] = other;
-        //                 before = now;
-        //                 now = (now + 1) & mask;
-        //                 other = array[now];
-        //             }
-        //
-        //             array[before] = Combined();
-        //             count--;
-        //             return ret;
-        //         } else if (other_off < off) { // Other is rich
-        //             break;
-        //
-        //         } else if (bef_init) { // Else other has equal or greater
-        //                                // offset, so he is poor.
-        //             if (before_off == other_off && before_key == other_key) {
-        //                 if (count == capacity) { // I am stuck.
-        //                     break;
-        //                 }
-        //                 // Proceed
-        //             } else {
-        //                 before_off = other_off;
-        //                 before_key = other_key;
-        //                 off++;
-        //             }
-        //         } else {
-        //             bef_init = true;
-        //             before_off = other_off;
-        //             before_key = other_key;
-        //             off++;
-        //         }
-        //
-        //     } else if (found_it) { // Found empty space after last element
-        //     for
-        //                            // remove.
-        //         auto before = before_index(now, mask);
-        //         auto ret = OptionPtr<D>(array[before].ptr());
-        //         array[before] = Combined();
-        //
-        //         return ret;
-        //     } else {
-        //         break;
-        //     }
-        //
-        //     now = (now + 1) & mask;
-        // }
+
+        if (count > 0) {
+            auto key = std::ref(key_in);
+            size_t mask = this->mask();
+            size_t now = index(key, mask);
+            size_t off = 0;
+            size_t checked = 0;
+            size_t border = 8 <= capacity ? 8 : capacity;
+            Combined other = array[now];
+            while (other.valid() && off < border) {
+                auto other_off = other.off();
+                bool multi = false;
+                if (other_off == off && key == other.ptr()->get_key()) {
+                    do {
+                        now = (now + 1) & mask;
+                        other = array[now];
+                        if (!other.valid()) {
+                            break;
+                        }
+                        other_off = other.off();
+                    } while (other_off == off &&
+                             other.ptr()->get_key() == key &&
+                             (multi = true)); // multi = true is correct
+
+                    auto bef = before_index(now, mask);
+                    auto ret = OptionPtr<D>(array[bef].ptr());
+
+                    auto start_rem = bef;
+                    while (other.valid() &&
+                           (is_off_adjusted_rem(other, mask, start_rem, bef,
+                                                now, multi) ||
+                            other.decrement_off())) {
+                        array[bef] = other;
+                        bef = now;
+                        now = (now + 1) & mask;
+                        other = array[now];
+                    }
+
+                    array[bef] = Combined();
+                    count--;
+                    return ret;
+
+                } else if (other_off < off) { // Other is rich
+                    break;
+
+                } else { // Else other has equal or greater
+                         // offset, so he is poor.
+                    auto other_key = other.ptr()->get_key();
+                    do {
+                        now = (now + 1) & mask;
+                        other = array[now];
+                        checked++;
+                        if (checked >= count) { // Reason is possibility of map
+                                                // full of same values.
+                            break;
+                        }
+                    } while (other.valid() && other.off() == other_off &&
+                             other.ptr()->get_key() == other_key);
+                    off++;
+                }
+            }
+        }
+
         return OptionPtr<D>();
     }
 
+    // Removes element equal by key and value. Returns true if it existed.
+    bool remove(D *data)
+    {
+        if (count > 0) {
+            auto key = std::ref(data->get_key());
+            size_t mask = this->mask();
+            size_t now = index(key, mask);
+            size_t off = 0;
+            size_t checked = 0;
+            size_t border = 8 <= capacity ? 8 : capacity;
+            Combined other = array[now];
+            while (other.valid() && off < border) {
+                auto other_off = other.off();
+                bool multi = false;
+                if (other_off == off && key == other.ptr()->get_key()) {
+                    auto founded = capacity;
+                    do {
+                        if (other.ptr() == data) {
+                            founded = now;
+                        }
+                        now = (now + 1) & mask;
+                        other = array[now];
+                        if (!other.valid()) {
+                            break;
+                        }
+                        other_off = other.off();
+                    } while (other_off == off &&
+                             other.ptr()->get_key() == key &&
+                             (multi = true)); // multi = true is correct
+                    if (founded == capacity) {
+                        return false;
+                    }
+
+                    auto bef = before_index(now, mask);
+                    array[founded] = array[bef];
+                    // Same as remove of key only diffrence is with founded
+
+                    auto start_rem = bef;
+                    while (other.valid() &&
+                           (is_off_adjusted_rem(other, mask, start_rem, bef,
+                                                now, multi) ||
+                            other.decrement_off())) {
+                        array[bef] = other;
+                        bef = now;
+                        now = (now + 1) & mask;
+                        other = array[now];
+                    }
+
+                    array[bef] = Combined();
+                    count--;
+                    return true;
+
+                } else if (other_off < off) { // Other is rich
+                    break;
+
+                } else { // Else other has equal or greater
+                         // offset, so he is poor.
+                    auto other_key = other.ptr()->get_key();
+                    do {
+                        now = (now + 1) & mask;
+                        other = array[now];
+                        checked++;
+                        if (checked >= count) { // Reason is possibility of map
+                                                // full of same values.
+                            break;
+                        }
+                    } while (other.valid() && other.off() == other_off &&
+                             other.ptr()->get_key() == other_key);
+                    off++;
+                }
+            }
+        }
+        return false;
+    }
+
     void clear()
     {
         free(array);
diff --git a/tests/unit/rh_hashmultimap.cpp b/tests/unit/rh_hashmultimap.cpp
index 3c19deb7e..39aab9a9b 100644
--- a/tests/unit/rh_hashmultimap.cpp
+++ b/tests/unit/rh_hashmultimap.cpp
@@ -42,18 +42,18 @@ TEST_CASE("Robin hood hashmultimap insert/get check")
     REQUIRE(*map.find(0) == ptr0);
 }
 
-// TEST_CASE("Robin hood hasmultihmap remove functionality")
-// {
-//     RhHashMultiMap<int, Data> map;
-//
-//     REQUIRE(map.find(0) == map.end());
-//     auto ptr0 = new Data(0);
-//     map.add(ptr0);
-//     REQUIRE(map.find(0) != map.end());
-//     REQUIRE(*map.find(0) == ptr0);
-//     REQUIRE(map.remove(0).get() == ptr0);
-//     REQUIRE(map.find(0) == map.end());
-// }
+TEST_CASE("Robin hood hasmultihmap remove functionality")
+{
+    RhHashMultiMap<int, Data> map;
+
+    REQUIRE(map.find(0) == map.end());
+    auto ptr0 = new Data(0);
+    map.add(ptr0);
+    REQUIRE(map.find(0) != map.end());
+    REQUIRE(*map.find(0) == ptr0);
+    REQUIRE(map.remove(0).get() == ptr0);
+    REQUIRE(map.find(0) == map.end());
+}
 
 TEST_CASE("Robin hood hashmultimap double insert")
 {
@@ -144,39 +144,62 @@ TEST_CASE("Robin hood hashmultimap checked rand")
     cross_validate(map, s_map);
 }
 
-// TEST_CASE("Robin hood hashmultimap with remove checked")
-// {
-//     RhHashMultiMap<int, Data> map;
-//     std::multimap<int, Data *> s_map;
-//
-//     for (int i = 0; i < 2638; i++) {
-//         int key = (std::rand() % 100) << 3;
-//         if ((std::rand() % 3) == 0) {
-//             std::cout << "Remove: " << key << "\n";
-//             auto removed = map.remove(key);
-//             // auto it = s_map.find(key);
-//             if (removed.is_present()) {
-//                 // while (it != s_map.end() && it->second != removed.get()) {
-//                 //     it++;
-//                 // }
-//                 // REQUIRE(it != s_map.end());
-//                 // s_map.erase(it);
-//                 // cross_validate(map, s_map);
-//
-//             } else {
-//                 // REQUIRE(it == s_map.end());
-//             }
-//         } else {
-//             std::cout << "Insert: " << key << "\n";
-//             auto data = new Data(key);
-//             map.add(data);
-//             s_map.insert(std::pair<int, Data *>(key, data));
-//             cross_validate(map, s_map);
-//         }
-//     }
-//
-//     cross_validate_weak(map, s_map);
-// }
+TEST_CASE("Robin hood hashmultimap with remove checked")
+{
+    RhHashMultiMap<int, Data> map;
+    std::multimap<int, Data *> s_map;
+
+    std::srand(std::time(0));
+    for (int i = 0; i < 162638; i++) {
+        int key = (std::rand() % 10000) << 3;
+        if ((std::rand() % 3) == 0) {
+            auto removed = map.remove(key);
+            auto it = s_map.find(key);
+            if (removed.is_present()) {
+                while (it != s_map.end() && it->second != removed.get()) {
+                    it++;
+                }
+                REQUIRE(it != s_map.end());
+                s_map.erase(it);
+
+            } else {
+                REQUIRE(it == s_map.end());
+            }
+        } else {
+            auto data = new Data(key);
+            map.add(data);
+            s_map.insert(std::pair<int, Data *>(key, data));
+        }
+    }
+
+    cross_validate(map, s_map);
+}
+
+TEST_CASE("Robin hood hashmultimap with remove data checked")
+{
+    RhHashMultiMap<int, Data> map;
+    std::multimap<int, Data *> s_map;
+
+    std::srand(std::time(0));
+    for (int i = 0; i < 162638; i++) {
+        int key = (std::rand() % 10000) << 3;
+        if ((std::rand() % 2) == 0) {
+            auto it = s_map.find(key);
+            if (it == s_map.end()) {
+                REQUIRE(map.find(key) == map.end());
+            } else {
+                s_map.erase(it);
+                REQUIRE(map.remove(it->second));
+            }
+        } else {
+            auto data = new Data(key);
+            map.add(data);
+            s_map.insert(std::pair<int, Data *>(key, data));
+        }
+    }
+
+    cross_validate(map, s_map);
+}
 
 void cross_validate(RhHashMultiMap<int, Data> &map,
                     std::multimap<int, Data *> &s_map)
@@ -193,7 +216,6 @@ void cross_validate(RhHashMultiMap<int, Data> &map,
 
     for (auto e : s_map) {
         auto it = map.find(e.first);
-        // std::cout << "s_map: " << e.first << "\n";
 
         while (it != map.end() && *it != e.second) {
             it++;