| /* | |
| * Copyright 2015 Google Inc. | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #ifndef SkTHash_DEFINED | |
| #define SkTHash_DEFINED | |
| #include "include/core/SkTypes.h" | |
| #include "src/core/SkChecksum.h" | |
| #include "src/core/SkMathPriv.h" | |
| #include <initializer_list> | |
| #include <memory> | |
| #include <new> | |
| #include <type_traits> | |
| #include <utility> | |
| namespace skia_private { | |
| // Before trying to use THashTable, look below to see if THashMap or THashSet works for you. | |
| // They're easier to use, usually perform the same, and have fewer sharp edges. | |
| // T and K are treated as ordinary copyable C++ types. | |
| // Traits must have: | |
| // - static K GetKey(T) | |
| // - static uint32_t Hash(K) | |
| // Traits may also define (both required if either is defined): | |
| // - static bool ShouldGrow(int count, int capacity) | |
| // - static bool ShouldShrink(int count, int capacity) | |
| // , which specify the max/min load factor of the table. | |
| // If the key is large and stored inside T, you may want to make K a const&. | |
| // Similarly, if T is large you might want it to be a pointer. | |
| template <typename T, typename K, typename Traits = T> | |
| class THashTable { | |
| public: | |
| THashTable() = default; | |
| ~THashTable() = default; | |
| THashTable(const THashTable& that) { *this = that; } | |
| THashTable( THashTable&& that) { *this = std::move(that); } | |
| THashTable& operator=(const THashTable& that) { | |
| if (this != &that) { | |
| fCount = that.fCount; | |
| fCapacity = that.fCapacity; | |
| fSlots.reset(new Slot[that.fCapacity]); | |
| for (int i = 0; i < fCapacity; i++) { | |
| fSlots[i] = that.fSlots[i]; | |
| } | |
| } | |
| return *this; | |
| } | |
| THashTable& operator=(THashTable&& that) { | |
| if (this != &that) { | |
| fCount = that.fCount; | |
| fCapacity = that.fCapacity; | |
| fSlots = std::move(that.fSlots); | |
| that.fCount = that.fCapacity = 0; | |
| } | |
| return *this; | |
| } | |
| // Clear the table. | |
| void reset() { *this = THashTable(); } | |
| // How many entries are in the table? | |
| int count() const { return fCount; } | |
| // How many slots does the table contain? (Note that unlike an array, hash tables can grow | |
| // before reaching 100% capacity.) | |
| int capacity() const { return fCapacity; } | |
| // Approximately how many bytes of memory do we use beyond sizeof(*this)? | |
| size_t approxBytesUsed() const { return fCapacity * sizeof(Slot); } | |
| // Exchange two hash tables. | |
| void swap(THashTable& that) { | |
| std::swap(fCount, that.fCount); | |
| std::swap(fCapacity, that.fCapacity); | |
| std::swap(fSlots, that.fSlots); | |
| } | |
| void swap(THashTable&& that) { | |
| *this = std::move(that); | |
| } | |
| // !!!!!!!!!!!!!!!!! CAUTION !!!!!!!!!!!!!!!!! | |
| // set(), find() and foreach() all allow mutable access to table entries. | |
| // If you change an entry so that it no longer has the same key, all hell | |
| // will break loose. Do not do that! | |
| // | |
| // Please prefer to use THashMap or THashSet, which do not have this danger. | |
| // The pointers returned by set() and find() are valid only until the next call to set(). | |
| // The pointers you receive in foreach() are only valid for its duration. | |
| // Copy val into the hash table, returning a pointer to the copy now in the table. | |
| // If there already is an entry in the table with the same key, we overwrite it. | |
| T* set(T val) { | |
| bool shouldGrow = false; | |
| if constexpr (HasShouldGrow<Traits>::value) { | |
| shouldGrow = Traits::ShouldGrow(fCount, fCapacity); | |
| } else { | |
| shouldGrow = (4 * fCount >= 3 * fCapacity); | |
| } | |
| if (shouldGrow) { | |
| this->resize(fCapacity > 0 ? fCapacity * 2 : 4); | |
| } | |
| return this->uncheckedSet(std::move(val)); | |
| } | |
| // If there is an entry in the table with this key, return a pointer to it. If not, null. | |
| T* find(const K& key) const { | |
| uint32_t hash = Hash(key); | |
| int index = hash & (fCapacity-1); | |
| for (int n = 0; n < fCapacity; n++) { | |
| Slot& s = fSlots[index]; | |
| if (s.empty()) { | |
| return nullptr; | |
| } | |
| if (hash == s.fHash && key == Traits::GetKey(*s)) { | |
| return &*s; | |
| } | |
| index = this->next(index); | |
| } | |
| SkASSERT(fCapacity == fCount); | |
| return nullptr; | |
| } | |
| // If there is an entry in the table with this key, return it. If not, null. | |
| // This only works for pointer type T, and cannot be used to find an nullptr entry. | |
| T findOrNull(const K& key) const { | |
| if (T* p = this->find(key)) { | |
| return *p; | |
| } | |
| return nullptr; | |
| } | |
| // If a value with this key exists in the hash table, removes it and returns true. | |
| // Otherwise, returns false. | |
| bool removeIfExists(const K& key) { | |
| uint32_t hash = Hash(key); | |
| int index = hash & (fCapacity-1); | |
| for (int n = 0; n < fCapacity; n++) { | |
| Slot& s = fSlots[index]; | |
| if (s.empty()) { | |
| return false; | |
| } | |
| if (hash == s.fHash && key == Traits::GetKey(*s)) { | |
| this->removeSlot(index); | |
| if (fCapacity > 4) { | |
| bool shouldShrink = false; | |
| if constexpr (HasShouldShrink<Traits>::value) { | |
| shouldShrink = Traits::ShouldShrink(fCount, fCapacity); | |
| } else { | |
| shouldShrink = (4 * fCount <= fCapacity); | |
| } | |
| if (shouldShrink) { | |
| this->resize(fCapacity / 2); | |
| } | |
| } | |
| return true; | |
| } | |
| index = this->next(index); | |
| } | |
| SkASSERT(fCapacity == fCount); | |
| return false; | |
| } | |
| // Removes the value with this key from the hash table. Asserts if it is missing. | |
| void remove(const K& key) { | |
| SkAssertResult(this->removeIfExists(key)); | |
| } | |
| // Hash tables will automatically resize themselves when set() and remove() are called, but | |
| // resize() can be called to manually grow capacity before a bulk insertion. | |
| void resize(int capacity) { | |
| // We must have enough capacity to hold every key. | |
| SkASSERT(capacity >= fCount); | |
| // `capacity` must be a power of two, because we use `hash & (capacity-1)` to look up keys | |
| // in the table (since this is faster than a modulo). | |
| SkASSERT((capacity & (capacity - 1)) == 0); | |
| int oldCapacity = fCapacity; | |
| SkDEBUGCODE(int oldCount = fCount); | |
| fCount = 0; | |
| fCapacity = capacity; | |
| std::unique_ptr<Slot[]> oldSlots = std::move(fSlots); | |
| fSlots.reset(new Slot[capacity]); | |
| for (int i = 0; i < oldCapacity; i++) { | |
| Slot& s = oldSlots[i]; | |
| if (s.has_value()) { | |
| this->uncheckedSet(*std::move(s)); | |
| } | |
| } | |
| SkASSERT(fCount == oldCount); | |
| } | |
| // Reserve extra capacity. This only grows capacity; requests to shrink are ignored. | |
| // We assume that the passed-in value represents the number of items that the caller wants to | |
| // store in the table. The passed-in value is adjusted to honor the following rules: | |
| // - Hash tables must have a power-of-two capacity. | |
| // - Hash tables grow when they exceed 3/4 capacity, not when they are full. | |
| void reserve(int n) { | |
| int newCapacity = SkNextPow2(n); | |
| bool shouldGrow = false; | |
| if constexpr (HasShouldGrow<Traits>::value) { | |
| shouldGrow = Traits::ShouldGrow(n, newCapacity); | |
| } else { | |
| shouldGrow = (n * 4 > newCapacity * 3); | |
| } | |
| if (shouldGrow) { | |
| newCapacity *= 2; | |
| } | |
| if (newCapacity > fCapacity) { | |
| this->resize(newCapacity); | |
| } | |
| } | |
| // Call fn on every entry in the table. You may mutate the entries, but be very careful. | |
| template <typename Fn> // f(T*) | |
| void foreach(Fn&& fn) { | |
| for (int i = 0; i < fCapacity; i++) { | |
| if (fSlots[i].has_value()) { | |
| fn(&*fSlots[i]); | |
| } | |
| } | |
| } | |
| // Call fn on every entry in the table. You may not mutate anything. | |
| template <typename Fn> // f(T) or f(const T&) | |
| void foreach(Fn&& fn) const { | |
| for (int i = 0; i < fCapacity; i++) { | |
| if (fSlots[i].has_value()) { | |
| fn(*fSlots[i]); | |
| } | |
| } | |
| } | |
| // A basic iterator-like class which disallows mutation; sufficient for range-based for loops. | |
| // Intended for use by THashMap and THashSet via begin() and end(). | |
| // Adding or removing elements may invalidate all iterators. | |
| template <typename SlotVal> | |
| class Iter { | |
| public: | |
| using TTable = THashTable<T, K, Traits>; | |
| Iter(const TTable* table, int slot) : fTable(table), fSlot(slot) {} | |
| static Iter MakeBegin(const TTable* table) { | |
| return Iter{table, table->firstPopulatedSlot()}; | |
| } | |
| static Iter MakeEnd(const TTable* table) { | |
| return Iter{table, table->capacity()}; | |
| } | |
| const SlotVal& operator*() const { | |
| return *fTable->slot(fSlot); | |
| } | |
| const SlotVal* operator->() const { | |
| return fTable->slot(fSlot); | |
| } | |
| bool operator==(const Iter& that) const { | |
| // Iterators from different tables shouldn't be compared against each other. | |
| SkASSERT(fTable == that.fTable); | |
| return fSlot == that.fSlot; | |
| } | |
| bool operator!=(const Iter& that) const { | |
| return !(*this == that); | |
| } | |
| Iter& operator++() { | |
| fSlot = fTable->nextPopulatedSlot(fSlot); | |
| return *this; | |
| } | |
| Iter operator++(int) { | |
| Iter old = *this; | |
| this->operator++(); | |
| return old; | |
| } | |
| protected: | |
| const TTable* fTable; | |
| int fSlot; | |
| }; | |
| private: | |
| template <typename U, typename = void> struct HasShouldGrow : std::false_type {}; | |
| template <typename U, typename = void> struct HasShouldShrink : std::false_type {}; | |
| template <typename U> | |
| struct HasShouldGrow< | |
| U, | |
| std::void_t<decltype(U::ShouldGrow(std::declval<int>(), std::declval<int>()))>> | |
| : std::true_type { | |
| static_assert(HasShouldShrink<U>::value, | |
| "The traits class must also provide ShouldShrink() method."); | |
| }; | |
| template <typename U> | |
| struct HasShouldShrink< | |
| U, | |
| std::void_t<decltype(U::ShouldShrink(std::declval<int>(), std::declval<int>()))>> | |
| : std::true_type { | |
| static_assert(HasShouldGrow<U>::value, | |
| "The traits class must also provide ShouldGrow() method."); | |
| }; | |
| // Finds the first non-empty slot for an iterator. | |
| int firstPopulatedSlot() const { | |
| for (int i = 0; i < fCapacity; i++) { | |
| if (fSlots[i].has_value()) { | |
| return i; | |
| } | |
| } | |
| return fCapacity; | |
| } | |
| // Increments an iterator's slot. | |
| int nextPopulatedSlot(int currentSlot) const { | |
| for (int i = currentSlot + 1; i < fCapacity; i++) { | |
| if (fSlots[i].has_value()) { | |
| return i; | |
| } | |
| } | |
| return fCapacity; | |
| } | |
| // Reads from an iterator's slot. | |
| const T* slot(int i) const { | |
| SkASSERT(fSlots[i].has_value()); | |
| return &*fSlots[i]; | |
| } | |
| T* uncheckedSet(T&& val) { | |
| const K& key = Traits::GetKey(val); | |
| SkASSERT(key == key); | |
| uint32_t hash = Hash(key); | |
| int index = hash & (fCapacity-1); | |
| for (int n = 0; n < fCapacity; n++) { | |
| Slot& s = fSlots[index]; | |
| if (s.empty()) { | |
| // New entry. | |
| s.emplace(std::move(val), hash); | |
| fCount++; | |
| return &*s; | |
| } | |
| if (hash == s.fHash && key == Traits::GetKey(*s)) { | |
| // Overwrite previous entry. | |
| // Note: this triggers extra copies when adding the same value repeatedly. | |
| s.emplace(std::move(val), hash); | |
| return &*s; | |
| } | |
| index = this->next(index); | |
| } | |
| SkASSERT(false); | |
| return nullptr; | |
| } | |
| 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.reset(); | |
| return; | |
| } | |
| originalIndex = s.fHash & (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; } | |
| return index; | |
| } | |
| static uint32_t Hash(const K& key) { | |
| uint32_t hash = Traits::Hash(key) & 0xffffffff; | |
| return hash ? hash : 1; // We reserve hash 0 to mark empty. | |
| } | |
| class Slot { | |
| public: | |
| Slot() = default; | |
| ~Slot() { this->reset(); } | |
| Slot(const Slot& that) { *this = that; } | |
| Slot& operator=(const Slot& that) { | |
| if (this == &that) { | |
| return *this; | |
| } | |
| if (fHash) { | |
| if (that.fHash) { | |
| fVal.fStorage = that.fVal.fStorage; | |
| fHash = that.fHash; | |
| } else { | |
| this->reset(); | |
| } | |
| } else { | |
| if (that.fHash) { | |
| new (&fVal.fStorage) T(that.fVal.fStorage); | |
| fHash = that.fHash; | |
| } else { | |
| // do nothing, no value on either side | |
| } | |
| } | |
| return *this; | |
| } | |
| Slot(Slot&& that) { *this = std::move(that); } | |
| Slot& operator=(Slot&& that) { | |
| if (this == &that) { | |
| return *this; | |
| } | |
| if (fHash) { | |
| if (that.fHash) { | |
| fVal.fStorage = std::move(that.fVal.fStorage); | |
| fHash = that.fHash; | |
| } else { | |
| this->reset(); | |
| } | |
| } else { | |
| if (that.fHash) { | |
| new (&fVal.fStorage) T(std::move(that.fVal.fStorage)); | |
| fHash = that.fHash; | |
| } else { | |
| // do nothing, no value on either side | |
| } | |
| } | |
| return *this; | |
| } | |
| T& operator*() & { return fVal.fStorage; } | |
| const T& operator*() const& { return fVal.fStorage; } | |
| T&& operator*() && { return std::move(fVal.fStorage); } | |
| const T&& operator*() const&& { return std::move(fVal.fStorage); } | |
| Slot& emplace(T&& v, uint32_t h) { | |
| this->reset(); | |
| new (&fVal.fStorage) T(std::move(v)); | |
| fHash = h; | |
| return *this; | |
| } | |
| bool has_value() const { return fHash != 0; } | |
| explicit operator bool() const { return this->has_value(); } | |
| bool empty() const { return !this->has_value(); } | |
| void reset() { | |
| if (fHash) { | |
| fVal.fStorage.~T(); | |
| fHash = 0; | |
| } | |
| } | |
| uint32_t fHash = 0; | |
| private: | |
| union Storage { | |
| T fStorage; | |
| Storage() {} | |
| ~Storage() {} | |
| } fVal; | |
| }; | |
| int fCount = 0, | |
| fCapacity = 0; | |
| std::unique_ptr<Slot[]> fSlots; | |
| }; | |
| // Maps K->V. A more user-friendly wrapper around THashTable, suitable for most use cases. | |
| // K and V are treated as ordinary copyable C++ types, with no assumed relationship between the two. | |
| template <typename K, typename V, typename HashK = SkGoodHash> | |
| class THashMap { | |
| public: | |
| // Allow default construction and assignment. | |
| THashMap() = default; | |
| THashMap(THashMap<K, V, HashK>&& that) = default; | |
| THashMap(const THashMap<K, V, HashK>& that) = default; | |
| THashMap<K, V, HashK>& operator=(THashMap<K, V, HashK>&& that) = default; | |
| THashMap<K, V, HashK>& operator=(const THashMap<K, V, HashK>& that) = default; | |
| // Construct with an initializer list of key-value pairs. | |
| struct Pair : public std::pair<K, V> { | |
| using std::pair<K, V>::pair; | |
| static const K& GetKey(const Pair& p) { return p.first; } | |
| static auto Hash(const K& key) { return HashK()(key); } | |
| }; | |
| THashMap(std::initializer_list<Pair> pairs) { | |
| int capacity = pairs.size() >= 4 ? SkNextPow2(pairs.size() * 4 / 3) | |
| : 4; | |
| fTable.resize(capacity); | |
| for (const Pair& p : pairs) { | |
| fTable.set(p); | |
| } | |
| } | |
| // Clear the map. | |
| void reset() { fTable.reset(); } | |
| // How many key/value pairs are in the table? | |
| int count() const { return fTable.count(); } | |
| // Is empty? | |
| bool empty() const { return fTable.count() == 0; } | |
| // Approximately how many bytes of memory do we use beyond sizeof(*this)? | |
| size_t approxBytesUsed() const { return fTable.approxBytesUsed(); } | |
| // Reserve extra capacity. | |
| void reserve(int n) { fTable.reserve(n); } | |
| // Exchange two hash maps. | |
| void swap(THashMap& that) { fTable.swap(that.fTable); } | |
| void swap(THashMap&& that) { fTable.swap(std::move(that.fTable)); } | |
| // N.B. The pointers returned by set() and find() are valid only until the next call to set(). | |
| // Set key to val in the table, replacing any previous value with the same key. | |
| // We copy both key and val, and return a pointer to the value copy now in the table. | |
| V* set(K key, V val) { | |
| Pair* out = fTable.set({std::move(key), std::move(val)}); | |
| return &out->second; | |
| } | |
| // If there is key/value entry in the table with this key, return a pointer to the value. | |
| // If not, return null. | |
| V* find(const K& key) const { | |
| if (Pair* p = fTable.find(key)) { | |
| return &p->second; | |
| } | |
| return nullptr; | |
| } | |
| V& operator[](const K& key) { | |
| if (V* val = this->find(key)) { | |
| return *val; | |
| } | |
| return *this->set(key, V{}); | |
| } | |
| // Removes the key/value entry in the table with this key. Asserts if the key is not present. | |
| void remove(const K& key) { | |
| fTable.remove(key); | |
| } | |
| // If the key exists in the table, removes it and returns true. Otherwise, returns false. | |
| bool removeIfExists(const K& key) { | |
| return fTable.removeIfExists(key); | |
| } | |
| // Call fn on every key/value pair in the table. You may mutate the value but not the key. | |
| template <typename Fn, // f(K, V*) or f(const K&, V*) | |
| std::enable_if_t<std::is_invocable_v<Fn, K, V*>>* = nullptr> | |
| void foreach(Fn&& fn) { | |
| fTable.foreach([&fn](Pair* p) { fn(p->first, &p->second); }); | |
| } | |
| // Call fn on every key/value pair in the table. You may not mutate anything. | |
| template <typename Fn, // f(K, V), f(const K&, V), f(K, const V&) or f(const K&, const V&). | |
| std::enable_if_t<std::is_invocable_v<Fn, K, V>>* = nullptr> | |
| void foreach(Fn&& fn) const { | |
| fTable.foreach([&fn](const Pair& p) { fn(p.first, p.second); }); | |
| } | |
| // Call fn on every key/value pair in the table. You may not mutate anything. | |
| template <typename Fn, // f(Pair), or f(const Pair&) | |
| std::enable_if_t<std::is_invocable_v<Fn, Pair>>* = nullptr> | |
| void foreach(Fn&& fn) const { | |
| fTable.foreach([&fn](const Pair& p) { fn(p); }); | |
| } | |
| // Dereferencing an iterator gives back a key-value pair, suitable for structured binding. | |
| using Iter = typename THashTable<Pair, K>::template Iter<std::pair<K, V>>; | |
| Iter begin() const { | |
| return Iter::MakeBegin(&fTable); | |
| } | |
| Iter end() const { | |
| return Iter::MakeEnd(&fTable); | |
| } | |
| private: | |
| THashTable<Pair, K> fTable; | |
| }; | |
| // A set of T. T is treated as an ordinary copyable C++ type. | |
| template <typename T, typename HashT = SkGoodHash> | |
| class THashSet { | |
| public: | |
| // Allow default construction and assignment. | |
| THashSet() = default; | |
| THashSet(THashSet<T, HashT>&& that) = default; | |
| THashSet(const THashSet<T, HashT>& that) = default; | |
| THashSet<T, HashT>& operator=(THashSet<T, HashT>&& that) = default; | |
| THashSet<T, HashT>& operator=(const THashSet<T, HashT>& that) = default; | |
| // Construct with an initializer list of Ts. | |
| THashSet(std::initializer_list<T> vals) { | |
| int capacity = vals.size() >= 4 ? SkNextPow2(vals.size() * 4 / 3) | |
| : 4; | |
| fTable.resize(capacity); | |
| for (const T& val : vals) { | |
| fTable.set(val); | |
| } | |
| } | |
| // Clear the set. | |
| void reset() { fTable.reset(); } | |
| // How many items are in the set? | |
| int count() const { return fTable.count(); } | |
| // Is empty? | |
| bool empty() const { return fTable.count() == 0; } | |
| // Approximately how many bytes of memory do we use beyond sizeof(*this)? | |
| size_t approxBytesUsed() const { return fTable.approxBytesUsed(); } | |
| // Reserve extra capacity. | |
| void reserve(int n) { fTable.reserve(n); } | |
| // Exchange two hash sets. | |
| void swap(THashSet& that) { fTable.swap(that.fTable); } | |
| void swap(THashSet&& that) { fTable.swap(std::move(that.fTable)); } | |
| // Copy an item into the set. | |
| void add(T item) { fTable.set(std::move(item)); } | |
| // Is this item in the set? | |
| bool contains(const T& item) const { return SkToBool(this->find(item)); } | |
| // If an item equal to this is in the set, return a pointer to it, otherwise null. | |
| // This pointer remains valid until the next call to add(). | |
| const T* find(const T& item) const { return fTable.find(item); } | |
| // Remove the item in the set equal to this. | |
| void remove(const T& item) { | |
| SkASSERT(this->contains(item)); | |
| fTable.remove(item); | |
| } | |
| // Call fn on every item in the set. You may not mutate anything. | |
| template <typename Fn> // f(T), f(const T&) | |
| void foreach (Fn&& fn) const { | |
| fTable.foreach(fn); | |
| } | |
| private: | |
| struct Traits { | |
| static const T& GetKey(const T& item) { return item; } | |
| static auto Hash(const T& item) { return HashT()(item); } | |
| }; | |
| public: | |
| using Iter = typename THashTable<T, T, Traits>::template Iter<T>; | |
| Iter begin() const { | |
| return Iter::MakeBegin(&fTable); | |
| } | |
| Iter end() const { | |
| return Iter::MakeEnd(&fTable); | |
| } | |
| private: | |
| THashTable<T, T, Traits> fTable; | |
| }; | |
| } // namespace skia_private | |
| #endif // SkTHash_DEFINED |