STL의 벡터에 맵 값 복사


85

현재 효과적인 STL을 통해 작업 중입니다. 항목 5는 일반적으로 단일 요소 대응에 대해 범위 멤버 함수를 사용하는 것이 바람직하다는 것을 제안합니다. 현재 맵의 모든 값 (예 : 키가 필요하지 않음)을 벡터에 복사하고 싶습니다.

이를 수행하는 가장 깨끗한 방법은 무엇입니까?


키가 필요하지 않은 경우 전체 맵도 필요하지 않을 수 있습니다. 이 경우이 질문에 설명 된대로 맵에서 벡터로 값을 이동하는 것을 고려하십시오 .
Nykodym

답변:


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 );
    }
}

78
파이썬이 정말 나를 망쳤습니다 :-(
Gilad Naor

1
좋습니다, 템플릿입니다. 컨테이너 대신 출력 반복자를 제공 할 수 있습니다!
xtofl 2009

Skurmedel의 솔루션은 훨씬 더 좋습니다. ap-> p.second 펑터와 함께 '변환'함수를 사용하십시오.
xtofl 2009

2
저는 Occam의 Razor를 굳게 믿습니다. 불필요하게 엔티티를 소개하지 마십시오. 변환 솔루션의 경우 명시 적 루프 솔루션에 필요하지 않은 보조 함수가 필요합니다. 따라서 이름없는 함수를 얻을 때까지 내 솔루션을 고수 할 것입니다.

3
Occam의 Razor 해석에주의하십시오. 상수가 아닌 새로운 변수 "it"을 도입하는 것은 결국 가장 안전한 솔루션이 아닐 수 있습니다. STL 알고리즘은 꽤 오랫동안 빠르고 강력하다는 것이 입증되었습니다.
Vincent Robert

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 2009

마지막 매개 변수로 람다를 사용하는 것이 좋습니다.
varepsilon 2015 년

@varepsilon : 아마도 좋은 생각 일 것입니다 (최신 C ++ 컴파일러에있는 경우).하지만 저는 더 이상 C ++에 대해 확신이 없습니다. 요즘은 C 친구입니다. 누군가가 그것을 개선하고 싶어하고 그것을 할 수 있다고 생각하는 경우 : 진행하시기 바랍니다
Skurmedel

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
Slava

1
@Slava를 기반으로 범위에 새로운 사람을 명확히하기 위해 : 내가 작성한 방식이 작동하지만 Slava가 제안한 버전은 참조를 사용하여 반복기 객체를 복사하는 것을 피하고 const를 지정하므로 더 빠르고 안전합니다. 반복자를 수정하는 것은 위험합니다. 감사.
Seth

4
가장 짧고 깨끗한 솔루션. 그리고 아마도 가장 빠를 것입니다 (허용 된 솔루션보다 빠르며 @Aragornx의 솔루션보다 빠름). 추가 reserve()하면 또 다른 성능 향상을 얻을 수 있습니다. C ++ 11의 출현으로 이제 허용되는 솔루션이되어야합니다!
Adrian W

3
이것은 names.push_back (s.second); 질문이 벡터의 키가 아닌 값을 요구하므로?
David

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) );
}

이 솔루션은 부스트 메일 링리스트 에있는 Michael Goldshteyn의 게시물을 기반으로 합니다 .


23
#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모든 항목을 호출 합니다 . 작업 값은inputIteratorbeginInputRangeendInputRangeoutputIterator .

전체 맵을 통해 작업하려면 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; }

나는 이것이 모든 의심을 설명한다고 생각합니다.


3
안녕하세요, 코드를 이해하는 데 도움이되므로 코드와 함께 약간의 설명을 추가하세요. 코드 전용 답변은 눈살을 찌푸립니다.
Bhargav Rao

1
예! 이 코드 스 니펫은 글의 품질을 향상시키는 데 도움이 되는 설명을 포함 하여 질문을 해결할 수 있습니다 . 미래에 독자를 위해 질문에 답하고 있으며 해당 사용자는 코드 제안 이유를 모를 수 있습니다.
J. Chomel

자동은 이전의 람다에서 지원되지 않기 때문에 C ++ 14부터 만 작동한다고 생각합니다. 명시 적 함수 서명은 계속 작동합니다.
turoni

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 ()) 할 필요가 없다고 생각합니다. 새로운 요소를 push_back함에 따라 v가 커질 것이기 때문입니다.
Dragan Ostojić

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에 없습니까?
Mykola Golubyev

select2nd는 SGI 버전의 STL에 대한 확장입니다 (비공식적). 함수 템플릿을 유틸리티로 추가하는 것은 이제 제 2의 특성 일뿐입니다 (영감을 얻으려면 make_pair <> () 참조).
Martin York

2

한 가지 방법은 functor를 사용하는 것입니다.

 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));
Martin York

당신이하고있는 일이기 때문에 '변환'을 선호합니다. 매우 간단한 펑터를 사용하여지도를 벡터로 변환합니다.
xtofl 2009

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


나는 당신의 생각 VEC이 의 두 배 크기입니다 지도
dyomas

감사 dyomas, 내가 대신 크기 조정의 예비 작업을 수행 할 기능을 업데이트했습니다 지금은 제대로 작동
월 Wilmans을

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 2010 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.