J'écrivais un algorithme ce matin et je me suis retrouvé dans une situation curieuse. J'ai deux std::map
S. Je souhaite effectuer une intersection d'ensemble sur les ensembles de clés de chacun (pour trouver quelles clés sont communes aux deux cartes). À un moment donné dans le futur, je pense qu'il est probable que je souhaite également effectuer une soustraction d'ensemble ici également. Heureusement, la STL comprend des fonctions pour ces deux opérations. Le problème est que je n'arrive pas à obtenir un std::set
Des clés d'un std::map
. Y a-t-il un moyen de faire ça? Je recherche quelque chose qui serait aussi simple, comme c'est le cas en Java:
std::set<Foo> keys = myMap.getKeySet();
Je crois comprendre que je ne peux pas utiliser la fonction std::set_intersection()
directement sur les itérateurs dans les cartes parce que les cartes exposent des objets std::pair
Au lieu de juste des clés. De plus, je ne pense pas que la carte garantit l'ordre. Je suis également intéressé à effectuer cette même opération sur une paire de std::multimap
S, si cela fait une différence.
EDIT : J'ai oublié de mentionner initialement qu'en raison de l'âge du compilateur je suis obligé d'utiliser (MSVC++ 6), la plupart des astuces de template astucieuses qui sont disponibles en boost ne peuvent pas être utilisés.
Vous pouvez utiliser le polyvalent boost :: transform_iterator pour renvoyer un itérateur qui ne renvoie que les clés (et non les valeurs). Voir Comment récupérer toutes les clés (ou valeurs) d'un std :: map et les mettre dans un vecteur?
Ce que vous voulez en gros, c'est une copie, car std :: map ne garde pas les clés dans un std :: set. std :: copy suppose que les types valeur sont compatibles, ce qui n'est pas le cas ici. Le std :: map :: value_type est un std :: pair. Vous souhaitez copier uniquement la première partie de la paire, ce qui signifie que vous avez besoin d'un std :: transform. Maintenant, puisque vous allez utiliser un insert_iterator sur l'ensemble, l'ordre n'a pas d'importance. Le std :: set triera lors de l'insertion, même si la carte a déjà été triée.
[modifier] Le code pourrait être plus simple. Haut de ma tête, non compilé.
std::transform(MyMap.begin(), MyMap.end(),
std::inserter(MySet, MySet.end()),
boost::bind(&std::pair<Key,Value>::first, _1));
Si vous possédez le select1st de SGI, vous n'avez pas besoin de boost :: bind.
[modifier] Mis à jour pour C++ 14
std::transform(MyMap.begin(), MyMap.end(),
std::inserter(MySet, MySet.end()),
[](auto pair){ return pair.first; });
La carte garantit l'ordre; c'est pourquoi on l'appelle conteneur associatif trié . Vous pouvez utiliser set_intersection avec une fonction de comparateur personnalisée, la deuxième variante répertoriée ici .
Alors, quelque chose comme
bool your_less(const your_map::value_type &v1, const your_map::value_type &v2)
{ return v1.first < v2.first; }
set_intersection(m1.begin(), m1.end(), m2.begin(), m2.end(), your_output_it, your_less);
devrait faire l'affaire. (Il est également possible d'utiliser boost :: lambda et bind pour éviter d'écrire une fonction temporaire.)
L'opérateur par défaut <over pairs compare les deux composants. Étant donné que vous n'avez besoin d'équivalence que sur la première partie de la paire (la clé de carte), vous devez définir votre propre opérateur de comparaison qui fournit une telle relation (ce que fait la fonction ci-dessus).
En pratique,
yourmap::const_iterator mi;
set<key_type> k;
for (mi = yourmap.begin(); mi != yourmap.end(); ++mi)
k.insert(mi->first);
return k;
La meilleure solution compatible avec les algorithmes STL non SGI et non boost est d'étendre map :: iterator comme suit:
template<typename map_type>
class key_iterator : public map_type::iterator
{
public:
typedef typename map_type::iterator map_iterator;
typedef typename map_iterator::value_type::first_type key_type;
key_iterator(const map_iterator& other) : map_type::iterator(other) {} ;
key_type& operator *()
{
return map_type::iterator::operator*().first;
}
};
// helpers to create iterators easier:
template<typename map_type>
key_iterator<map_type> key_begin(map_type& m)
{
return key_iterator<map_type>(m.begin());
}
template<typename map_type>
key_iterator<map_type> key_end(map_type& m)
{
return key_iterator<map_type>(m.end());
}
puis utilisez-les comme ceci:
map<string,int> test;
test["one"] = 1;
test["two"] = 2;
set<string> keys;
// // method one
// key_iterator<map<string,int> > kb(test.begin());
// key_iterator<map<string,int> > ke(test.end());
// keys.insert(kb, ke);
// // method two
// keys.insert(
// key_iterator<map<string,int> >(test.begin()),
// key_iterator<map<string,int> >(test.end()));
// method three (with helpers)
keys.insert(key_begin(test), key_end(test));
J'ai trouvé un bon lien pour votre question ici
et ayez du code pour votre problème:
#include <iostream>
#include <map>
#include <set>
#include <iterator>
typedef std::map<std::string, int> MyMap;
// also known as select1st in SGI STL implementation
template<typename T_PAIR>
struct GetKey: public std::unary_function<T_PAIR, typename T_PAIR::first_type>
{
const typename T_PAIR::first_type& operator()(const T_PAIR& item) const
{
return item.first;
}
};
int main(int argc, char** argv)
{
MyMap m1,m2;
m1["a"] = 1;
m1["b"] = 2;
m2["c"] = 3;
m2["b"] = 3;
std::set<std::string> s;
std::transform(m1.begin(), m1.end(), std::inserter(s, s.begin()), GetKey<MyMap::value_type>());
std::transform(m2.begin(), m2.end(), std::inserter(s, s.begin()), GetKey<MyMap::value_type>());
std::copy(s.begin(), s.end(), std::ostream_iterator<std::string>(std::cout, " "));
std::cout << std::endl;
return 0;
}
Vous pouvez simplement parcourir et ajouter chaque clé à un ensemble. Les ensembles et les cartes sont tous deux ordonnés, les variantes non ordonnées ne le sont pas.
Vous pourriez peut-être créer un itérateur pour une carte qui ne donne les clés qu'en utilisant boost :: adapters :: map_key, voir l'exemple dans la documentation boost :: adapters :: map_key . Cela semble avoir été introduit dans Boost 1.43, et est censé être compatible C++ 2003, mais je ne connais pas spécifiquement VC++ 6, qui date de l'ère C++ 98.
À partir de la réponse de zvrba et du commentaire de dianot:
Faites simplement que la collection réceptrice soit un vecteur de paires au lieu d'une carte, et le problème signalé par dianot est terminé. Donc, en utilisant l'exemple zvrba:
std::vector<std::pair<keytype, valtype>> v;
set_intersection(m1.begin(), m1.end(), m2.begin(), m2.end(),
std::back_inserter(v), []( std::pair<keytype, valtype> const & a,
std::pair<keytype, valtype> const & b){return a.first < b.first;});
Ainsi, sans construire de copies ou d'ensembles intermédiaires, nous pouvons obtenir efficacement l'intersection de deux cartes. Cette construction se compile avec gcc5.3.