Копировать значения карты в вектор в STL

В данный момент прорабатываю эффективный STL. Правило 5 предполагает, что обычно предпочтительнее использовать функции-члены диапазона вместо их одноэлементных аналогов. В настоящее время я хочу скопировать все значения на карте (т.е. мне не нужны ключи) в вектор.

Как лучше всего это сделать?


person Gilad Naor    schedule 21.04.2009    source источник
comment
Если ключи не нужны, может не понадобиться и вся карта. В таком случае рассмотрите возможность перемещения значений с карты в вектор, как описано в этом вопросе.   -  person Nykodym    schedule 16.01.2020


Ответы (13)


Вы не можете легко использовать здесь диапазон, потому что итератор, который вы получаете от карты, ссылается на 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 );
    }
}
person Community    schedule 21.04.2009
comment
Python меня действительно избаловал :-( - person Gilad Naor; 21.04.2009
comment
Хороший шаблон. Может, дать ему итератор вывода вместо контейнера! - person xtofl; 21.04.2009
comment
Решение Скурмеделя еще лучше: используйте функцию 'transform' с функтором p - ›p.second. - person xtofl; 21.04.2009
comment
Я твердо верю в бритву Оккама - не вводите сущности без надобности. В случае решения с преобразованием нам нужна вспомогательная функция, которая не нужна для явного решения цикла. Так что, пока мы не получим безымянные функции, я буду придерживаться своего решения. - person ; 21.04.2009
comment
Остерегайтесь интерпретации бритвы Оккама. Введение новой неконстантной переменной в конечном итоге может оказаться не самым безопасным решением. Алгоритмы STL уже давно зарекомендовали себя как быстрые и надежные. - person Vincent Robert; 21.04.2009
comment
Но как только появятся лямбда-функции, это будет самое простое решение. - person tstenner; 21.04.2009
comment
@vincent, но область действия переменной ограничена функцией - если бы я мог ограничить область действия вспомогательной функции (как я смогу, в конечном итоге), я бы сам использовал подход преобразования - до тех пор нет. - person ; 21.04.2009
comment
@tstenner - к сожалению, нам приходится программировать здесь и сейчас. - person ; 21.04.2009
comment
@neil На самом деле, вы можете использовать анонимное пространство имен, чтобы скрыть функторы от внешнего мира. - person Vincent Robert; 22.04.2009
comment
В C ++ 11 теперь есть более элегантное решение с диапазоном для. См. Ответ @ Seth. Но также традиционный подход с итератором оставляет место для улучшения формы: m.end() не изменяется во время цикла. Вы должны кешировать его в переменной. - person Adrian W; 11.10.2017
comment
@GiladNaor, первый вариант здесь скорее питонический: techiedelight. ru / convert-map-vector-key-value-pair-cpp - person franjesus; 28.08.2018
comment
Извините, разве ++it в цикле for не означает, что код пропускает первый элемент карты m, а в конце пытается получить доступ к m.end()->second? - person Kagaratsch; 24.02.2021

Вероятно, вы могли бы использовать для этой цели std::transform. Я, возможно, предпочел бы версию Нилса, в зависимости от того, что более читабельно.


Пример от 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) );
}

Очень общий, не забудьте отдать ему должное, если сочтете это полезным.

person Skurmedel    schedule 21.04.2009
comment
это мне нравится даже больше, чем у Нейла. Тренировка, тренировка! - person xtofl; 21.04.2009
comment
Я бы посоветовал использовать лямбду в качестве последнего параметра. - person varepsilon; 25.09.2015
comment
@varepsilon: Вероятно, хорошая идея (если она на современном компиляторе C ++), но я больше не уверен в C ++, я вроде как чувак на C. Если кто-то хочет улучшить его и думает, что может это сделать, пожалуйста, продолжайте :) - person Skurmedel; 01.10.2015

Старый вопрос, новый ответ. В C ++ 11 появился новый модный цикл for:

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

где схемы - это std::map, а имена - это std::vector.

Это заполняет массив (имена) ключами из карты (схем); измените s.first на s.second, чтобы получить массив значений.

person Seth    schedule 29.12.2015
comment
Это должно быть const auto &s - person Slava; 30.12.2015
comment
@Slava, чтобы прояснить для любого нового в диапазоне, основанном на: способ, которым я написал, это работает, однако версия, предложенная Славой, быстрее и безопаснее, поскольку позволяет избежать копирования объекта итератора с помощью ссылки и указывает константу, поскольку она была бы опасно изменять итератор. Спасибо. - person Seth; 31.12.2015
comment
Кратчайшее и чистое решение. И, вероятно, самый быстрый (проверено, что он быстрее, чем принятое решение, а также быстрее, чем решение @Aragornx). Добавьте reserve(), и вы получите еще один прирост производительности. С появлением C ++ 11 это должно стать приемлемым решением! - person Adrian W; 11.10.2017
comment
Разве это не должно быть names.push_back (s.second); поскольку вопрос требует значений, а не ключей в векторе? - person David; 12.04.2018

#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 (_5 _-_ 6_). Значение операции сохраняется в outputIterator.

Если мы хотим работать со всей картой - мы используем map.begin () и map.end () в качестве диапазона ввода. Мы хотим сохранить значения нашей карты в векторе - поэтому мы должны использовать back_inserter в нашем векторе: back_inserter(your_values_vector). Back_inserter - это специальный outputIterator, который подталкивает новые элементы в конец данной (как параметр) коллекции. Последний параметр - unaryOperation - принимает только один параметр - значение inputIterator. Таким образом, мы можем использовать lambda: [](auto &kv) { [...] }, где & kv - это просто ссылка на пару элементов карты. Поэтому, если мы хотим вернуть только значения элементов карты, мы можем просто вернуть kv.second:

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

Думаю, это объясняет любые сомнения.

person Aragornx    schedule 23.09.2016
comment
Привет, добавьте немного пояснений вместе с кодом, так как это помогает понять ваш код. Ответы только на код не одобряются. - person Bhargav Rao; 23.09.2016
comment
Да! этот фрагмент кода может решить вопрос, включая объяснение действительно помогает улучшить качество вашего поста. Помните, что вы отвечаете на вопрос для читателей в будущем, и эти люди могут не знать причины вашего предложения кода. - person J. Chomel; 23.09.2016
comment
Я думаю, что это работает только начиная с C ++ 14, поскольку до этого в лямбдах не поддерживалось auto. Явная подпись функции все равно будет работать. - person turoni; 30.07.2018

Если вы используете библиотеки 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) );
}

Это решение основано на сообщении Майкла Голдштейна в списке рассылки boost .

person OK.    schedule 09.04.2010

Используя лямбды, можно сделать следующее:

{
   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); });
}
person Community    schedule 21.04.2009
comment
Я не думаю, что вам нужно v.reserve (m.size ()), потому что v будет расти по мере того, как вы нажимаете новые элементы. - person Dragan Ostojić; 28.09.2016
comment
@ DraganOstojić .reserve () вызывает только одно перераспределение. В зависимости от количества элементов .push_back () может выполнять несколько распределений, чтобы получить один и тот же размер. - person mskfisher; 14.03.2017

Вот что я бы сделал.
Также я бы использовал функцию шаблона, чтобы упростить создание 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)
                  );
}
person Martin York    schedule 21.04.2009
comment
Неплохо. А почему make_select2nd нет в stl? - person Mykola Golubyev; 21.04.2009
comment
select2nd - это расширение STL в версии SGI (так неофициально). Добавление шаблонов функций в качестве утилит теперь просто вторая натура (см. Make_pair ‹› () для вдохновения). - person Martin York; 21.04.2009

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

 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);
}
person aJ.    schedule 21.04.2009
comment
Я бы не стал заморачиваться с переменной aConverter. просто создайте временный файл в for_each. std :: for_each (myMap.begin (), myMap.end (), CopyMapToVec ‹std :: string, int› (myVector)); - person Martin York; 21.04.2009
comment
предпочитаю «преобразовать», поскольку именно это вы и делаете: преобразование карты в вектор с помощью довольно простого функтора. - person xtofl; 21.04.2009

Почему нет:

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 (anymap);

person Jan Wilmans    schedule 18.12.2015
comment
Я думаю, ваш vec будет вдвое больше, чем map - person dyomas; 30.08.2016
comment
спасибо dyomas, я обновил функцию, чтобы сделать резервную копию вместо изменения размера, и теперь она работает правильно - person Jan Wilmans; 20.03.2017

Я думал так должно быть

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

Мы должны использовать функцию преобразования из алгоритма 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;
       });
person Loc Tran    schedule 01.02.2019

В других ответах упоминается std :: transform, и семантически это правильный выбор. Но на практике для этой задачи может лучше подходить std :: accumulate, потому что:

  • позволяет добавить к результирующему вектору const;
  • он просто выглядит красивее, по-настоящему функционально.

Пример (с использованием синтаксиса C ++ 17):

#include <numeric> // for std::accumulate. Note that it's not in <algorithm> where std::transform is located, thanks to Anton Krug for pointing this out

auto map = std::map<int,bool>{};
map[0]=true;
map[1]=false;

const auto mapValues = std::accumulate(map.begin(), map.end(), std::vector<bool>(map.size()), [](auto& vector, const auto& mapEntry) {
    vector.push_back(mapEntry.second);
    return vector;
});
person Michael D.    schedule 15.04.2021
comment
Это не совсем правильно, ваш код не компилируется, вот версия для компиляции: godbolt.org/z/hMafr6jzE BTW накапливается в ‹numeric›, а не в ‹algorithm›. Преобразование осуществляется в ‹algorithm›, поэтому информация вводится в заблуждение: en.cppreference.com / w / cpp / Algorithm / накопить - person Anton Krug; 15.04.2021

Удивленный, что никто не упомянул наиболее очевидное решение, используйте конструктор 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());
}
person franjesus    schedule 28.08.2018
comment
Это потому, что ваше решение не соответствует поставленному вопросу. Вектор должен состоять только из значений. - person ypnos; 05.10.2018