So che STL ha un'API HashMap, ma non riesco a trovare alcuna documentazione valida e completa con buoni esempi al riguardo.
Eventuali buoni esempi saranno apprezzati.
So che STL ha un'API HashMap, ma non riesco a trovare alcuna documentazione valida e completa con buoni esempi al riguardo.
Eventuali buoni esempi saranno apprezzati.
Risposte:
La libreria standard include i contenitori ( std::map
e std::unordered_map
) della mappa ordinati e non ordinati . In una mappa ordinata gli elementi sono ordinati per chiave, inserire e l'accesso è in O (log n) . Di solito la libreria standard utilizza internamente alberi neri rossi per le mappe ordinate. Ma questo è solo un dettaglio di implementazione. In una mappa non ordinata inserire e accedere è in O (1). È solo un altro nome per un hashtable.
Un esempio con (ordinato) std::map
:
#include <map>
#include <iostream>
#include <cassert>
int main(int argc, char **argv)
{
std::map<std::string, int> m;
m["hello"] = 23;
// check if key is present
if (m.find("world") != m.end())
std::cout << "map contains key world!\n";
// retrieve
std::cout << m["hello"] << '\n';
std::map<std::string, int>::iterator i = m.find("hello");
assert(i != m.end());
std::cout << "Key: " << i->first << " Value: " << i->second << '\n';
return 0;
}
Produzione:
23 Chiave: ciao Valore: 23
Se hai bisogno di ordinare nel tuo contenitore e stai bene con il runtime O (log n), usa semplicemente std::map
.
Altrimenti, se hai davvero bisogno di una tabella hash (O (1) insert / access), dai un'occhiata std::unordered_map
, che ha std::map
un'API simile (ad esempio nell'esempio sopra devi solo cercare e sostituire map
con unordered_map
).
Il unordered_map
contenitore è stato introdotto con la revisione standard C ++ 11 . Pertanto, a seconda del compilatore, è necessario abilitare le funzionalità C ++ 11 (ad es. Quando si utilizza GCC 4.8 è necessario aggiungere -std=c++11
a CXXFLAGS).
Anche prima della versione C ++ 11 supportata da GCC unordered_map
- nello spazio dei nomi std::tr1
. Quindi, per i vecchi compilatori GCC puoi provare ad usarlo in questo modo:
#include <tr1/unordered_map>
std::tr1::unordered_map<std::string, int> m;
Fa anche parte del boost, ovvero è possibile utilizzare l' intestazione boost corrispondente per una migliore portabilità.
hash_map
SL STL in un modo o nell'altro.
unordered_map
. Pertanto, non vi è motivo di considerare il non standard hash_map
.
A hash_map
è una versione precedente e non standardizzata di ciò che ai fini della standardizzazione è chiamata an unordered_map
(originariamente in TR1 e inclusa nello standard dal C ++ 11). Come suggerisce il nome, è diverso dall'essere std::map
principalmente non ordinato: se, ad esempio, si scorre attraverso una mappa da begin()
a end()
, si ottengono gli elementi in ordine con il tasto 1 , ma se si scorre attraverso un unordered_map
da begin()
a end()
, si ottengono elementi in un ordine più o meno arbitrario.
unordered_map
Normalmente si prevede che An abbia una complessità costante. Cioè, un inserimento, una ricerca, ecc., Richiede in genere essenzialmente un periodo di tempo fisso, indipendentemente dal numero di elementi presenti nella tabella. Una std::map
complessità che è logaritmica sul numero di elementi archiviati, il che significa che il tempo per inserire o recuperare un oggetto aumenta, ma abbastanza lentamente , man mano che la mappa aumenta. Ad esempio, se ci vuole 1 microsecondo per cercare uno dei 1 milione di articoli, allora ci si può aspettare che ci vorranno circa 2 microsecondi per cercare uno dei 2 milioni di articoli, 3 microsecondi per uno dei 4 milioni di articoli, 4 microsecondi per uno degli 8 milioni articoli, ecc.
Da un punto di vista pratico, questa non è proprio l'intera storia. Per natura, una semplice tabella hash ha una dimensione fissa. Adattarlo ai requisiti di dimensioni variabili per un contenitore per scopi generici è in qualche modo non banale. Di conseguenza, le operazioni che (potenzialmente) fanno crescere la tabella (ad esempio, l'inserimento) sono potenzialmente relativamente lente (cioè la maggior parte sono abbastanza veloci, ma periodicamente una sarà molto più lenta). Le ricerche, che non possono modificare le dimensioni della tabella, sono generalmente molto più veloci. Di conseguenza, la maggior parte delle tabelle basate sull'hash tendono ad essere al meglio quando si eseguono molte ricerche rispetto al numero di inserimenti. Per le situazioni in cui si inseriscono molti dati, scorrere una volta nella tabella per recuperare i risultati (ad esempio, contando il numero di parole univoche in un file) è probabile che unstd::map
sarà altrettanto veloce e probabilmente anche più veloce (ma, ancora una volta, la complessità computazionale è diversa, quindi può dipendere anche dal numero di parole uniche nel file).
1 Dove l'ordine è definito dal terzo parametro del modello quando si crea la mappa, std::less<T>
per impostazione predefinita.
rehash
. Quando si chiama rehash
, si specifica una dimensione per la tabella. Tale dimensione verrà utilizzata a meno che ciò non superi il fattore di carico massimo specificato per la tabella (in tal caso, la dimensione verrà aumentata automaticamente per mantenere il fattore di carico entro i limiti).
Ecco un esempio più completo e flessibile che non omette le inclusioni necessarie per generare errori di compilazione:
#include <iostream>
#include <unordered_map>
class Hashtable {
std::unordered_map<const void *, const void *> htmap;
public:
void put(const void *key, const void *value) {
htmap[key] = value;
}
const void *get(const void *key) {
return htmap[key];
}
};
int main() {
Hashtable ht;
ht.put("Bob", "Dylan");
int one = 1;
ht.put("one", &one);
std::cout << (char *)ht.get("Bob") << "; " << *(int *)ht.get("one");
}
Ancora non particolarmente utile per le chiavi, a meno che non siano predefinite come puntatori, perché un valore corrispondente non lo farà! (Tuttavia, poiché normalmente utilizzo stringhe per chiavi, la sostituzione di "stringa" con "const void *" nella dichiarazione della chiave dovrebbe risolvere questo problema.)
void*
. Per i principianti, non c'è motivo di concludere unordered_map
poiché fa parte dello standard e riduce la manutenibilità del codice. Quindi, se insisti per avvolgerlo, usa templates
. Questo è esattamente quello per cui sono.
Prova che std::unordered_map
utilizza una mappa hash in GCC stdlibc ++ 6.4
Questo è stato menzionato su: https://stackoverflow.com/a/3578247/895245 ma nella seguente risposta: Qual è la struttura dei dati all'interno di std :: map in C ++? Ne ho fornito ulteriori prove per l'implementazione di GCC stdlibc ++ 6.4:
Ecco un'anteprima del grafico delle caratteristiche prestazionali descritto in quella risposta:
Come utilizzare una classe personalizzata e una funzione hash con unordered_map
Questa risposta lo inchioda: C ++ unordered_map usando un tipo di classe personalizzato come chiave
Estratto: uguaglianza:
struct Key
{
std::string first;
std::string second;
int third;
bool operator==(const Key &other) const
{ return (first == other.first
&& second == other.second
&& third == other.third);
}
};
Funzione hash:
namespace std {
template <>
struct hash<Key>
{
std::size_t operator()(const Key& k) const
{
using std::size_t;
using std::hash;
using std::string;
// Compute individual hash values for first,
// second and third and combine them using XOR
// and bit shifting:
return ((hash<string>()(k.first)
^ (hash<string>()(k.second) << 1)) >> 1)
^ (hash<int>()(k.third) << 1);
}
};
}
Per quelli di noi che cercano di capire come eseguire l'hashing delle proprie classi mentre si utilizza ancora il modello standard, esiste una soluzione semplice:
Nella tua classe devi definire un sovraccarico dell'operatore di uguaglianza ==
. Se non sai come fare, GeeksforGeeks ha un ottimo tutorial https://www.geeksforgeeks.org/operator-overloading-c/
Sotto lo spazio dei nomi standard, dichiara un tipo di struttura chiamato hash con il tuo nome classe come tipo (vedi sotto). Ho trovato un ottimo post sul blog che mostra anche un esempio di calcolo degli hash utilizzando XOR e bitshifting, ma non rientra nell'ambito di questa domanda, ma include anche istruzioni dettagliate su come eseguire le funzioni hash e https://prateekvjoshi.com/ 2014/06/05 / using-hash-funzione-in-c-per-user-classi definite /
namespace std {
template<>
struct hash<my_type> {
size_t operator()(const my_type& k) {
// Do your hash function here
...
}
};
}
std::map
o std::unordered_map
proprio come faresti normalmente e usarla my_type
come chiave, la libreria standard utilizzerà automaticamente la funzione hash definita in precedenza (nel passaggio 2) per eseguire l'hash le tue chiavi.#include <unordered_map>
int main() {
std::unordered_map<my_type, other_type> my_map;
}