Ответ 1
Вы можете использовать универсальный boost:: transform_iterator для возврата итератора, который возвращает только ключи (а не значения). См. Как извлечь все ключи (или значения) из std:: map и поместить их в вектор?
Сегодня утром я писал алгоритм, и я столкнулся с любопытной ситуацией. У меня два std::map
s. Я хочу выполнить набор пересечений на наборах ключей каждого (чтобы найти, какие ключи являются общими для обеих карт). В какой-то момент в будущем, я думаю, что, вероятно, мне также понадобится выполнить вычитание здесь. К счастью, STL включает функции для обеих этих операций. Проблема в том, что я не могу получить std::set
ключей из std::map
. Есть какой-либо способ сделать это? Я ищу что-то, что было бы так просто, как в Java:
std::set<Foo> keys = myMap.getKeySet();
Я понимаю, что я не могу использовать функцию std::set_intersection()
непосредственно на итераторах в карты, потому что в картах отображаются объекты std::pair
вместо простых ключей. Кроме того, я не думаю, что карта гарантирует заказ. Я также заинтересован в выполнении этой же операции на паре std::multimap
s, если это имеет значение.
EDIT. Я забыл упомянуть изначально, что из-за возраста компилятора, который я вынужден использовать (MSVС++ 6), большинство изящных шаблонных трюков, доступных в boost, не могут быть б.
Вы можете использовать универсальный boost:: transform_iterator для возврата итератора, который возвращает только ключи (а не значения). См. Как извлечь все ключи (или значения) из std:: map и поместить их в вектор?
В основном вы хотите получить копию, так как std:: map не хранит ключи в std:: set. std:: copy предполагает, что типы значений совместимы, что здесь не так. Std:: map:: value_type - std:: пара. Вы хотите скопировать только первую часть пары, что означает, что вам нужен std:: transform. Теперь, поскольку вы будете использовать insert_iterator на наборе, порядок не имеет значения. Std:: set будет сортировать по вставке, даже если карта уже была отсортирована.
[edit] Код может быть проще. Вершина моей головы, не скомпилирована.
std::transform(MyMap.begin(), MyMap.end(),
std::inserter(MySet, MySet.end()),
boost::bind(&std::pair<Key,Value>::first, _1));
Если у вас есть SGI select1st, вам не нужен boost:: bind.
[править] Обновлено для С++ 14
std::transform(MyMap.begin(), MyMap.end(),
std::inserter(MySet, MySet.end()),
[](auto pair){ return pair.first; });
Карта действительно гарантирует заказ; поэтому он назвал отсортированный ассоциативный контейнер. Вы можете использовать set_intersection с пользовательской функцией компаратора, второй вариант указан здесь.
Итак, что-то вроде
bool your_less(const your_map::value_type &v1, const your_map::value_type &v2)
{ return v1.first < v2.first; }
set_intersection(m1.begin(), m1.end(), m2.begin(), m2.end(), your_output_it, your_less);
должен сделать трюк. (Также можно использовать boost:: lambda и bind, чтобы избежать записи временной функции.)
Оператор по умолчанию < более пары сравнивают оба компонента. Так как вам нужна эквивалентность только над первой частью пары (ключ карты), вам нужно определить свой собственный оператор сравнения, который обеспечивает такое отношение (что и делает функция выше).
На практике
yourmap::const_iterator mi;
set<key_type> k;
for (mi = yourmap.begin(); mi != yourmap.end(); ++mi)
k.insert(mi->first);
return k;
Вы можете просто перебрать и добавить каждую клавишу в набор. Наборы и карты являются как упорядоченными, так и неупорядоченными вариантами.
Я нашел хорошую ссылку для вашего вопроса здесь
и у вас есть код для вашей проблемы:
#include <iostream>
#include <map>
#include <set>
#include <iterator>
typedef std::map<std::string, int> MyMap;
// also known as select1st in SGI STL implementation
template<typename T_PAIR>
struct GetKey: public std::unary_function<T_PAIR, typename T_PAIR::first_type>
{
const typename T_PAIR::first_type& operator()(const T_PAIR& item) const
{
return item.first;
}
};
int main(int argc, char** argv)
{
MyMap m1,m2;
m1["a"] = 1;
m1["b"] = 2;
m2["c"] = 3;
m2["b"] = 3;
std::set<std::string> s;
std::transform(m1.begin(), m1.end(), std::inserter(s, s.begin()), GetKey<MyMap::value_type>());
std::transform(m2.begin(), m2.end(), std::inserter(s, s.begin()), GetKey<MyMap::value_type>());
std::copy(s.begin(), s.end(), std::ostream_iterator<std::string>(std::cout, " "));
std::cout << std::endl;
return 0;
}
Лучшим решением, не поддерживающим SGI, без ускорения STL, является расширение map:: iterator следующим образом:
template<typename map_type>
class key_iterator : public map_type::iterator
{
public:
typedef typename map_type::iterator map_iterator;
typedef typename map_iterator::value_type::first_type key_type;
key_iterator(const map_iterator& other) : map_type::iterator(other) {} ;
key_type& operator *()
{
return map_type::iterator::operator*().first;
}
};
// helpers to create iterators easier:
template<typename map_type>
key_iterator<map_type> key_begin(map_type& m)
{
return key_iterator<map_type>(m.begin());
}
template<typename map_type>
key_iterator<map_type> key_end(map_type& m)
{
return key_iterator<map_type>(m.end());
}
а затем используйте их так:
map<string,int> test;
test["one"] = 1;
test["two"] = 2;
set<string> keys;
// // method one
// key_iterator<map<string,int> > kb(test.begin());
// key_iterator<map<string,int> > ke(test.end());
// keys.insert(kb, ke);
// // method two
// keys.insert(
// key_iterator<map<string,int> >(test.begin()),
// key_iterator<map<string,int> >(test.end()));
// method three (with helpers)
keys.insert(key_begin(test), key_end(test));
Возможно, вы можете создать итератор для карты, которая дает только ключи с помощью boost:: adapters:: map_key, см. пример в boost:: adapters:: map_key documentation. Это, похоже, было введено в Boost 1.43 и должно быть совместимо с С++ 2003, но я не знаю о VС++ 6 конкретно, который относится к эпохе С++ 98.
Построение ответа от zvrba и комментарий от дианота:
Просто сделайте приемную коллекцию вектором пар вместо карты, а проблема, указанная в дианатоме, закончилась. Итак, используя пример zvrba:
std::vector<std::pair<keytype, valtype>> v;
set_intersection(m1.begin(), m1.end(), m2.begin(), m2.end(),
std::back_inserter(v), []( std::pair<keytype, valtype> const & a,
std::pair<keytype, valtype> const & b){return a.first < b.first;});
Таким образом, без создания промежуточных копий или множеств мы можем эффективно получить пересечение двух карт. Эта конструкция компилируется с помощью gcc5.3.