web-dev-qa-db-fra.com

existe-t-il un itérateur sur des clés uniques dans un std :: multimap?

Existe-t-il un moyen simple ou standard d’avoir un itérateur multi-cartes qui parcourt des clés uniques dans une multi-cartes?

c'est-à-dire que pour un ensemble ressemblant à: {1, "a"}, {1, "lemon"}, {2, "peacock"}, {3, "angel"}, un itérateur commençant par {1, "a"} puis incrémenté pointant vers {2, "peacock"}, puis incrémenté à nouveau pointant vers {3, "angel"}?

37
Bingo

Vous pouvez utiliser upper_bound pour incrémenter la position de l'itérateur au lieu de ++:

#include <map>
#include <string>
#include <iostream>

using namespace std;

int main()
{
  multimap<int,string> mm;
  mm.insert(make_pair(1, "a"));
  mm.insert(make_pair(1, "lemon"));
  mm.insert(make_pair(2, "peacock"));
  mm.insert(make_pair(3, "angel"));

  for( auto it = mm.begin(), end = mm.end();
       it != end;
       it = mm.upper_bound(it->first)
  )
    cout << it->first << ' ' << it->second << endl;
  return 0;
}

Ceci résulte en :

1 a
2 peacock
3 angel
44
Pablo

Utiliser upper_bound résulterait en une boucle facile à lire, mais chaque appel effectuera une recherche dans une arborescence binaire, ce qui donnera un O (n log n) au lieu de O(n) traversal. Si la différence d'efficacité compte, vous pouvez structurer votre parcours de la manière suivante:

typedef std::multimap<std::string, int> MapType;
MapType container;
for (MapType::iterator it = container.begin(); it != container.end(); ) {
  std::string key = it->first;

  doSomething(key);

  // Advance to next non-duplicate entry.
  do {
    ++it;
  } while (it != container.end() && key == it->first);
}
26
user3701170

Comme indiqué dans la réponse sélectionnée, l'utilisation répétée de multimap::upper_bound conduit à une traversée de la carte par O (n log n). L'utilisation de la fonction externe upper_bound vous donne O (n). Cependant, vous devez vous assurer de ne comparer que la clé de la carte:

std::multimap<int, std::string> myMap = ... ;
const auto compareFirst = [](const std::pair<const int, std::string>& lhs, const std::pair<const int, std::string>& rhs) {
    return lhs.first < rhs.first;
};

for(auto it = myMap.begin(); it != myMap.end(); it = std::upper_bound(it, myMap.end(), *it, compareFirst)) {
    // Do stuff...

}

L’approche sous-jacente est essentiellement identique à la solution de user3701170 - c’est-à-dire une recherche linéaire - mais nous avons mis l’incrément d’incrément dans l’instruction for proprement dite, et non le corps de la boucle. En plus de mettre l’incrément là où il "vit" habituellement, cela signifie également que toute instruction continue de la boucle se comportera comme prévu.

3
Stewart Becker

Exemple exécutable

Ceci est une légère amélioration par rapport à https://stackoverflow.com/a/24212648/895245 avec un test unitaire exécutable:

#include <cassert>
#include <map>
#include <vector>

int main() {

    // For testing.
    auto m = std::multimap<int, int>{
        {1, 2},
        {1, 3},
        {2, 4}
    };
    std::vector<int> out;

    // The algorithm.
    auto it = m.begin();
    auto end = m.end();
    while (it != end) {
        auto key = it->first;

        // Do what you want to do with the keys.
        out.Push_back(key);

        do {
            if (++it == end)
                break;
        } while (it->first == key);
    }

    // Assert it worked.
    assert(out == std::vector<int>({1, 2}));
}

si vous devez passer rapidement toutes les clés uniques, vous pouvez utiliser std :: map à la place;

typedef std::map< KeyType, std::list< ValueType > > MapKeyToMultiValue;

L'insertion serait plus difficile. Cependant, vous pouvez parcourir toutes les clés sans avoir à vous soucier des entrées en double. L'insertion ressemblerait à ceci:

void insert_m(MapKeyToMultiValue &map, const KeyType key, const ValueType value )
{
  auto it = map.find( key );
  if (it == map.end())
  {
     std::list<ValueType> empty;
     std::pair< MapKeyToMultiValue::iterator, bool > ret =
        map.insert( MapKeyToMultiValue::value_type( key, empty ) );
     it = ret.first;
  }

  it->second.Push_back( value );
}

ou vous pouvez faire cela très basé sur un modèle:

template<typename KeyType, typename ValueType, 
     typename MapType = std::map< KeyType, std::list< ValueType > > >
void insert_multi( MapType &map, const KeyType key, const ValueType value )
{

  auto it = map.find( key );
  if (it == map.end())
  {
     std::list<ValueType> empty;
     std::pair< typename MapType::iterator, bool > ret =
        map.insert( typename MapType::value_type( key, empty ) );
     it = ret.first;
  }

  it->second.Push_back( value );
}

Le programme de test complet se présente comme suit:

#include <map>
#include <list>
#include <string>
#include <stdio.h>

typedef std::string KeyType;  
typedef int ValueType;

typedef std::map< KeyType, std::list< ValueType > >  MapKeyToMultiValue;

void insert_m(MapKeyToMultiValue &map, const KeyType key, const ValueType value )
{
  auto it = map.find( key );
  if (it == map.end())
  {
     std::list<ValueType> empty;
     std::pair< MapKeyToMultiValue::iterator, bool > ret =
        map.insert( MapKeyToMultiValue::value_type( key, empty ) );
     it = ret.first;
  }

  it->second.Push_back( value );
}


template<typename KeyType, typename ValueType, 
   typename MapType = std::map< KeyType, std::list< ValueType > > >
void insert_multi( MapType &map, const KeyType key, const ValueType value )
{

  auto it = map.find( key );
  if (it == map.end())
  {
     std::list<ValueType> empty;
     std::pair< typename MapType::iterator, bool > ret =
        map.insert( typename MapType::value_type( key, empty ) );
     it = ret.first;
  }

  it->second.Push_back( value );
}


int main()
{
    MapKeyToMultiValue map;


    insert_m(map, std::string("aaa"), 1 );
    insert_m(map, std::string("aaa"), 2 );
    insert_m(map, std::string("bb"), 3 );
    insert_m(map, std::string("cc"), 4 );


    insert_multi(map, std::string("ddd"), 1 );
    insert_multi(map, std::string("ddd"), 2 );
    insert_multi(map, std::string("ee"), 3 );
    insert_multi(map, std::string("ff"), 4 );


    for(auto i = map.begin(); i != map.end(); ++i)
    {
      printf("%s\n", i->first.c_str() );
    }


    return 0;
}
1
MichaelMoser

Essayez equal_range:

http://fr.cppreference.com/w/cpp/container/multimap/equal_range

Cela doit être une correspondance exacte.

0
Red.Wave