2009-03-25 2 views
29

Я писал алгоритм этим утром, и я столкнулся с любопытной ситуацией. У меня есть два std::map s. Я хочу выполнить набор пересечений на наборах ключей каждого (чтобы найти, какие ключи являются общими для обеих карт). В какой-то момент в будущем, я думаю, что, вероятно, мне также понадобится выполнить вычитание здесь. К счастью, STL включает функции для обеих этих операций. Проблема в том, что я не могу получить std::set ключей из std::map. Есть какой-либо способ сделать это? Я искал что-то, что бы это просто, как это в Java:как я могу получить std :: набор ключей к std :: map

std::set<Foo> keys = myMap.getKeySet(); 

Я понимаю, что я не могу использовать функцию std::set_intersection() непосредственно на итераторах в карты, так как карты выставить std::pair объектов вместо просто ключей. Кроме того, я не думаю, что карта гарантирует заказ. Я также заинтересован в выполнении этой же операции по паре std::multimap, если это имеет значение.

EDIT: Я забыл упомянуть, что первоначально в связи с возрастом компилятора я вынужден использовать (MSVC++ 6), большинство из хитроумных уловок шаблонов, которые доступны в импульс не может быть использован.

+2

Не так быстро отказаться от MSVC++ 6 - см. Этот вопрос http://stackoverflow.com/questions/252492/whats-the-latest-version-of-boost-compatible-with-vc6 –

+0

Карта делает ключи в порядке –

ответ

6

Вы можете использовать универсальное повышение :: transform_iterator вернуть итератор, который возвращает только ключи (а не значение). См. How to retrieve all keys (or values) from a std::map and put them into a vector?

+0

это кажется идеальным решением, но я сомневаюсь, что оно скомпилируется для меня ... см. Мое редактирование на вопрос. – rmeador

2

Вы можете просто перебирать и добавлять каждую клавишу в набор. Наборы и карты являются как упорядоченными, так и неупорядоченными вариантами.

+1

, это, по сути, то, что я уже делаю. Я думал, что может быть лучший способ, где «лучше» означает «более стандартное» или «лучшее исполнение». – rmeador

14

Что вы в основном хотите, это копия, так как std :: map не хранит ключи в std :: set. std :: copy предполагает, что типы значений совместимы, что здесь не так. Std :: map :: value_type - std :: пара. Вы хотите скопировать только первую часть пары, что означает, что вам нужен std :: transform. Теперь, поскольку вы будете использовать insert_iterator на наборе, порядок не имеет значения. Std :: set будет сортировать по вставке, даже если карта уже была отсортирована.

[изменить] Код может быть проще. Вершина головы, а не компиляция.

std::transform(MyMap.begin(), MyMap.end(), 
    std::inserter(MySet, MySet.end()), 
    boost::bind(&std::pair<Key,Value>::first, _1)); 

Если у вас есть SGI select1st, вам не нужен boost :: bind.

[править] Обновления для 14

C++
std::transform(MyMap.begin(), MyMap.end(), 
    std::inserter(MySet, MySet.end()), 
    [](auto pair){ return pair.first; }); 
+0

Я не могу полностью следовать тому, что вы говорите, но мне кажется, что это, скорее всего, «правильное» решение. Это также намного сложнее (звучание), чем ответ rlbond (это то, что я уже делаю). Можете ли вы предоставить пример кода? Благодарю. – rmeador

+0

Я не знаю о версии inserter(), принимающей только один аргумент. И использование версии с двумя аргументами будет хорошей вещью, поскольку она использует операцию «вставить с подсказкой», используя тот факт, что элементы карты отсортированы. "std :: inserter (MySet, MySet.end())". –

+0

Yup, back_inserter - один-арг. Исправлена. – MSalters

12

гарантийный заказ; поэтому он называется sorted associative container. Вы можете использовать set_intersection с пользовательской функцией компаратора, второй вариант указан here.

Так, что-то вроде

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, чтобы избежать записи временной функции.)

Оператор по умолчанию < по парам сравнивает оба компонента. Так как вам нужна эквивалентность только над первой частью пары (ключ карты), вам необходимо определить собственный оператор сравнения, который обеспечивает такое отношение (что и делает функция выше).

+3

Для других, кто пытается это сделать, я не считаю, что это работает. Я попытался это и обнаружил, что я падаю на операцию присваивания 'set_intersection',' * _Dest ++ = * _First1 ++; '. Ключ - это const в паре карты, поэтому назначение не выполняется (с ужасной непонятной ошибкой шаблона). – dlanod

6

На практике

yourmap::const_iterator mi; 
set<key_type> k; 
for (mi = yourmap.begin(); mi != yourmap.end(); ++mi) 
    k.insert(mi->first); 
return k; 
+1

O (N), копирует значения ключей. – slacy

2

Я нашел хорошую ссылку на ваш вопрос here

и есть некоторый код для вашей проблемы:

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

такой избыток .... – 2009-03-27 01:19:16

+0

Да, если вы не можете использовать SGI select1st или boost, тогда вы должны сами его закодировать. Для меня это хорошее упражнение. –

2

Лучшее без SGI, не повышающего Решение, совместимое с алгоритмом STL, состоит в том, чтобы расширить map :: итератор следующим образом:

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)); 
+0

Это гений. Так как я просто не хочу, чтобы набор, но даже итераторы, имел к нему доступ. С этим решением у меня есть все, что я хочу, без каких-либо дополнительных зависимостей :) – Paranaix

1

Вы могли бы, возможно, создать итератор для карты, которая только дает ключи, используя подталкивания :: адаптеры :: map_key, смотри пример в boost::adapters::map_key documentation. Это, похоже, было введено в Boost 1.43 и должно быть совместимо с C++ 2003, но я не знаю о VC++ 6, в частности, от эпохи C++ 98.

+0

Доступен ли адаптер map_key в версии Boost, которая работает с VC6? Если нет, можете ли вы * отредактировать * вопрос, чтобы включить это заявление об отказе от ответственности? Если да, можете ли вы ссылаться на более раннюю документацию, скажем, на эру 1,34? – chwarr

+0

@chwarr - спасибо за то, что привлекли это ограничение к моему вниманию. Похоже, что это от Boost 1.43, как самая ранняя ревизия документов Boost, которая, похоже, есть http: //www.boost.org/doc/libs/1_43_0/libs/range/doc/html/range/reference/adapters/reference/map_keys.html. – YitzikC

1

Построение из ответа от zvrba и комментария от dianot:

Просто делают коллекцию Принимающей быть вектором пара вместо карты, и проблема указываемого dianot закончился. Таким образом, используя 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.

Смежные вопросы