| |
| #ifndef HASHMAP_H |
| #define HASHMAP_H |
| #include <iostream> |
| #include <iomanip> |
| #include <exception> |
| #include <mutex> |
| #include <condition_variable> |
| |
| /* |
| * wrapper for items stored in the map |
| */ |
| template <typename K, typename V> |
| class HashItem { |
| public: |
| HashItem(K key, V value) { |
| this->key = key; |
| this->value = value; |
| this->nextItem = nullptr; |
| } |
| |
| /* |
| * copy constructor |
| */ |
| HashItem(const HashItem &item) { |
| this->key = item.getKey(); |
| this->value = item.getValue(); |
| this->nextItem = nullptr; |
| } |
| |
| void setNext(HashItem<K, V> *item) { this->nextItem = item; } |
| |
| HashItem *getNext() { return nextItem; } |
| |
| K getKey() { return key; } |
| |
| V getValue() { return value; } |
| |
| void setValue(V value) { this->value = value; } |
| |
| private: |
| K key; |
| V value; |
| HashItem *nextItem; |
| }; |
| |
| /* |
| * template HashMap for storing items |
| * default hash function HF = std::hash<K> |
| */ |
| template <typename K, typename V, typename HF = std::hash<K>> |
| class HashMap { |
| public: |
| /* |
| * constructor |
| * @mSize specifies the bucket size og the map |
| */ |
| HashMap(std::size_t mSize) { |
| // lock initialization for single thread |
| std::lock_guard<std::mutex> lock(mtx); |
| // if (mSize < 1) |
| // throw std::exception("Number of buckets ust be greater than zero."); |
| |
| mapSize = mSize; |
| numOfItems = 0; |
| // initialize |
| hMap = new HashItem<K, V> *[mapSize](); |
| } |
| |
| /* |
| * for simplicity no copy constructor |
| * anyway we want test how different threads |
| * use same instance of the map |
| */ |
| // HashMap(const HashMap & hmap) = delete; |
| |
| /* |
| * inserts item |
| * replaces old value with the new one when item already exists |
| * @key key of the item |
| * @value value of the item |
| */ |
| void insert(const K &key, const V &value) { |
| std::lock_guard<std::mutex> lock(mtx); |
| insertHelper(this->hMap, this->mapSize, numOfItems, key, value); |
| condVar.notify_all(); |
| } |
| |
| /* |
| * erases item with key when siúch item exists |
| * @key of item to erase |
| */ |
| void erase(const K &key) { |
| std::lock_guard<std::mutex> lock(mtx); |
| // calculate the bucket where item must be inserted |
| std::size_t hVal = hashFunc(key) % mapSize; |
| HashItem<K, V> *prev = nullptr; |
| HashItem<K, V> *item = hMap[hVal]; |
| |
| while ((item != nullptr) && (item->getKey() != key)) { |
| prev = item; |
| item = item->getNext(); |
| } |
| // no item found with the given key |
| if (item == nullptr) { |
| return; |
| } else { |
| if (prev == nullptr) { |
| // item found is the first item in the bucket |
| hMap[hVal] = item->getNext(); |
| } else { |
| // item found in one of the entries in the bucket |
| prev->setNext(item->getNext()); |
| } |
| delete item; |
| numOfItems--; |
| } |
| condVar.notify_all(); |
| } |
| |
| /* |
| * get element with the given key by reference |
| * @key is the key of item that has to be found |
| * @value is the holder where the value of item with key will be copied |
| */ |
| bool getItem(const K &key, V &value) const { |
| std::lock_guard<std::mutex> lock(mtx); |
| // calculate the bucket where item must be inserted |
| std::size_t hVal = hashFunc(key) % mapSize; |
| HashItem<K, V> *item = hMap[hVal]; |
| |
| while ((item != nullptr) && (item->getKey() != key)) item = item->getNext(); |
| // item not found |
| if (item == nullptr) { |
| return false; |
| } |
| |
| value = item->getValue(); |
| return true; |
| } |
| |
| /* |
| * get element with the given key by reference |
| * @key is the key of item that has to be found |
| * shows an example of thread waitung for some condition |
| * @value is the holder where the value of item with key will be copied |
| */ |
| bool getWithWait(const K &key, V &value) { |
| std::unique_lock<std::mutex> ulock(mtxForWait); |
| condVar.wait(ulock, [this] { return !this->empty(); }); |
| // calculate the bucket where item must be inserted |
| std::size_t hVal = hashFunc(key) % mapSize; |
| HashItem<K, V> *item = hMap[hVal]; |
| |
| while ((item != nullptr) && (item->getKey() != key)) item = item->getNext(); |
| // item not found |
| if (item == nullptr) { |
| return false; |
| } |
| |
| value = item->getValue(); |
| return true; |
| } |
| |
| /* |
| * resizes the map |
| * creates new map on heap |
| * copies the elements into new map |
| * @newSize specifies new bucket size |
| */ |
| void resize(std::size_t newSize) { |
| std::lock_guard<std::mutex> lock(mtx); |
| if (newSize < 1) |
| throw std::exception("Number of buckets must be greater than zero."); |
| |
| resizeHelper(newSize); |
| condVar.notify_all(); |
| } |
| |
| /* |
| * outputs all items of the map |
| */ |
| void outputMap() const { |
| std::lock_guard<std::mutex> lock(mtx); |
| if (numOfItems == 0) { |
| std::cout << "Map is empty." << std::endl << std::endl; |
| return; |
| } |
| std::cout << "Map contains " << numOfItems << " items." << std::endl; |
| for (std::size_t i = 0; i < mapSize; i++) { |
| HashItem<K, V> *item = hMap[i]; |
| while (item != nullptr) { |
| std::cout << "Bucket: " << std::setw(3) << i |
| << ", key: " << std::setw(3) << item->getKey() |
| << ", value:" << std::setw(3) << item->getValue() |
| << std::endl; |
| item = item->getNext(); |
| } |
| } |
| std::cout << std::endl; |
| } |
| |
| /* |
| * returns true when map has no items |
| */ |
| bool empty() const { |
| std::lock_guard<std::mutex> lock(mtx); |
| return numOfItems == 0; |
| } |
| |
| void clear() { |
| std::lock_guard<std::mutex> lock(mtx); |
| deleteMap(hMap, mapSize); |
| numOfItems = 0; |
| hMap = new HashItem<K, V> *[mapSize](); |
| } |
| |
| void deleteDeep() { |
| std::lock_guard<std::mutex> lock(mtx); |
| deleteMapDeep(hMap, mapSize); |
| numOfItems = 0; |
| hMap = new HashItem<K, V> *[mapSize](); |
| } |
| |
| /* |
| * returns number of items stored in the map |
| */ |
| std::size_t size() const { |
| std::lock_guard<std::mutex> lock(mtx); |
| return numOfItems; |
| } |
| |
| /* |
| * returns number of buckets |
| */ |
| std::size_t bucket_count() const { |
| std::lock_guard<std::mutex> lock(mtx); |
| return mapSize; |
| } |
| |
| /* |
| * desctructor |
| */ |
| ~HashMap() { |
| std::lock_guard<std::mutex> lock(mtx); |
| deleteMap(hMap, mapSize); |
| } |
| |
| private: |
| std::size_t mapSize; |
| std::size_t numOfItems; |
| HF hashFunc; |
| HashItem<K, V> **hMap; |
| mutable std::mutex mtx; |
| mutable std::mutex mtxForWait; |
| std::condition_variable condVar; |
| |
| /* |
| * help method for inserting key, value item into the map hm |
| * mapSize specifies the size of the map, items - the number |
| * of stored items, will be incremented when insertion is completed |
| * @hm HashMap |
| * @mSize specifies number of buckets |
| * @items holds the number of items in hm, will be incremented when insertion |
| * successful |
| * @key - key of item to insert |
| * @value - value of item to insert |
| */ |
| void insertHelper(HashItem<K, V> **hm, const std::size_t &mSize, |
| std::size_t &items, const K &key, const V &value) { |
| std::size_t hVal = hashFunc(key) % mSize; |
| HashItem<K, V> *prev = nullptr; |
| HashItem<K, V> *item = hm[hVal]; |
| |
| while ((item != nullptr) && (item->getKey() != key)) { |
| prev = item; |
| item = item->getNext(); |
| } |
| |
| // inserting new item |
| if (item == nullptr) { |
| item = new HashItem<K, V>(key, value); |
| items++; |
| if (prev == nullptr) { |
| // insert new value as first item in the bucket |
| hm[hVal] = item; |
| } else { |
| // append new item on previous in the same bucket |
| prev->setNext(item); |
| } |
| } else { |
| // replace existing value |
| item->setValue(value); |
| } |
| } |
| |
| /* |
| * help method to resize the map |
| * @newSize specifies new number of buckets |
| */ |
| void resizeHelper(std::size_t newSize) { |
| HashItem<K, V> **newMap = new HashItem<K, V> *[newSize](); |
| std::size_t items = 0; |
| for (std::size_t i = 0; i < mapSize; i++) { |
| HashItem<K, V> *item = hMap[i]; |
| while (item != nullptr) { |
| insertHelper(newMap, newSize, items, item->getKey(), item->getValue()); |
| item = item->getNext(); |
| } |
| } |
| |
| deleteMap(hMap, mapSize); |
| hMap = newMap; |
| mapSize = newSize; |
| numOfItems = items; |
| newMap = nullptr; |
| } |
| |
| /* |
| * help function for deleting the map hm |
| * @hm HashMap |
| * @mSize number of buckets in hm |
| */ |
| void deleteMap(HashItem<K, V> **hm, std::size_t mSize) { |
| // delete all nodes |
| for (std::size_t i = 0; i < mSize; ++i) { |
| HashItem<K, V> *item = hm[i]; |
| while (item != nullptr) { |
| HashItem<K, V> *prev = item; |
| item = item->getNext(); |
| delete prev; |
| } |
| hm[i] = nullptr; |
| } |
| // delete the map |
| delete[] hm; |
| } |
| |
| /* |
| * help function for deleting the map hm |
| * @hm HashMap |
| * @mSize number of buckets in hm |
| */ |
| void deleteMapDeep(HashItem<K, V> **hm, std::size_t mSize) { |
| // delete all nodes |
| for (std::size_t i = 0; i < mSize; ++i) { |
| HashItem<K, V> *item = hm[i]; |
| while (item != nullptr) { |
| HashItem<K, V> *prev = item; |
| item = item->getNext(); |
| delete prev->getValue(); |
| delete prev; |
| } |
| hm[i] = nullptr; |
| } |
| // delete the map |
| delete[] hm; |
| } |
| }; |
| |
| #endif /* HASHMAP_H */ |