Итерировать ключи в карте C ++

123

Есть ли способ перебирать ключи, а не пары карты C ++?

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

Ответы:

70

Если вам действительно нужно скрыть значение, которое возвращает «настоящий» итератор (например, потому что вы хотите использовать свой итератор-ключ со стандартными алгоритмами, чтобы они работали с ключами, а не парами), то взгляните на Boost's transform_iterator .

[Совет: просматривая документацию по Boost для нового класса, сначала прочтите "примеры" в конце. Тогда у вас есть шанс выяснить, о чем, черт побери, все остальное говорит :-)]

Стив Джессоп
источник
2
С помощью boost вы можете написать 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/…
rodrigob
121

карта является ассоциативным контейнером. Следовательно, итератор - это пара ключей val. ЕСЛИ вам нужны только ключи, вы можете игнорировать часть значения из пары.

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

РЕДАКТИРОВАТЬ:: Если вы хотите выставить снаружи только ключи, вы можете преобразовать карту в вектор или ключи и выставить.

Aj.
источник
Но тогда было бы действительно плохой идеей выставлять итератор вектора снаружи.
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 для этой карты. Например, отображение строк в целые числа:

#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
Под шаблонным решением я добавил итератор Value.
degski
связал ваш вопрос с моим.
Ян
template<typename C> class key_iterator : public C::iteratorи т. д.
Габриэль
38

В C ++ 17 вы можете использовать структурированную привязку внутри цикла for на основе диапазона (соответственно адаптируя ответ Джона Х. ):

#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поскольку можно было бы использовать for std::tie(..), не работает, см. Это обсуждение ).

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

Elmar
источник
не могли бы вы назначить его на std :: ignore в принципе? Действительно ли это повредит эффективности скомпилированного кода или ничего не даст? (Я имею в виду не привязку, а скорее действие внутри цикла)
KotoroShinoto
Начиная с C ++ 17 вы также можете использовать [[might_unused]]. Это подавляет предупреждение. Как это:for ([[maybe_unused]] const auto &[key, v_not_used] : my_map) { use(key); }
arhuaco
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; }
};

Все кредиты принадлежат Яну ... Спасибо, Ян.

degski
источник
11

Вы ищете map_keys , с его помощью вы можете писать такие вещи, как

BOOST_FOREACH(const key_t key, the_map | boost::adaptors::map_keys)
{
  // do something with key
}
rodrigob
источник
1
BOOST_FOREACH(const key_t& key, ...
strickli
5

Вот пример того, как это сделать с помощью transform_iterator Boost

#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
Mordachai
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: // я хотел бы выставить итератор по ключам приватной карты здесь private: map <>};
Богдан Балан
В этом случае, я думаю, вы можете создать std :: list, используя std :: trasnform и собирая только ключи с карты. Затем вы можете открыть итератор списка, так как добавление дополнительных элементов в список не приведет к аннулированию существующих итераторов.
Naveen
3

Если вам нужен итератор, который просто возвращает ключи, вам нужно обернуть итератор карты в ваш собственный класс, который предоставляет желаемый интерфейс. Вы можете объявить новый класс итератора с нуля, как здесь , или использовать существующие вспомогательные конструкции. Этот ответ показывает, как использовать Boost, transform_iteratorчтобы обернуть итератор в тот, который возвращает только значения / ключи.

STH
источник
2

Ты мог

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

Этот ответ похож на ответ Родригоба, только без BOOST_FOREACH . Вместо этого вы можете использовать диапазон С ++.

#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
источник
0

Без Boost вы могли бы сделать это так. Было бы неплохо, если бы вы могли написать оператор приведения вместо 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лямбда.

ipapadop
источник
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

голубоватый
источник