web-dev-qa-db-fra.com

Comment créer un ensemble non ordonné de paires d'entiers en C++?

Le programme suivant ne compile pas un ensemble non ordonné de paires d'entiers, il le fait pour les entiers. unordered_set et ses fonctions membres peuvent-ils être utilisés sur des types définis par l'utilisateur, et comment puis-je le définir? 

#include <unordered_set>
...

class A{
...
private: 
    std::unordered_set< std::pair<int, int> > u_Edge_;
};

Erreur du compilateur:

erreur: pas de fonction correspondante pour l'appel à 'std :: unordered_set> :: unordered_set ()'

36
Pippi

Votre code est compilé sur VS2010 SP1 (VC10), mais échoue avec GCC g ++ 4.7.2.

Cependant, vous voudrez peut-être envisager d'utiliser boost::hash de Boost.Functional pour hacher un std::pair (avec cet ajout, votre code est également compilé avec g ++).

#include <unordered_set>
#include <boost/functional/hash.hpp>

class A
{
private: 
    std::unordered_set< 
        std::pair<int, int>, 
        boost::hash< std::pair<int, int> > 
    > u_Edge_;
};
22
Mr.C64

Il n'y a pas de méthode standard pour calculer un hachage sur une paire. Ajoutez cette définition à votre fichier:

struct pair_hash {
    inline std::size_t operator()(const std::pair<int,int> & v) const {
        return v.first*31+v.second;
    }
};

Maintenant, vous pouvez l'utiliser comme ceci:

std::unordered_set< std::pair<int, int>,  pair_hash> u_Edge_;

Cela fonctionne car pair<T1,T2> définit l'égalité. Pour les classes personnalisées qui ne permettent pas de tester l'égalité, vous devrez peut-être fournir une fonction distincte pour tester si deux instances sont égales.

Bien entendu, cette solution est limitée à une paire de deux entiers. Voici un lien vers une réponse qui vous aide à définir un moyen plus général de créer un hachage pour plusieurs objets.

34
dasblinkenlight

Le problème est que std::unordered_set utilise std::hash template pour calculer les hachages pour ses entrées et qu'il n'existe pas de spécialisation std::hash pour les paires. Donc, vous devrez faire deux choses:

  1. Décidez quelle fonction de hachage vous souhaitez utiliser.
  2. Spécialisez std::hash pour votre type de clé (std::pair<int, int>) en utilisant cette fonction.

Voici un exemple simple:

#include <unordered_set>

namespace std {
template <> struct hash<std::pair<int, int>> {
    inline size_t operator()(const std::pair<int, int> &v) const {
        std::hash<int> int_hasher;
        return int_hasher(v.first) ^ int_hasher(v.second);
    }
};

}

int main()
{
    std::unordered_set< std::pair<int, int> > Edge;
}
8
user405725

Vous devez fournir une spécialisation pour std::hash<> qui fonctionne avec std::pair<int, int>. Voici un exemple très simple de la définition de la spécialisation:

#include <utility>
#include <unordered_set>

namespace std
{
    template<>
    struct hash<std::pair<int, int>>
    {
        size_t operator () (std::pair<int, int> const& p)
        {
            // A bad example of computing the hash, 
            // rather replace with something more clever
            return (std::hash<int>()(p.first) + std::hash<int>()(p.second));
        }
    };
}

class A
{
private:
    // This won't give you problems anymore
    std::unordered_set< std::pair<int, int> > u_Edge_;
};
3
Andy Prowl

Il vous manque une fonction de hachage pour std::pair<int, int>>. Par exemple,

struct bad_hash
{
  std::size_t operator()(const std::pair<int,int>& p) const
  {
    return 42;
  }
};

....

std::unordered_set< std::pair<int, int>, bad_hash> u_Edge_;

Vous pouvez également spécialiser std::hash<T> pour std::hash<std::pair<int,int>>, auquel cas vous pouvez omettre le deuxième paramètre de modèle.

1
juanchopanza

Les autres réponses proposées ici suggèrent toutes de créer une fonction de hachage combinant vos deux entiers.

Cela fonctionnera, mais produira des hachages non uniques. Bien que cela convienne pour votre utilisation de unordered_set, il peut être inacceptable pour certaines applications. Dans votre cas, si vous choisissez une mauvaise fonction de hachage, cela peut entraîner de nombreuses collisions inutiles.

Mais vous pouvez produire des hachages uniques!

int est généralement 4 octets. Vous pouvez rendre ceci explicite en utilisant int32_t.

Le type de données du hachage est std::size_t. Sur la plupart des machines, il s'agit de 8 octets. Vous pouvez vérifier ceci lors de la compilation.

Puisqu'une paire est constituée de deux types int32_t, vous pouvez placer les deux nombres dans un std::size_t pour créer un hachage unique.

Cela ressemble à ceci (je ne sais pas comment obliger le compilateur à traiter une valeur signée comme si elle n'était pas signée pour la manipulation de bits, alors j'ai écrit ceci pour uint32_t.)

#include <cassert>
#include <cstdint>
#include <unordered_set>
#include <utility>


struct IntPairHash {
  std::size_t operator()(const std::pair<uint32_t, uint32_t> &p) const {
    assert(sizeof(std::size_t)>=8);  //Ensure that std::size_t, the type of the hash, is large enough
    //Shift first integer over to make room for the second integer. The two are
    //then packed side by side.
    return (((uint64_t)p.first)<<32) | ((uint64_t)p.second);
  }
};

int main(){
  std::unordered_set< std::pair<uint32_t, uint32_t>, IntPairHash> uset;
  uset.emplace(10,20);
  uset.emplace(20,30);
  uset.emplace(10,20);
  assert(uset.size()==2);
}
1
Richard

Comme déjà mentionné dans la plupart des autres réponses à cette question, vous devez fournir une fonction de hachage pour std::pair<int, int>. Cependant, depuis C++ 11 , vous pouvez également utiliser un expression lambda au lieu de définir une fonction de hachage. Le code suivant prend la solution donnée par dasblinkenlight comme base:

auto hash = [](const std::pair<int, int>& p){ return p.first * 31 + p.second; };
std::unordered_set<std::pair<int, int>, decltype(hash)> u_Edge_(8, hash);

Code sur Ideone

Je voudrais répéter l'avertissement de dasblinkenlight: cette solution est limitée à une paire de deux entiers. Cette réponse donne l’idée d’une solution plus générale.

0
honk