From 2fc9fa6d08df3b12c764d88f4458d28d4352de9b Mon Sep 17 00:00:00 2001 From: Herb Derby Date: Thu, 12 Dec 2019 15:07:11 -0500 Subject: [PATCH] Add filter to SkTHashTable and SkTHashMap Filter takes a bool function where fales means remove the entry. Change-Id: I768baed6a4e26308f571b0b595ae19aea9d8dca7 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/259821 Commit-Queue: Herb Derby Reviewed-by: Mike Klein --- include/private/SkTHash.h | 90 ++++++++++++++++++++++++++------------- tests/HashTest.cpp | 31 ++++++++++++++ 2 files changed, 91 insertions(+), 30 deletions(-) diff --git a/include/private/SkTHash.h b/include/private/SkTHash.h index d04a2cc014..55afd89cd8 100644 --- a/include/private/SkTHash.h +++ b/include/private/SkTHash.h @@ -104,39 +104,11 @@ public: Slot& s = fSlots[index]; SkASSERT(!s.empty()); if (hash == s.hash && key == Traits::GetKey(s.val)) { - fCount--; - break; + this->removeSlot(index); + return; } index = this->next(index); } - - // Rearrange elements to restore the invariants for linear probing. - for (;;) { - Slot& emptySlot = fSlots[index]; - int emptyIndex = index; - int originalIndex; - // Look for an element that can be moved into the empty slot. - // If the empty slot is in between where an element landed, and its native slot, then - // move it to the empty slot. Don't move it if its native slot is in between where - // the element landed and the empty slot. - // [native] <= [empty] < [candidate] == GOOD, can move candidate to empty slot - // [empty] < [native] < [candidate] == BAD, need to leave candidate where it is - do { - index = this->next(index); - Slot& s = fSlots[index]; - if (s.empty()) { - // We're done shuffling elements around. Clear the last empty slot. - emptySlot = Slot(); - return; - } - originalIndex = s.hash & (fCapacity - 1); - } while ((index <= originalIndex && originalIndex < emptyIndex) - || (originalIndex < emptyIndex && emptyIndex < index) - || (emptyIndex < index && index <= originalIndex)); - // Move the element to the empty slot. - Slot& moveFrom = fSlots[index]; - emptySlot = std::move(moveFrom); - } } // Call fn on every entry in the table. You may mutate the entries, but be very careful. @@ -159,6 +131,25 @@ public: } } + // Call fn on every entry in the table. Fn can return false to remove the entry. You may mutate + // the entries, but be very careful. + template // f(T*) + void mutate(Fn&& fn) { + for (int i = 0; i < fCapacity;) { + bool keep = true; + if (!fSlots[i].empty()) { + keep = fn(&fSlots[i].val); + } + if (keep) { + i++; + } else { + this->removeSlot(i); + // Something may now have moved into slot i, so we'll loop + // around to check slot i again. + } + } + } + private: T* uncheckedSet(T&& val) { const K& key = Traits::GetKey(val); @@ -204,6 +195,38 @@ private: SkASSERT(fCount == oldCount); } + void removeSlot(int index) { + fCount--; + + // Rearrange elements to restore the invariants for linear probing. + for (;;) { + Slot& emptySlot = fSlots[index]; + int emptyIndex = index; + int originalIndex; + // Look for an element that can be moved into the empty slot. + // If the empty slot is in between where an element landed, and its native slot, then + // move it to the empty slot. Don't move it if its native slot is in between where + // the element landed and the empty slot. + // [native] <= [empty] < [candidate] == GOOD, can move candidate to empty slot + // [empty] < [native] < [candidate] == BAD, need to leave candidate where it is + do { + index = this->next(index); + Slot& s = fSlots[index]; + if (s.empty()) { + // We're done shuffling elements around. Clear the last empty slot. + emptySlot = Slot(); + return; + } + originalIndex = s.hash & (fCapacity - 1); + } while ((index <= originalIndex && originalIndex < emptyIndex) + || (originalIndex < emptyIndex && emptyIndex < index) + || (emptyIndex < index && index <= originalIndex)); + // Move the element to the empty slot. + Slot& moveFrom = fSlots[index]; + emptySlot = std::move(moveFrom); + } + } + int next(int index) const { index--; if (index < 0) { index += fCapacity; } @@ -299,6 +322,13 @@ public: fTable.foreach([&fn](const Pair& p){ fn(p.key, p.val); }); } + // Call fn on every key/value pair in the table. Fn may return false to remove the entry. You + // may mutate the value but not the key. + template // f(K, V*) or f(const K&, V*) + void mutate(Fn&& fn) { + fTable.mutate([&fn](Pair* p) { return fn(p->key, &p->val); }); + } + private: struct Pair { K key; diff --git a/tests/HashTest.cpp b/tests/HashTest.cpp index b60025c699..3d53b94ac2 100644 --- a/tests/HashTest.cpp +++ b/tests/HashTest.cpp @@ -199,3 +199,34 @@ DEF_TEST(HashFindOrNull, r) { REPORTER_ASSERT(r, &seven == table.findOrNull(7)); } + +DEF_TEST(HashFilter, r) { + + + struct HashTraits { + static int GetKey(const int e) { return e; } + static uint32_t Hash(int key) { return 0; } + }; + + SkTHashTable table; + + for (int i = 0; i < 10; i++) { + table.set(i); + } + + REPORTER_ASSERT(r, table.count() == 10); + + table.mutate([](int* i) { + // Do not remove. + return true; + }); + + REPORTER_ASSERT(r, table.count() == 10); + + table.mutate([](int* i) { + // table.remove(*i); + return false; + }); + + REPORTER_ASSERT(r, table.count() == 0); +}