Iterasi kunci di peta C ++

122

Apakah ada cara untuk mengulang kunci, bukan pasangan peta C ++?

Bogdan Balan
sumber
Ide untuk mendapatkan iterator ke nilai adalah dengan menggunakannya dalam algoritma STL, misalnya, persimpangan kunci dari dua peta. Solusi yang melibatkan Boost tidak memungkinkan ini, karena itu akan menghasilkan iterator Boost. Jawaban terburuk mendapat suara terbanyak!

Jawaban:

70

Jika Anda benar-benar perlu menyembunyikan nilai yang dikembalikan iterator "asli" (misalnya karena Anda ingin menggunakan iterator kunci dengan algoritme standar, sehingga mereka beroperasi pada kunci, bukan berpasangan), lihat Boost's transform_iterator .

[Tip: saat melihat dokumentasi Boost untuk kelas baru, baca "contoh" di bagian akhir terlebih dahulu. Anda kemudian memiliki kesempatan olahraga untuk mencari tahu apa yang sedang dibicarakan oleh semua itu :-)]

Steve Jessop
sumber
2
Dengan dorongan Anda dapat menulis BOOST_FOREACH (const key_t kunci, the_map | meningkatkan :: adapter :: map_keys) {melakukan sesuatu} boost.org/doc/libs/1_50_0/libs/range/doc/html/range/reference/...
rodrigob
120

map adalah wadah asosiatif. Oleh karena itu, iterator adalah sepasang kunci, val. JIKA Anda hanya membutuhkan kunci, Anda dapat mengabaikan bagian nilai dari pasangan.

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

EDIT:: Jika Anda hanya ingin mengekspos kunci ke luar maka Anda dapat mengonversi peta ke vektor atau kunci dan mengekspos.

aJ.
sumber
Tapi kemudian akan sangat buruk untuk mengekspos iterator vektor di luar.
Naveen
Jangan buka iterator. Berikan saja kunci dalam vektor
aJ.
5
Anda mungkin ingin melakukan ini sebagai gantinya: const Key& k(iter->first);
strickli
17
Dua hal, ini menjawab pertanyaan OP dengan tepat jawaban yang ia sudah tahu dan tidak mencari, kedua metode ini tidak akan membantu Anda jika Anda ingin melakukan sesuatu seperti: std::vector<Key> v(myMap.begin(), myMap.end()).
Andreas Magnusson
Jangan mengonversi kunci menjadi vektor. Membuat vektor baru mengalahkan tujuan iterasi, yang seharusnya cepat dan tidak mengalokasikan apa pun. Juga, akan lambat untuk set besar.
Kevin Chen
85

Dengan C ++ 11 sintaks iterasinya sederhana. Anda masih mengulang berpasangan, tetapi mengakses kuncinya saja itu mudah.

#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.
sumber
29
Pertanyaan awal secara eksplisit mengatakan "bukan pasangan".
Ian
41

Tanpa Boost

Anda dapat melakukan ini hanya dengan memperluas iterator STL untuk peta itu. Misalnya, pemetaan string ke int:

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

Anda juga dapat melakukan ekstensi ini di template , untuk solusi yang lebih umum.

Anda menggunakan iterator Anda persis seperti Anda akan menggunakan iterator daftar, kecuali Anda melakukan iterasi pada peta begin()dan 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
sumber
16
+1: Akhirnya, seseorang yang membaca sedikit "bukan pasangan"! Cheers, ini telah menghemat waktu saya untuk menggali spesifikasi!
Mark K Cowan
1
Dan di bawah solusi template, dan saya menambahkan iterator Nilai.
degski
menautkan pertanyaan Anda dari saya.
Ian
template<typename C> class key_iterator : public C::iterator, dll
Gabriel
38

Dengan C ++ 17 Anda dapat menggunakan pengikatan terstruktur di dalam loop for berbasis rentang (menyesuaikan jawaban John H. ):

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

Sayangnya, standar C ++ 17 mengharuskan Anda untuk mendeklarasikan valuevariabel, meskipun Anda tidak menggunakannya ( std::ignorekarena yang akan digunakan untuk std::tie(..)tidak berfungsi, lihat diskusi ini ).

Oleh karena itu, beberapa kompiler mungkin memperingatkan Anda tentang valuevariabel yang tidak digunakan ! Peringatan waktu kompilasi mengenai variabel yang tidak digunakan adalah larangan untuk kode produksi apa pun dalam pikiran saya. Jadi, ini mungkin tidak berlaku untuk versi kompilator tertentu.

Elmar
sumber
tidak bisakah Anda menetapkannya ke std :: ignore pada prinsipnya? Apakah itu benar-benar merusak efisiensi dalam kode yang dikompilasi atau apakah itu benar-benar tidak menghasilkan apa-apa? (Saya tidak bermaksud dalam mengikat melainkan sebagai tindakan dalam lingkaran)
KotoroShinoto
Sejak C ++ 17 Anda juga dapat menggunakan [[maybe_unused]]. Ini menekan peringatan. Seperti ini:for ([[maybe_unused]] const auto &[key, v_not_used] : my_map) { use(key); }
arhuaco
15

Di bawah solusi template yang lebih umum yang dirujuk Ian ...

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

Semua kredit diberikan kepada Ian ... Terima kasih Ian.

degski
sumber
11

Anda mencari map_keys , dengan itu Anda dapat menulis sesuatu seperti

BOOST_FOREACH(const key_t key, the_map | boost::adaptors::map_keys)
{
  // do something with key
}
rodrigob
sumber
1
BOOST_FOREACH(const key_t& key, ...
strickli
5

Berikut adalah contoh cara melakukannya menggunakan Boost's transform_iterator

#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;
  }
}
alga
sumber
4

Ketika tidak ada eksplisit begindan enddiperlukan, yaitu untuk perulangan-rentang, perulangan atas kunci (contoh pertama) atau nilai (contoh kedua) dapat diperoleh dengan

#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
sumber
1
harus di std
Mordachai
3

Anda ingin melakukan ini?

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
sumber
Ya, saya tahu, masalahnya adalah saya memiliki kelas A {publik: // saya ingin mengekspos iterator atas kunci peta pribadi di sini private: map <>};
Bogdan Balan
Dalam hal ini, saya rasa Anda dapat membuat std :: list dengan menggunakan std :: trasnform dan hanya mengambil kunci dari peta. Kemudian Anda dapat mengekspos iterator daftar karena memasukkan lebih banyak elemen ke daftar tidak akan membatalkan iterator yang ada.
Naveen
3

Jika Anda memerlukan iterator yang hanya mengembalikan kunci, Anda perlu menggabungkan iterator peta di kelas Anda sendiri yang menyediakan antarmuka yang diinginkan. Anda dapat mendeklarasikan kelas iterator baru dari awal seperti di sini , menggunakan konstruksi helper yang sudah ada. Jawaban ini menunjukkan cara menggunakan Boost transform_iteratoruntuk menggabungkan iterator yang hanya mengembalikan nilai / kunci.

sth
sumber
2

Anda bisa

  • buat kelas iterator khusus, yang menggabungkan std::map<K,V>::iterator
  • penggunaan std::transformdari Anda map.begin()untuk map.end() dengan boost::bind( &pair::second, _1 )functor
  • abaikan saja ->secondanggota tersebut saat melakukan iterasi dengan forloop.
xtofl
sumber
2

Jawaban ini seperti jawaban rodrigob kecuali tanpa BOOST_FOREACH. Anda dapat menggunakan c ++ sebagai gantinya.

#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;
     }
}
pengguna4608041
sumber
0

Tanpa Boost, Anda dapat melakukannya seperti ini. Alangkah baiknya jika Anda bisa menulis operator cast daripada getKeyIterator (), tetapi saya tidak bisa membuatnya untuk dikompilasi.

#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
sumber
0

Untuk anak cucu, dan karena saya mencoba menemukan cara untuk membuat rentang, alternatifnya adalah menggunakan boost :: adapters :: transform

Berikut contoh kecilnya:

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

Jika Anda ingin mengulang nilai, gunakan t.seconddi lambda.

ipapadop
sumber
0

Banyak jawaban bagus di sini, di bawah ini adalah pendekatan menggunakan beberapa di antaranya yang memungkinkan Anda menulis ini:

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

Jika itu yang selalu Anda inginkan, berikut adalah kode untuk 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
sumber
0

Saya telah mengadopsi jawaban Ian untuk bekerja dengan semua tipe peta dan tetap mengembalikan referensi untuk 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
sumber
-1

Saya tahu ini tidak menjawab pertanyaan Anda, tetapi satu opsi yang mungkin ingin Anda lihat hanyalah memiliki dua vektor dengan indeks yang sama menjadi informasi "ditautkan" ..

Jadi di ..

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

std::vector<int> vNameCount;

jika Anda ingin menghitung nama berdasarkan nama Anda cukup melakukan pengulangan cepat untuk vName.size (), dan ketika Anda menemukannya, itulah indeks untuk vNameCount yang Anda cari.

Tentu ini mungkin tidak memberi Anda semua fungsionalitas peta, dan tergantung mungkin atau mungkin tidak lebih baik, tetapi mungkin lebih mudah jika Anda tidak tahu kuncinya, dan tidak perlu menambahkan terlalu banyak pemrosesan.

Ingatlah ketika Anda menambah / menghapus dari satu Anda harus melakukannya dari yang lain atau hal-hal akan menjadi gila heh: P

kebiruan
sumber