Скопіюйте значення карти у вектор у STL


86

На даний момент я працюю на шляху ефективного STL. Пункт 5 припускає, що зазвичай переважно використовувати функції-члени діапазону для їхніх одноелементних аналогів. На даний момент я хочу скопіювати всі значення на карті (тобто - мені не потрібні ключі) у вектор.

Який найчистіший спосіб це зробити?


Якщо ключі не потрібні, може знадобитися і не вся карта. У такому випадку розгляньте можливість переміщення значень з карти у вектор, як описано у цьому питанні .
Никодим

Відповіді:


61

Тут ви не можете легко використовувати діапазон, оскільки ітератор, який ви отримуєте з карти, посилається на std :: pair, де ітератори, які ви використовували б для вставки у вектор, посилаються на об'єкт типу, що зберігається у векторі, тобто (якщо ви відкидаєте ключ) не пара.

Я справді не думаю, що це стає набагато чистішим, ніж очевидне:

#include <map>
#include <vector>
#include <string>
using namespace std;

int main() {
    typedef map <string, int> MapType;
    MapType m;  
    vector <int> v;

    // populate map somehow

    for( MapType::iterator it = m.begin(); it != m.end(); ++it ) {
        v.push_back( it->second );
    }
}

яку я, мабуть, переписав би як шаблонну функцію, якби збирався використовувати її більше одного разу. Щось на зразок:

template <typename M, typename V> 
void MapToVec( const  M & m, V & v ) {
    for( typename M::const_iterator it = m.begin(); it != m.end(); ++it ) {
        v.push_back( it->second );
    }
}

79
Python мене справді розпестив :-(
Гілад Наор,

1
Приємно, шаблон. Можливо, дайте йому вихідний ітератор замість контейнера!
xtofl

Рішення Скурмеделя ще приємніше: використовуйте функцію 'перетворення' з ap -> p. Другим функтором.
xtofl

2
Я твердо вірю в бритву Оккама - не вводьте сутності без потреби. У випадку рішення перетворення нам потрібна допоміжна функція, яка не потрібна для рішення явного циклу. Отже, поки ми не отримаємо безіменні функції, я буду дотримуватися свого рішення.

3
Остерігайтеся інтерпретації Бритви Оккама. Введення нової змінної non-const "it", зрештою, може бути не найбезпечнішим рішенням. Алгоритми STL вже давно доведено швидкими та надійними.
Вінсент Роберт,

62

Можливо, ви могли б використовувати std::transformдля цієї мети. Можливо, я віддав би перевагу версії Neils, залежно від того, що є більш читабельним.


Приклад xtofl (див. Коментарі):

#include <map>
#include <vector>
#include <algorithm>
#include <iostream>

template< typename tPair >
struct second_t {
    typename tPair::second_type operator()( const tPair& p ) const { return p.second; }
};

template< typename tMap > 
second_t< typename tMap::value_type > second( const tMap& m ) { return second_t< typename tMap::value_type >(); }


int main() {
    std::map<int,bool> m;
    m[0]=true;
    m[1]=false;
    //...
    std::vector<bool> v;
    std::transform( m.begin(), m.end(), std::back_inserter( v ), second(m) );
    std::transform( m.begin(), m.end(), std::ostream_iterator<bool>( std::cout, ";" ), second(m) );
}

Дуже загальний, не забудьте дати йому кредит, якщо вважаєте це корисним.


що мені подобається навіть більше, ніж у Ніла. Виправлення праці, виправлення праці!
xtofl

Я б запропонував використовувати лямбду для останнього параметра.
varepsilon

@varepsilon: Напевно, це гарна ідея (якщо хтось перебуває на сучасному компіляторі C ++), але я вже не настільки впевнений у C ++, я в даний час справді чувак C Якщо хтось хоче його вдосконалити і вважає, що може це зробити, будь ласка, продовжуйте :)
Скурмедель

29

Старе питання, нова відповідь. З C ++ 11 у нас є фантастичний новий цикл for:

for (const auto &s : schemas)
   names.push_back(s.first);

де схеми - а, std::mapа імена - std::vector.

Це заповнює масив (імена) ключами з карти (схеми); змінити s.firstна, s.secondщоб отримати масив значень.


3
Має бутиconst auto &s
Слава

1
@Slava пояснити будь-яке нове для діапазону, засноване на: те, як я це написав, працює, однак, версія, яку запропонував Слава, є швидшою та безпечнішою, оскільки дозволяє уникнути копіювання об'єкта-ітератора за допомогою посилання, і вказує const, оскільки це буде небезпечно змінювати ітератор. Дякую.
Сет

4
Найкоротше і найчистіше рішення. І, мабуть, найшвидший (перевірено на швидкість, ніж прийняте рішення, а також швидше, ніж рішення @ Aragornx). Додайте, reserve()і ви отримаєте ще один приріст продуктивності. З появою С ++ 11 це тепер має бути прийнятим рішенням!
Adrian W

3
Це не повинно бути names.push_back (s.second); як питання задає значення, а не ключі у векторі?
Девід

24

Якщо ви використовуєте бібліотеки boost , ви можете використовувати boost :: bind для доступу до другого значення пари наступним чином:

#include <string>
#include <map>
#include <vector>
#include <algorithm>
#include <boost/bind.hpp>

int main()
{
   typedef std::map<std::string, int> MapT;
   typedef std::vector<int> VecT;
   MapT map;
   VecT vec;

   map["one"] = 1;
   map["two"] = 2;
   map["three"] = 3;
   map["four"] = 4;
   map["five"] = 5;

   std::transform( map.begin(), map.end(),
                   std::back_inserter(vec),
                   boost::bind(&MapT::value_type::second,_1) );
}

Це рішення базується на дописі Майкла Голдштейна у списку розсилки .


24
#include <algorithm> // std::transform
#include <iterator>  // std::back_inserter
std::transform( 
    your_map.begin(), 
    your_map.end(),
    std::back_inserter(your_values_vector),
    [](auto &kv){ return kv.second;} 
);

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

transform( beginInputRange, endInputRange, outputIterator, unaryOperation)

ця функція викликає unaryOperationкожен елемент із inputIteratorдіапазону ( beginInputRange- endInputRange). Значення операції зберігається в outputIterator.

Якщо ми хочемо оперувати цілою картою - ми використовуємо map.begin () та map.end () як наш діапазон введення. Ми хочемо зберегти наші цінності карти у векторі - тому ми повинні використовувати back_inserter на нашому векторі: back_inserter(your_values_vector). Back_inserter - це спеціальний outputIterator, який штовхає нові елементи в кінці даної (як параметр) колекції. Останній параметр - unaryOperation - він приймає лише один параметр - значення inputIterator. Тож ми можемо використовувати лямбда:, [](auto &kv) { [...] }де & kv - це лише посилання на пару об’єкта карти. Отже, якщо ми хочемо повернути лише значення елементів на карті, ми можемо просто повернути kv.second:

[](auto &kv) { return kv.second; }

Думаю, це пояснює будь-які сумніви.


3
Привіт, додайте трохи пояснень разом із кодом, оскільки це допомагає зрозуміти ваш код. Відповіді лише за кодом не бачать.
Бхаргав Рао

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

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

19

Використовуючи лямбди можна виконати наступне:

{
   std::map<std::string,int> m;
   std::vector<int> v;
   v.reserve(m.size());
   std::for_each(m.begin(),m.end(),
                 [&v](const std::map<std::string,int>::value_type& p) 
                 { v.push_back(p.second); });
}

1
Я не думаю, що вам потрібно v.reserve (m.size ()), тому що v буде рости, коли ви відсуваєте нові елементи.
Драган Остоїч

11
@ DraganOstojić .reserve () викликає лише одне перерозподіл. Залежно від кількості елементів, .push_back () може виконувати кілька розподілів, щоб отримати однаковий розмір.
mskfisher

8

Ось що б я зробив.
Також я б використав функцію шаблону, щоб полегшити побудову select2nd.

#include <map>
#include <vector>
#include <algorithm>
#include <memory>
#include <string>

/*
 * A class to extract the second part of a pair
 */   
template<typename T>
struct select2nd
{
    typename T::second_type operator()(T const& value) const
    {return value.second;}
};

/*
 * A utility template function to make the use of select2nd easy.
 * Pass a map and it automatically creates a select2nd that utilizes the
 * value type. This works nicely as the template functions can deduce the
 * template parameters based on the function parameters. 
 */
template<typename T>
select2nd<typename T::value_type> make_select2nd(T const& m)
{
    return select2nd<typename T::value_type>();
}

int main()
{
    std::map<int,std::string>   m;
    std::vector<std::string>    v;

    /*
     * Please note: You must use std::back_inserter()
     *              As transform assumes the second range is as large as the first.
     *              Alternatively you could pre-populate the vector.
     *
     * Use make_select2nd() to make the function look nice.
     * Alternatively you could use:
     *    select2nd<std::map<int,std::string>::value_type>()
     */   
    std::transform(m.begin(),m.end(),
                   std::back_inserter(v),
                   make_select2nd(m)
                  );
}

1
Хороший. А чому make_select2nd немає в stl?
Микола Голуб'єв

select2nd - розширення STL у версії SGI (настільки неофіційна). Додавання шаблонів функцій як утиліт - це лише друга природа (див. Make_pair <> () для натхнення).
Мартін Йорк,

2

Один із способів - використовувати функтор:

 template <class T1, class T2>
    class CopyMapToVec
    {
    public: 
        CopyMapToVec(std::vector<T2>& aVec): mVec(aVec){}

        bool operator () (const std::pair<T1,T2>& mapVal) const
        {
            mVec.push_back(mapVal.second);
            return true;
        }
    private:
        std::vector<T2>& mVec;
    };


int main()
{
    std::map<std::string, int> myMap;
    myMap["test1"] = 1;
    myMap["test2"] = 2;

    std::vector<int>  myVector;

    //reserve the memory for vector
    myVector.reserve(myMap.size());
    //create the functor
    CopyMapToVec<std::string, int> aConverter(myVector);

    //call the functor
    std::for_each(myMap.begin(), myMap.end(), aConverter);
}

Я б не турбувався про змінну aConverter. просто створіть тимчасове в for_each. std :: for_each (myMap.begin (), myMap.end (), CopyMapToVec <std :: string, int> (myVector));
Мартін Йорк,

віддайте перевагу "перетворенню", оскільки це те, що ви робите: перетворюючи карту у вектор за допомогою досить простого функтора.
xtofl

2

Чому ні:

template<typename K, typename V>
std::vector<V> MapValuesAsVector(const std::map<K, V>& map)
{
   std::vector<V> vec;
   vec.reserve(map.size());
   std::for_each(std::begin(map), std::end(map),
        [&vec] (const std::map<K, V>::value_type& entry) 
        {
            vec.push_back(entry.second);
        });
    return vec;
}

використання:

auto vec = MapValuesAsVector (будь-яка карта);


Я думаю, що ваш vec буде вдвічі більшим за карту
Dyomas

спасибі діомасе, я оновив функцію робити резерв замість зміни розміру, і тепер вона працює правильно
Ян Вілманс

2

Я думав, що так повинно бути

std::transform( map.begin(), map.end(), 
                   std::back_inserter(vec), 
                   boost::bind(&MapT::value_type::first,_1) ); 

2

Ми повинні використовувати функцію перетворення з алгоритму STL, останнім параметром функції перетворення може бути об'єкт функції, покажчик функції або лямбда-функція, яка перетворює елемент карти в елемент вектора. У цій карті випадків елементи мають пару типів, які потрібно перетворити на елемент, який має тип int для вектора. Ось моє рішення, що я використовую лямбда-функцію:

#include <algorithm> // for std::transform
#include <iterator>  // for back_inserted

// Map of pair <int, string> need to convert to vector of string
std::map<int, std::string> mapExp = { {1, "first"}, {2, "second"}, {3, "third"}, {4,"fourth"} };

// vector of string to store the value type of map
std::vector<std::string> vValue;

// Convert function
std::transform(mapExp.begin(), mapExp.end(), std::back_inserter(vValue),
       [](const std::pair<int, string> &mapItem)
       {
         return mapItem.second;
       });

-3

Здивований, що ніхто не згадав про найбільш очевидне рішення , використовуйте конструктор std :: vector.

template<typename K, typename V>
std::vector<std::pair<K,V>> mapToVector(const std::unordered_map<K,V> &map)
{
    return std::vector<std::pair<K,V>>(map.begin(), map.end());
}

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