web-dev-qa-db-fra.com

unordered_map avec la paire comme clé - pas de compilation

J'essaie de créer un uneredered_map pour mapper des paires avec des entiers. 

#include <unordered_map>

using namespace std;
using Vote = pair<string, string>;
using Unordered_map = unordered_map<Vote, int>;

J'ai une classe où j'ai déclaré Unordered_map en tant que membre privé. 

Cependant, je reçois cette erreur lorsque j'essaie de compiler:

/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/type_traits:948:38: Implicit instantiation of undefined template 'std::__1::hash<std::__1::pair<std::__1::basic_string<char>, std::__1::basic_string<char> > >'

Je n'obtiens pas cette erreur si j'utilise une carte régulière telle que map<pair<string, string>, int> au lieu d'une unordered_map.

N'est-il pas possible d'utiliser pair comme clé dans des cartes non ordonnées?

36
Mapplet

Vous devez fournir une fonction de hachage adaptée à votre type de clé. Un exemple simple:

#include <unordered_map>
#include <functional>
#include <string>
#include <utility>

// Only for pairs of std::hash-able types for simplicity.
// You can of course template this struct to allow other hash functions
struct pair_hash {
    template <class T1, class T2>
    std::size_t operator () (const std::pair<T1,T2> &p) const {
        auto h1 = std::hash<T1>{}(p.first);
        auto h2 = std::hash<T2>{}(p.second);

        // Mainly for demonstration purposes, i.e. works but is overly simple
        // In the real world, use sth. like boost.hash_combine
        return h1 ^ h2;  
    }
};

using Vote = std::pair<std::string, std::string>;
using Unordered_map = std::unordered_map<Vote, int, pair_hash>;

int main() {
    Unordered_map um;
}

Cela fonctionnera, mais n'aura pas les meilleures propriétés de hachage. Vous voudrez peut-être jeter un coup d'œil à quelque chose comme boost.hash_combine pour obtenir des résultats de meilleure qualité lors de la combinaison des hachages.

Pour une utilisation réelle: Boost fournit également la fonction set hash_value qui fournit déjà une fonction de hachage pour std::pair, ainsi que std::Tuple et la plupart des conteneurs standard.


Plus précisément, cela produira trop de collisions. Par exemple, chaque paire symétrique hachera à 0 et les paires qui ne diffèrent que par permutation auront le même hachage. C'est probablement bien pour votre exercice de programmation, mais cela pourrait sérieusement nuire aux performances du code réel.

44
Baum mit Augen

Pour résoudre ce problème, ma méthode préférée consiste à définir une fonction key qui transforme votre paire en un entier unique (ou tout type de données hachurable). Cette clé n'est pas la clé de hachage. C'est l'identifiant unique de la paire de données qui sera ensuite hachée de manière optimale par le unordered_map. Par exemple, vous vouliez définir un unordered_map du type

  unordered_map<pair<int,int>,double> Map;

Et vous souhaitez utiliser Map[make_pair(i,j)]=value ou Map.find(make_pair(i,j)) pour opérer sur la carte. Ensuite, vous devrez indiquer au système comment hacher une paire d'entiers make_pair(i,j). Au lieu de cela, nous pouvons définir

  inline size_t key(int i,int j) {return (size_t) i << 32 | (unsigned int) j;}

puis changez le type de la carte en

  unordered_map<size_t,double> Map;

Nous pouvons maintenant utiliser Map[key(i,j)]=value ou Map.find(key(i,j)) pour opérer sur la carte. Chaque make_pair appelle maintenant la fonction inline key.

Cette méthode garantit que la clé sera hachée de manière optimale, car maintenant, la partie de hachage est réalisée par le système, qui choisira toujours la taille de la table de hachage interne comme étant optimale pour garantir la même probabilité pour chaque compartiment. Mais vous devez vous assurer à 100% que la key est unique pour chaque paire, c'est-à-dire qu'il n'y a pas deux paires distinctes qui peuvent avoir la même clé, ou qu'il peut y avoir des bogues très difficiles à trouver.

6
Zhuoran He

Pour la clé de paire, nous pouvons utiliser la fonction de hachage de paire renforcée:

#include <iostream>
#include <boost/functional/hash.hpp>
#include <unordered_map>
using namespace std;

int main() {
  unordered_map<pair<string, string>, int, boost::hash<pair<string, string>>> m;

  m[make_pair("123", "456")] = 1;
  cout << m[make_pair("123", "456")] << endl;
  return 0;
}

De même, nous pouvons utiliser le hachage de boost pour les vecteurs,

#include <iostream>
#include <boost/functional/hash.hpp>
#include <unordered_map>
#include <vector>
using namespace std;

int main() {
  unordered_map<vector<string>, int, boost::hash<vector<string>>> m;
  vector<string> a({"123", "456"});

  m[a] = 1;
  cout << m[a] << endl;
  return 0;
}
5
Felix Guo

Comme votre erreur de compilation l'indique, il n'y a pas d'instanciation valide de std::hash<std::pair<std::string, std::string>> dans votre espace de noms standard.

Selon mon compilateur:

Erreur C2338 La norme C++ ne fournit pas de hachage pour cela type. c:\fichiers de programme (x86)\Microsoft Visual Studio 14.0\vc\include\xstddef 381

Vous pouvez fournir votre propre spécialisation pour std::hash<Vote> comme suit:

#include <string>
#include <unordered_map>
#include <functional>

using namespace std;
using Vote = pair<string, string>;
using Unordered_map = unordered_map<Vote, int>;

namespace std
{
    template<>
    struct hash<Vote>
    {
        size_t operator()(Vote const& v) const
        {
            // ... hash function here ...
        }
    };
}

int main()
{
    Unordered_map m;
}
3
bku_drytt

Dans les commentaires sur le réponse de Baum mit Augen, l'utilisateur Joe Black _ ​​ a demandé un exemple sur l'utilisation d'un expressions lambda au lieu de définir une fonction de hachage. Je partage l’avis de la opinion de Baum mit Augen, selon lequel cela pourrait nuire à la lisibilité, en particulier si vous souhaitez mettre en œuvre une solution plus universelle. Par conséquent, je voudrais garder mon exemple bref en me concentrant sur une solution spécifique pour std::pair<std::string, std::string>, telle que présentée par l'OP. L'exemple utilise également une fabrication artisanale combinaison d'appels de fonctions std::hash<std::string>:

using Vote = std::pair<std::string, std::string>;
auto hash = [](const Vote& v){
    return std::hash<std::string>()(v.first) * 31 + std::hash<std::string>()(v.second);
};
using Unordered_map = std::unordered_map<Vote, int, decltype(hash)>;
Unordered_map um(8, hash);

_ { Code on Ideone } _

0
honk

Je sais que cette implémentation est trop naïve par rapport à d’autres réponses, mais il existe une solution de contournement.

Si vous prenez l’entrée de paires, hachez simplement la paire avec un autre entier dans unordered_hash en prenant l’entrée, puis utilisez cette valeur intégrale indirectement pour hacher la paire, c.-à-d.

unordered_hash<int, Vote> h;
using Unordered_map = unordered_map<i, int>; // i is corresponding value to pair
0
Gaurav Pant