Iterieren Sie Schlüssel in einer C ++ - Zuordnung

121

Gibt es eine Möglichkeit, über die Schlüssel zu iterieren, nicht über die Paare einer C ++ - Zuordnung?

Bogdan Balan
quelle
Die Idee, einen Iterator zu den Werten zu bringen, besteht darin, ihn in STL-Algorithmen zu verwenden, beispielsweise als Schnittpunkt von Schlüsseln zweier Karten. Die Lösung mit Boost erlaubt dies nicht, da ein Boost-Iterator erzeugt wird. Die schlechteste Antwort bekommt die meisten Stimmen!

Antworten:

70

Wenn Sie den Wert, den der "echte" Iterator zurückgibt, wirklich ausblenden müssen (z. B. weil Sie Ihren Schlüsseliterator mit Standardalgorithmen verwenden möchten, damit diese auf den Schlüsseln anstelle der Paare arbeiten), schauen Sie sich Boosts an transform_iterator .

[Tipp: Wenn Sie sich die Boost-Dokumentation für eine neue Klasse ansehen, lesen Sie zuerst die "Beispiele" am Ende. Sie haben dann eine sportliche Chance herauszufinden, wovon um alles in der Welt der Rest spricht :-)]

Steve Jessop
quelle
2
Mit Boost können Sie schreiben BOOST_FOREACH (const key_t Schlüssel, the_map | boost :: Adapter :: map_keys) {etwas tun} boost.org/doc/libs/1_50_0/libs/range/doc/html/range/reference/...
rodrigob
120

Karte ist assoziativer Container. Daher ist der Iterator ein Schlüsselpaar, val. Wenn Sie nur Schlüssel benötigen, können Sie den Werteteil des Paares ignorieren.

for(std::map<Key,Val>::iterator iter = myMap.begin(); iter != myMap.end(); ++iter)
{
Key k =  iter->first;
//ignore value
//Value v = iter->second;
}

BEARBEITEN :: Wenn Sie nur die Schlüssel nach außen verfügbar machen möchten, können Sie die Karte in einen Vektor oder Schlüssel konvertieren und verfügbar machen.

aJ.
quelle
Aber dann ist es wirklich eine schlechte Idee, den Iterator des Vektors draußen verfügbar zu machen.
Naveen
Setzen Sie den Iterator nicht frei.
Geben Sie
5
Vielleicht möchten Sie dies stattdessen tun: const Key& k(iter->first);
Strickli
17
Zwei Dinge, dies beantwortet die Frage des OP mit genau der Antwort, die er bereits kannte und nicht suchte. Zweitens hilft Ihnen diese Methode nicht, wenn Sie etwas tun möchten wie : std::vector<Key> v(myMap.begin(), myMap.end()).
Andreas Magnusson
Konvertieren Sie die Schlüssel nicht in einen Vektor. Das Erstellen eines neuen Vektors macht den Zweck der Iteration zunichte, die schnell sein und nichts zuweisen soll. Außerdem ist es bei großen Sets langsam.
Kevin Chen
84

Mit C ++ 11 ist die Iterationssyntax einfach. Sie iterieren immer noch über Paare, aber der Zugriff auf nur den Schlüssel ist einfach.

#include <iostream>
#include <map>

int main()
{
    std::map<std::string, int> myMap;

    myMap["one"] = 1;
    myMap["two"] = 2;
    myMap["three"] = 3;

    for ( const auto &myPair : myMap ) {
        std::cout << myPair.first << "\n";
    }
}
John H.
quelle
29
Die ursprüngliche Frage lautet ausdrücklich "nicht die Paare".
Ian
41

Ohne Boost

Sie können dies tun, indem Sie einfach den STL-Iterator für diese Karte erweitern. Zum Beispiel eine Zuordnung von Zeichenfolgen zu Ints:

#include <map>
typedef map<string, int> ScoreMap;
typedef ScoreMap::iterator ScoreMapIterator;

class key_iterator : public ScoreMapIterator
{
  public:
    key_iterator() : ScoreMapIterator() {};
    key_iterator(ScoreMapIterator s) : ScoreMapIterator(s) {};
    string* operator->() { return (string* const)&(ScoreMapIterator::operator->()->first); }
    string operator*() { return ScoreMapIterator::operator*().first; }
};

Sie können diese Erweiterung auch in einer Vorlage ausführen , um eine allgemeinere Lösung zu erhalten.

Sie verwenden Ihren Iterator genau so, wie Sie einen Listeniterator verwenden würden, außer dass Sie über die Karten begin()und iterieren end().

ScoreMap m;
m["jim"] = 1000;
m["sally"] = 2000;

for (key_iterator s = m.begin(); s != m.end(); ++s)
    printf("\n key %s", s->c_str());
Ian
quelle
16
+1: Endlich jemand, der das Bit "Nicht die Paare" gelesen hat! Prost, das hat mir Zeit gespart, die Spezifikation durchzuarbeiten!
Mark K Cowan
1
Und unterhalb der Vorlagenlösung habe ich den Wertiterator hinzugefügt.
Degski
habe deine Frage von mir verlinkt.
Ian
template<typename C> class key_iterator : public C::iterator, etc
Gabriel
38

Mit C ++ 17 können Sie eine strukturierte Bindung innerhalb einer bereichsbasierten for-Schleife verwenden (indem Sie die Antwort von John H. entsprechend anpassen ):

#include <iostream>
#include <map>

int main() {
    std::map<std::string, int> myMap;

    myMap["one"] = 1;
    myMap["two"] = 2;
    myMap["three"] = 3;

    for ( const auto &[key, value]: myMap ) {
        std::cout << key << '\n';
    }
}

Leider verlangt der C ++ 17-Standard, dass Sie die valueVariable deklarieren , obwohl Sie sie nicht verwenden ( std::ignorewie man es verwenden würde, std::tie(..)funktioniert nicht, siehe diese Diskussion ).

Einige Compiler warnen Sie daher möglicherweise vor der nicht verwendeten valueVariablen! Warnungen zur Kompilierungszeit in Bezug auf nicht verwendete Variablen sind für mich kein Problem für Produktionscode. Dies gilt möglicherweise nicht für bestimmte Compilerversionen.

Elmar
quelle
Könntest du es nicht grundsätzlich std :: ignore zuweisen? Würde dies tatsächlich die Effizienz des kompilierten Codes beeinträchtigen oder würde es tatsächlich zu nichts führen? (Ich meine nicht in der Bindung, sondern als Aktion innerhalb der Schleife)
KotoroShinoto
Seit C ++ 17 können Sie auch [[vielleicht_unused]] verwenden. Dies unterdrückt die Warnung. So:for ([[maybe_unused]] const auto &[key, v_not_used] : my_map) { use(key); }
Arhuaco
15

Unten die allgemeinere Vorlagenlösung, auf die sich Ian bezog ...

#include <map>

template<typename Key, typename Value>
using Map = std::map<Key, Value>;

template<typename Key, typename Value>
using MapIterator = typename Map<Key, Value>::iterator;

template<typename Key, typename Value>
class MapKeyIterator : public MapIterator<Key, Value> {

public:

    MapKeyIterator ( ) : MapIterator<Key, Value> ( ) { };
    MapKeyIterator ( MapIterator<Key, Value> it_ ) : MapIterator<Key, Value> ( it_ ) { };

    Key *operator -> ( ) { return ( Key * const ) &( MapIterator<Key, Value>::operator -> ( )->first ); }
    Key operator * ( ) { return MapIterator<Key, Value>::operator * ( ).first; }
};

template<typename Key, typename Value>
class MapValueIterator : public MapIterator<Key, Value> {

public:

    MapValueIterator ( ) : MapIterator<Key, Value> ( ) { };
    MapValueIterator ( MapIterator<Key, Value> it_ ) : MapIterator<Key, Value> ( it_ ) { };

    Value *operator -> ( ) { return ( Value * const ) &( MapIterator<Key, Value>::operator -> ( )->second ); }
    Value operator * ( ) { return MapIterator<Key, Value>::operator * ( ).second; }
};

Alle Credits gehen an Ian ... Danke Ian.

Degski
quelle
11

Sie suchen nach map_keys , mit denen Sie Dinge wie schreiben können

BOOST_FOREACH(const key_t key, the_map | boost::adaptors::map_keys)
{
  // do something with key
}
Rodrigob
quelle
1
BOOST_FOREACH(const key_t& key, ...
Strickli
5

Hier ist ein Beispiel, wie dies mit dem transform_iterator von Boost gemacht wird

#include <iostream>
#include <map>
#include <iterator>
#include "boost/iterator/transform_iterator.hpp"

using std::map;
typedef std::string Key;
typedef std::string Val;

map<Key,Val>::key_type get_key(map<Key,Val>::value_type aPair) {
  return aPair.first;
}

typedef map<Key,Val>::key_type (*get_key_t)(map<Key,Val>::value_type);
typedef map<Key,Val>::iterator map_iterator;
typedef boost::transform_iterator<get_key_t, map_iterator> mapkey_iterator;

int main() {
  map<Key,Val> m;
  m["a"]="A";
  m["b"]="B";
  m["c"]="C";

  // iterate over the map's (key,val) pairs as usual
  for(map_iterator i = m.begin(); i != m.end(); i++) {
    std::cout << i->first << " " << i->second << std::endl;
  }

  // iterate over the keys using the transformed iterators
  mapkey_iterator keybegin(m.begin(), get_key);
  mapkey_iterator keyend(m.end(), get_key);
  for(mapkey_iterator i = keybegin; i != keyend; i++) {
    std::cout << *i << std::endl;
  }
}
Algen
quelle
4

Wenn dies nicht explizit beginund enderforderlich ist, dh für eine Bereichsschleife, kann die Schleife über Schlüssel (erstes Beispiel) oder Werte (zweites Beispiel) mit erhalten werden

#include <boost/range/adaptors.hpp>

map<Key, Value> m;

for (auto k : boost::adaptors::keys(m))
  cout << k << endl;

for (auto v : boost::adaptors::values(m))
  cout << v << endl;
Darko Veberic
quelle
1
sollte im std sein
Mordachai
3

Du willst das machen?

std::map<type,type>::iterator iter = myMap.begin();
std::map<type,type>::iterator iter = myMap.end();
for(; iter != endIter; ++iter)
{
   type key = iter->first;  
   .....
}
Naveen
quelle
Ja, ich weiß, das Problem ist, dass ich eine Klasse A habe {public: // Ich möchte hier einen Iterator über Schlüssel einer privaten Karte verfügbar machen. Private: map <>};
Bogdan Balan
In diesem Fall können Sie eine std :: -Liste erstellen, indem Sie std :: trasnform verwenden und nur die Schlüssel von der Karte abrufen. Anschließend können Sie den Listeniterator verfügbar machen, da durch das Einfügen weiterer Elemente in die Liste die vorhandenen Iteratoren nicht ungültig werden.
Naveen
3

Wenn Sie einen Iterator benötigen, der nur die Schlüssel zurückgibt, müssen Sie den Iterator der Karte in Ihre eigene Klasse einbinden, die die gewünschte Schnittstelle bereitstellt. Sie können wie hier eine neue Iteratorklasse von Grund auf deklarieren , um vorhandene Hilfskonstrukte zu verwenden. Diese Antwort zeigt, wie Boosts verwendet werden transform_iterator, um den Iterator in einen Iterator zu verpacken, der nur die Werte / Schlüssel zurückgibt.

etw
quelle
2

Du könntest

  • Erstellen Sie eine benutzerdefinierte Iteratorklasse und aggregieren Sie die std::map<K,V>::iterator
  • Verwendung std::transformIhres map.begin()zu map.end() mit einem boost::bind( &pair::second, _1 )Funktor
  • Ignorieren Sie einfach das ->secondMitglied, während Sie mit einer forSchleife iterieren .
xtofl
quelle
2

Diese Antwort ist wie die von Rodrigob, außer ohne die BOOST_FOREACH. Sie können stattdessen den Bereich von c ++ verwenden, der auf basiert.

#include <map>
#include <boost/range/adaptor/map.hpp>
#include <iostream>

template <typename K, typename V>
void printKeys(std::map<K,V> map){
     for(auto key : map | boost::adaptors::map_keys){
          std::cout << key << std::endl;
     }
}
user4608041
quelle
0

Ohne Boost könnten Sie es so machen. Es wäre schön, wenn Sie anstelle von getKeyIterator () einen Cast-Operator schreiben könnten, aber ich kann ihn nicht zum Kompilieren bringen.

#include <map>
#include <unordered_map>


template<typename K, typename V>
class key_iterator: public std::unordered_map<K,V>::iterator {

public:

    const K &operator*() const {
        return std::unordered_map<K,V>::iterator::operator*().first;
    }

    const K *operator->() const {
        return &(**this);
    }
};

template<typename K,typename V>
key_iterator<K,V> getKeyIterator(typename std::unordered_map<K,V>::iterator &it) {
    return *static_cast<key_iterator<K,V> *>(&it);
}

int _tmain(int argc, _TCHAR* argv[])
{
    std::unordered_map<std::string, std::string> myMap;
    myMap["one"]="A";
    myMap["two"]="B";
    myMap["three"]="C";
    key_iterator<std::string, std::string> &it=getKeyIterator<std::string,std::string>(myMap.begin());
    for (; it!=myMap.end(); ++it) {
        printf("%s\n",it->c_str());
    }
}
Jack Haughton
quelle
0

Für die Nachwelt und da ich versucht habe, einen Weg zum Erstellen eines Bereichs zu finden, besteht eine Alternative darin, boost :: adapters :: transform zu verwenden

Hier ist ein kleines Beispiel:

#include <boost/range/adaptor/transformed.hpp>
#include <iostream>
#include <map>

int main(int argc, const char* argv[])
{
  std::map<int, int> m;
  m[0]  = 1;
  m[2]  = 3;
  m[42] = 0;

  auto key_range =
    boost::adaptors::transform(
      m,
      [](std::map<int, int>::value_type const& t) 
      { return t.first; }
    ); 
  for (auto&& key : key_range)
    std::cout << key << ' ';
  std::cout << '\n';
  return 0;
}

Wenn Sie die Werte durchlaufen möchten, verwenden Sie sie t.secondim Lambda.

ipapadop
quelle
0

Hier finden Sie viele gute Antworten. Im Folgenden finden Sie einige Ansätze, mit denen Sie Folgendes schreiben können:

void main()
{
    std::map<std::string, int> m { {"jim", 1000}, {"sally", 2000} };
    for (auto key : MapKeys(m))
        std::cout << key << std::endl;
}

Wenn Sie das schon immer wollten, dann ist hier der Code für MapKeys ():

template <class MapType>
class MapKeyIterator {
public:
    class iterator {
    public:
        iterator(typename MapType::iterator it) : it(it) {}
        iterator operator++() { return ++it; }
        bool operator!=(const iterator & other) { return it != other.it; }
        typename MapType::key_type operator*() const { return it->first; }  // Return key part of map
    private:
        typename MapType::iterator it;
    };
private:
    MapType& map;
public:
    MapKeyIterator(MapType& m) : map(m) {}
    iterator begin() { return iterator(map.begin()); }
    iterator end() { return iterator(map.end()); }
};
template <class MapType>
MapKeyIterator<MapType> MapKeys(MapType& m)
{
    return MapKeyIterator<MapType>(m);
}
Superfly Jon
quelle
0

Ich habe Ians Antwort übernommen, um mit allen Kartentypen zu arbeiten, und die Rückgabe einer Referenz für behoben operator*

template<typename T>
class MapKeyIterator : public T
{
public:
    MapKeyIterator() : T() {}
    MapKeyIterator(T iter) : T(iter) {}
    auto* operator->()
    {
        return &(T::operator->()->first);
    }
    auto& operator*()
    {
        return T::operator*().first;
    }
};
Gabriel Huber
quelle
-1

Ich weiß, dass dies Ihre Frage nicht beantwortet, aber eine Option, die Sie möglicherweise prüfen möchten, besteht darin, nur zwei Vektoren mit demselben Index als "verknüpfte" Informationen zu verwenden.

Also in ..

std::vector<std::string> vName;

std::vector<int> vNameCount;

Wenn Sie die Anzahl der Namen nach Namen festlegen möchten, führen Sie einfach eine schnelle for-Schleife über vName.size () durch. Wenn Sie diese finden, ist dies der Index für vNameCount, den Sie suchen.

Sicher, dies gibt Ihnen möglicherweise nicht die gesamte Funktionalität der Karte, und je nachdem kann es besser sein oder auch nicht, aber es ist möglicherweise einfacher, wenn Sie die Schlüssel nicht kennen und nicht zu viel Verarbeitung hinzufügen sollten.

Denken Sie daran, wenn Sie von einem hinzufügen / löschen, müssen Sie es von dem anderen tun, sonst werden die Dinge verrückt heh: P.

bläulich
quelle