C ++ haritasında anahtarları yineleme


122

Anahtarlar üzerinde yineleme yapmanın bir yolu var mı, C ++ haritasının çiftleri değil mi?


Değerlere bir yineleyici edinme fikri, onu STL algoritmalarında kullanmaktır, örneğin, iki haritanın anahtarlarının kesişimi. Boost'u içeren çözüm buna izin vermez, çünkü bir Boost yineleyicisi üretecektir. En kötü cevap en çok oyu alır!

Yanıtlar:


70

"Gerçek" yineleyicinin döndürdüğü değeri gerçekten gizlemeniz gerekiyorsa (örneğin, anahtar yineleyicinizi standart algoritmalarla kullanmak istediğiniz için, böylece çiftler yerine anahtarlar üzerinde çalışırlar), o zaman Boost'un transform_iterator .

[İpucu: Yeni bir sınıf için Boost belgelerine bakarken, önce en sondaki "örnekleri" okuyun. O halde, geri kalanının ne hakkında konuştuğunu anlamak için spor bir şansınız olur :-)]


2
Boost ile BOOST_FOREACH (const key_t key, the_map | boost :: adapters :: map_keys) {do something} boost.org/doc/libs/1_50_0/libs/range/doc/html/range/reference/… yazabilirsiniz
rodrigob

120

harita ilişkisel kapsayıcıdır. Dolayısıyla, yineleyici bir çift anahtardır, val. Yalnızca anahtarlara ihtiyacınız varsa, çiftin değer kısmını göz ardı edebilirsiniz.

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

DÜZENLEME:: Yalnızca anahtarları dışarıda göstermek istiyorsanız, haritayı vektöre veya anahtarlara dönüştürebilir ve açığa çıkarabilirsiniz.


Ama o zaman vektörün yineleyicisini dışarıda açığa çıkarmak gerçekten kötü bir fikir olacaktır.
Naveen

Yineleyiciyi açığa çıkarmayın. Sadece vektördeki anahtarları sağlayın
aJ.

5
Bunun yerine bunu yapmak isteyebilirsiniz: const Key& k(iter->first);
strickli

17
İki şey, bu o zaten biliyordu ve eğer böyle bir şey yapmak istiyorum ikinci olarak bu yöntem size yardımcı olmayacaktır aramıyordu tam olarak cevap OP'ın soruya cevap verir: std::vector<Key> v(myMap.begin(), myMap.end()).
Andreas Magnusson

Anahtarları bir vektöre dönüştürmeyin. Yeni bir vektör yapmak, hızlı olması ve hiçbir şey ayırmaması gereken yinelemenin amacını bozar. Ayrıca, büyük setler için yavaş olacaktır.
Kevin Chen

85

C ++ 11 ile yineleme sözdizimi basittir. Hala çiftler üzerinde yinelersiniz, ancak yalnızca anahtara erişmek kolaydır.

#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";
    }
}

29
Orijinal soru açıkça "çiftler değil" diyor.
Ian

41

Boost olmadan

Bunu, o harita için STL yineleyicisini genişleterek yapabilirsiniz. Örneğin, dizelerin tamsalara eşlenmesi:

#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; }
};

Daha genel bir çözüm için bu uzantıyı bir şablonda da gerçekleştirebilirsiniz .

Yineleyicinizi tam olarak bir liste yineleyici gibi kullanırsınız, tek fark haritanın begin()ve üzerinde yinelemeniz dışında 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());

16
+1: Son olarak, "çiftleri değil" bitini okuyan biri! Şerefe, bu teknik özellikleri araştırırken zaman kazandırdı!
Mark K Cowan

1
Ve şablonlu çözümün altına, Değer yineleyicisini ekledim.
degski

sorunuzu benimkinden bağladı.
Ian

template<typename C> class key_iterator : public C::iterator, vb
Gabriel

38

C ++ 17 ile , aralık tabanlı bir for döngüsü içinde yapılandırılmış bir bağlama kullanabilirsiniz ( John H.'nin yanıtını buna göre uyarlayarak ):

#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';
    }
}

Maalesef, C ++ 17 standardı, valuekullanmıyor olsanız bile değişkeni bildirmenizi gerektirir ( std::ignorebir tanesi std::tie(..)işe yaramaz, bu tartışmaya bakın ).

Bazı derleyiciler bu nedenle kullanılmayan valuedeğişken hakkında sizi uyarabilir ! Kullanılmayan değişkenlerle ilgili derleme zamanı uyarıları, aklımdaki herhangi bir üretim kodu için uygun değildir. Bu nedenle, bu belirli derleyici sürümleri için geçerli olmayabilir.


onu std :: ignore'a atayamaz mısın? Bu, derlenen kodda gerçekten verimliliğe zarar verir mi, yoksa hiçbir şey olarak değerlendirmez mi? (
Bağlamadan

C ++ 17'den beri [[might_unused]] da kullanabilirsiniz. Bu, uyarıyı bastırır. for ([[maybe_unused]] const auto &[key, v_not_used] : my_map) { use(key); }
Şunun

15

Ian'ın bahsettiği daha genel şablonlu çözümün altında ...

#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; }
};

Tüm krediler Ian'a gidiyor ... Teşekkürler Ian.


11

Map_keys arıyorsunuz, bununla aşağıdaki gibi şeyler yazabilirsiniz

BOOST_FOREACH(const key_t key, the_map | boost::adaptors::map_keys)
{
  // do something with key
}

1
BOOST_FOREACH(const key_t& key, ...
strickli

5

İşte Boost'un transform_iterator kullanarak nasıl yapılacağına dair bir örnek

#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;
  }
}

4

Açık beginve endgerekli olmadığında, yani aralık döngüsü için, anahtarlar üzerinden döngü (ilk örnek) veya değerler (ikinci örnek) ile elde edilebilir

#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;

1
standart olmalıdır
Mordachai

3

Bunu yapmak ister misin?

std::map<type,type>::iterator iter = myMap.begin();
std::map<type,type>::iterator iter = myMap.end();
for(; iter != endIter; ++iter)
{
   type key = iter->first;  
   .....
}

Evet, biliyorum, sorun benim bir sınıf A'ya sahibim {public: // burada özel haritanın anahtarları üzerinden bir yineleyiciyi açığa çıkarmak istiyorum private: map <>};
Bogdan Balan

Bu durumda std :: trasnform kullanarak ve haritadan sadece anahtarları alarak bir std :: listesi oluşturabileceğinizi düşünüyorum. Listeye daha fazla eleman eklemek mevcut yineleyicileri geçersiz kılmayacağından, daha sonra liste yineleyiciyi açığa çıkarabilirsiniz.
Naveen

3

Yalnızca anahtarları döndüren bir yineleyiciye ihtiyacınız varsa, haritanın yineleyicisini istenen arabirimi sağlayan kendi sınıfınızda sarmalamanız gerekir. Mevcut yardımcı yapıları kullanarak, burada olduğu gibi sıfırdan yeni bir yineleyici sınıfı bildirebilirsiniz . Bu cevap , transform_iteratoryineleyiciyi yalnızca değerleri / anahtarları döndüren bir tanesine sarmak için Boost'un nasıl kullanılacağını gösterir .


2

Yapabilirdiniz

  • özel bir yineleyici sınıfı oluşturun, std::map<K,V>::iterator
  • Kullanım std::transformçıkartmalarınızın map.begin()To map.end() a ile boost::bind( &pair::second, _1 )funktor
  • sadece ->secondbir fordöngü ile yineleme yaparken üyeyi yok sayın .

2

Bu yanıt BOOST_FOREACH,. Bunun yerine c ++ 'ın aralığı tabanlı kullanabilirsiniz.

#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;
     }
}

0

Boost olmadan bunu bu şekilde yapabilirsiniz. GetKeyIterator () yerine bir cast operatörü yazabilseydiniz güzel olurdu, ama onu derleyemiyorum.

#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());
    }
}

0

Gelecek nesil için ve bir aralık oluşturmanın bir yolunu bulmaya çalıştığım için, bir alternatif boost :: adapters :: transform kullanmaktır

İşte küçük bir örnek:

#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;
}

Değerler üzerinde yineleme yapmak istiyorsanız, lambda'da kullanın t.second.


0

Burada birçok iyi cevap var, aşağıda bunları yazmanıza izin veren birkaçını kullanan bir yaklaşım var:

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

Her zaman istediğin buysa, işte MapKeys () için kod:

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);
}

0

Ian'ın tüm harita türleriyle çalışmak için yanıtını benimsedim ve bir referans döndürmeyi düzelttim 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;
    }
};

-1

Bunun sorunuzu yanıtlamadığını biliyorum, ancak bakmak isteyebileceğiniz bir seçenek, aynı indeksi "bağlantılı" bilgi olan iki vektöre sahip olmaktır ..

Yani ...

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

std::vector<int> vNameCount;

adların isme göre sayılmasını istiyorsanız, sadece for döngüsünü vName.size () üzerinden yaparsınız ve bulduğunuzda, aradığınız vNameCount dizini budur.

Elbette bu size haritanın tüm işlevlerini vermeyebilir ve bağlı olarak daha iyi olabilir veya olmayabilir, ancak anahtarları bilmiyorsanız daha kolay olabilir ve çok fazla işlem yapmamalısınız.

Sadece birinden ne zaman ekleyip sildiğinizi hatırlayın, bunu diğerinden yapmanız gerekir, yoksa işler çıldırır heh: P

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.