Реализация хэш-карты в C++: функция хэширования для шаблонного типа данных


В последнее время я использую STL unordered_map, и хотя это, кажется, работает хорошо, я не совсем понимаю, как работает функция хэширования, учитывая, что тип данных задан в качестве параметра шаблона. В попытке понять эту структуру данных более подробно, я реализовал свой собственный маленький класс Hashmap в C++:

Интерфейс Hashmap:

#ifndef _HASHMAP_H_
#define _HASHMAP_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <vector.h>


//Beginning of Hashmap class definition

template <class Key, class Value>
class Hashmap{
private:

int mappedElementCount;



public:
explicit Hashmap();
virtual ~Hashmap();


virtual void test();

virtual int hash(Key*);

int* getSize();

void putKVPair(Key*,Value*);

void clearMap();


//When we use these methods, we'll want a linear vector of keys and values to 
    //iterate over, so vector is good here
std::vector<Key>* getKeys();
std::vector<Value>* getValues();

}; //end hashmap class definition
#endif /*_HASHMAP_H_*/

Реализация Hashmap:

#include "Hashmap.h"

template<class Key,class Value> Hashmap<Key,Value>::Hashmap(){
mappedElementCount = 0;
}
template<class Key,class Value> Hashmap<Key,Value>::~Hashmap(){
printf("nDestroying the base Hashmap object!n");
}

template<class Key,class Value> void Hashmap<Key,Value>::test(){
printf("The size of our Key is %i and the size of our Value is
    %in",sizeof(Key),sizeof(Value));
}


template<class Key,class Value> int Hashmap<Key,Value>::hash(Key* k_ptr){

    unsigned int hashval;

    /* we start our hash out at 0 */
    hashval = 0;

        //TODO: How do we generate a hash signature when we don't know what data type 
        //we're going to be working with?

    return hashval % mappedElementCount;

}

template<class Key,class Value> std::vector<Key>* Hashmap<Key,Value>::getKeys(){
//TODO: prepare a vector initialized with all Key objects and return it here
return keys;    
}

template<class Key,class Value> std::vector<Value>* Hashmap<Key,Value>::getValues(){
//TODO: prepare a vector initialized with all Value objects and return it here
return values;  
}

template<class Key,class Value> int* Hashmap<Key,Value>::getSize(){
return &mappedElementCount;
}

template<class Key,class Value> void Hashmap<Key,Value>::putKVPair(Key* k, Value* v){
    //TODO: implement hashing of the key object k to determine
    //the address of the value object v

    //first step, generate a hash from our key
    int tempHash = hash(k);

       //TODO: store the Value at an address given by or influenced by tempHash

    //If all was successfully completed, increment the mapped records counter
    mappedElementCount++;
}



template<class Key,class Value> void Hashmap<Key,Value>::clearMap(){
//TODO: implement a cascading chain of deallocation of stored objects within the 
    //hashmap
//MAYBE-- only if we create new objects rather than just mapping reference 
    //associations,
//which is really the goal here...  In the latter case, just empty the Hashmap 
    //itself
}

Одним из возможных методов решения этой проблемы является использование Hashmap в качестве базового класса и обеспечьте производные классы, которые имеют известные типы ключевых данных, такие как следующая Stringmap:

Интерфейс Stringmap:

#ifndef _STRINGMAP_H_
#define _STRINGMAP_H_

#include "Hashmap.h"

template <class Value>
class Stringmap:public Hashmap<std::string,Value>{
private:

public:
//Con/de 'structors
explicit Stringmap();
~Stringmap();

//Here we know our Key will be of type std::string
//so we can generate our hash sig by char values
    //Override hash from the base class
int hash(std::string*);

//override test from base class
void test();


};
#endif /*_STRINGMAP_H_ def*/

Реализация Stringmap:

#include "Stringmap.h"

template<class Value> Stringmap<Value>::Stringmap():Hashmap<std::string,Value>(){

}
template<class Value> Stringmap<Value>::~Stringmap(){
printf("nDestroying the derived stringmap object!n");
}

template<class Value> void Stringmap<Value>::test(){
printf("The size of our Value is %in",sizeof values[0]);
}

template<class Value> int Stringmap<Value>::hash(std::string* str_ptr){

    unsigned int hashval;

    /* we start our hash out at 0 */
    hashval = 0;


    /* for each character, we multiply the old hash by 31 and add the current
     * character.  Remember that shifting a number left is equivalent to
     * multiplying it by 2 raised to the number of places shifted.  So we
     * are in effect multiplying hashval by 32 and then subtracting hashval.
     * Why do we do this?  Because shifting and subtraction are much more
     * efficient operations than multiplication.
     */
    for(int i=0;i<str_ptr->length();i++) {
        hashval = (*(str_ptr))[i] + ((hashval << 5) - hashval);
    }

    /* we then return the hash value mod the hashmap size so that it will
     * fit into the necessary range
     */
    return hashval % (*(Hashmap<std::string,Value>::getSize()));

}

Таким образом, вопрос заключается в следующем: возможно ли создать хэш-сигнатуру, когда тип данных, подлежащих хэшу, в настоящее время неизвестен? Если да, то как? Глядя на документы std::hash, оказывается, что стандарт C++ просто определяет хэш-функцию для каждого примитивного типа данных, а также для T* (для любого типа T)... Чего не хватает это то, как это хеширование реализовано для данных примитивных типов данных и, более того, как оно реализовано для универсального T*. Наверное, я мог бы просто позвонить хэшу (ключу) и надеяться на лучшее, но было бы неплохо понять, что происходит за кулисами.

Спасибо, CCJ

2 3

2 ответа:

std::unorderd_map принимает 2 явных параметра шаблона (Key и Value), а также имеет кучу скрытых параметров шаблона, из которых хэш-функция по умолчанию имеет значение std::hash<Key>.

Эта хеш-функция STL std::hash<Key> принимает значение Key и возвращает значение std::size_t. Он уже специализирован для всех интегральных типов и std::string. Из этого справочного сайта

Шаблон хэша определяет объект функции, реализующий хэш функция. Экземпляры этого объекта функции определяют оператор (), который:

  1. принимает один параметр типа Key.
  2. возвращает значение типа size_t, представляющее хэш-значение параметра.
  3. не создает исключений при вызове.
  4. для двух параметров k1 и k2, которые равны, std:: hash () (k1) = = std::hash () (k2).
  5. для двух различных параметров k1 и k2, которые не равны, вероятность того, что std:: hash () (k1) = = std:: hash () (k2) должна будьте очень маленькими, приближаясь 1.0 / std:: numeric_limits:: max ().

Шаблон хэша является одновременно копируемым и разрушаемым. То неупорядоченные ассоциативные контейнеры std:: unordered_set, std:: unordered_multiset, std:: unordered_map, std:: unordered_multimap используйте специализации шаблона std:: hash в качестве хэша по умолчанию функция.

Ссылка заканчивается этой цитатой:

** фактические хэш-функции зависят от реализации и не требуются для выполнения каких-либо других функций. критерии качества, за исключением указанных выше. **

Таким образом, вы можете посмотреть на реализацию своей системы, но это ничего не гарантирует для реализации других систем.

Есть шаблон std::hash<T>, который специализирован для различных типов, и который вы можете специализировать для своих собственных типов.

По умолчанию std::unordered_map<T> просто делегирует хэширование std::hash<T> (или вы можете указать другую хэш-функцию в качестве аргумента шаблона).

Таким образом,

Не нужно вообще ничего знать о механике хеширования.

Что касается способа реализации std::hash, то он не указан. Тем не менее, я думаю, что разумно предположить, что любой приличный компилятор обеспечит качественная реализация. Нужно иметь в виду, что std::hash<char*> не хэширует строку C, он только хэширует значение указателя (был там :))