Ітерація ключів на карті C ++


121

Чи є спосіб перебрати ключі, а не пари карти C ++?


Ідея отримання ітератора значень полягає у використанні його в алгоритмах STL, наприклад, перетині ключів двох карт. Рішення, що включає Boost, не дозволяє цього, оскільки воно створить ітератор Boost. Найгірша відповідь отримує найбільше голосів!

Відповіді:


70

Якщо вам дійсно потрібно приховати значення, яке повертає "справжній" ітератор (наприклад, тому, що ви хочете використовувати свій ітератор ключів зі стандартними алгоритмами, щоб вони працювали над клавішами замість пар), тоді подивіться на Boost's transform_iterator .

[Порада: переглядаючи документацію Boost для нового класу, спочатку прочитайте "приклади". Тоді ви маєте спортивний шанс зрозуміти, про що йдеться на землі :-)]


2
Зі збільшенням ви можете написати BOOST_FOREACH (ключ const key_t, the_map | boost :: адаптери :: map_keys) {do something} boost.org/doc/libs/1_50_0/libs/range/doc/html/range/reference/…
rodrigob

120

map - асоціативний контейнер. Отже, ітератор - це пара ключа, val. Якщо вам потрібні лише ключі, ви можете ігнорувати частину значень з пари.

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

РЕДАКТУВАННЯ: Якщо ви хочете виставити назовні лише ключі, ви можете перетворити карту у векторні або клавіші та експонувати.


Але тоді буде дійсно поганою ідеєю виставити ітератор вектора назовні.
Naveen

Не піддавайте ітератор. Просто введіть ключі у векторному
AJ.

5
Ви можете зробити це замість цього: const Key& k(iter->first);
strickli

17
Дві речі, це відповідає на питання OP з точно відповіддю , який він уже знав і не шукав, по- друге , цей метод не допоможе вам , якщо ви хочете зробити що - щось на кшталт: std::vector<Key> v(myMap.begin(), myMap.end()).
Андреас Магнуссон

Не перетворюйте ключі у вектор. Створення нового вектора перемагає мету ітерації, яка повинна бути швидкою і нічого не виділяти. Крім того, це буде повільно для великих наборів.
Кевін Чен

85

З C ++ 11 синтаксис ітерації простий. Ви все ще перебираєте пари, але отримати доступ до ключа просто.

#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
Оригінальне запитання прямо говорить «не пари».
Ян

41

Без підвищення

Це можна зробити, просто продовживши ітератор STL для цієї карти. Наприклад, відображення рядків у 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; }
};

Ви також можете виконати це розширення в шаблоні для більш загального рішення.

Ви використовуєте свій ітератор так, як ви використовували б ітератор списку, за винятком того, що ви повторюєте карти begin()та 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: Нарешті, хтось, хто читав біт "не пари"! Будьте здорові, це заощадило мені час копатися по специфікації!
Марк К Коуан

1
І нижче шаблонного рішення, і я додав ітератор значення.
degski

пов’язали ваше питання з моїм.
Ян

template<typename C> class key_iterator : public C::iteratorтощо
Габріель

38

З C ++ 17 ви можете використовувати структуровану прив'язку всередині діапазону на основі циклу (відповідно адаптувавши відповідь Джона Х. ):

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

На жаль, стандарт C ++ 17 вимагає, щоб ви оголосили valueзмінну, навіть якщо ви її не використовуєте ( std::ignoreяк це можна було б використовувати std::tie(..), дивіться це обговорення ).

Тому деякі компілятори можуть попередити вас про невикористану valueзмінну! Попередження під час компіляції щодо невикористаних змінних на мою думку не підлягають жодному виробничому коду. Отже, це може не застосовуватися для певних версій компілятора.


ви не могли призначити його std :: ignore в принципі? Чи насправді це зашкодить ефективності компільованого коду чи він насправді оцінить ні до чого? (Я не маю на увазі прив'язку, а скоріше як дію в циклі)
KotoroShinoto

Оскільки C ++ 17, ви також можете використовувати [[можливо_навчаний]]. Це пригнічує попередження. for ([[maybe_unused]] const auto &[key, v_not_used] : my_map) { use(key); }
Ось так

15

Нижче більш загальне шаблонне рішення, на яке Іан посилався ...

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

Усі кредити йдуть Іану ... Дякую Іану.


11

Ви шукаєте map_keys , за допомогою них можна писати такі речі

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

Ось приклад того, як це зробити за допомогою трансформатора 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;
  }
}

4

Якщо явного beginі endне потрібного, тобто для циклічного циклу, цикл на клавіші (перший приклад) або значення (другий приклад) можна отримати за допомогою

#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
повинен бути в std
Мордахай

3

Ви хочете це зробити?

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

Так, я знаю, проблема полягає в тому, що у мене клас A {public: // я хотів би відкрити ітератор над ключами приватної карти тут приватною: map <>};
Богдан Балан

У такому випадку я думаю, що ви можете створити список std ::, використовуючи std :: trasnform та вибравши лише ключі з карти. Тоді ви можете викрити ітератор списку, оскільки вставлення в нього ще елементів не призведе до недійсності існуючих ітераторів.
Naveen

3

Якщо вам потрібен ітератор, який просто повертає ключі, вам потрібно перетворити ітератор карти у власний клас, що забезпечує потрібний інтерфейс. Ви можете оголосити новий клас ітераторів з нуля, як тут , використовувати існуючі допоміжні конструкції. Ця відповідь показує, як використовувати Boost, transform_iteratorщоб обернути ітератор у той, який повертає лише значення / ключі.


2

Ти міг

  • створити спеціальний клас ітератора, що агрегує std::map<K,V>::iterator
  • використання std::transformвашого map.begin()до map.end() з boost::bind( &pair::second, _1 )функтором
  • просто ігноруйте ->secondчлен під час ітерації з forциклом.

2

Ця відповідь подібна до родрігоба, за винятком без BOOST_FOREACH. Замість цього ви можете використовувати діапазон c ++.

#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

Без підвищення можна зробити це так. Було б добре, якби ви могли написати оператор лиття замість getKeyIterator (), але я не можу змусити його компілювати.

#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

Для нащадків, і оскільки я намагався знайти спосіб створення діапазону, альтернативою є використання boost :: adapters :: transform

Ось невеликий приклад:

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

Якщо ви хочете перебрати значення, використовуйте t.secondлямбда.


0

Тут багато хороших відповідей, нижче - підхід із використанням декількох з них, який дозволяє вам написати це:

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

Якщо ви завжди цього хотіли, то ось код для 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);
}

0

Я прийняв відповідь Іана про роботу з усіма типами карт і виправив повернення довідки 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

Я знаю, що це не дає відповіді на ваше запитання, але один із варіантів, який ви можете поглянути, - це лише наявність двох векторів з тим самим індексом, який "пов'язаний" інформацією.

Так у ..

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

std::vector<int> vNameCount;

якщо ви хочете кількість імен за іменем, ви просто зробите швидкий пошук циклу на vName.size (), і коли ви знайдете, це індекс для vNameCount, який ви шукаєте.

Звичайно, це може не надавати вам всієї функціональності карти, і залежно від цього може бути, а може і не бути кращим, але це може бути простіше, якщо ви не знаєте ключів і не повинні додавати занадто багато обробки.

Просто пам’ятайте, коли ви додаєте / видаляєте з одного, ви повинні зробити це з іншого, інакше речі стануть шаленими хе: P

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.