マップ値をSTLのベクトルにコピーします


85

現在、EffectiveSTLを使用しています。項目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
Pythonは本当に私を台無しにしました:
Gilad Naor

1
ニース、テンプレート。たぶん、コンテナの代わりに出力イテレータを与えてください!
xtofl 2009

Skurmedelのソリューションはさらに優れています。ap-> p.secondファンクターで「transform」関数を使用します。
xtofl 2009

2
私はオッカムの剃刀をしっかりと信じています。不必要にエンティティを紹介しないでください。変換ソリューションの場合、明示的なループソリューションでは必要のない補助関数が必要です。したがって、名前のない関数が得られるまで、私は自分の解決策に固執します。

3
オッカムの剃刀の解釈に注意してください。新しい非const変数「it」を導入することは、最終的に最も安全な解決策ではないかもしれません。STLアルゴリズムは、かなり前から高速で堅牢であることが証明されています。
ヴィンセントロバート

62

あなたはおそらく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) );
}

非常に一般的です。役立つと思われる場合は、彼にクレジットを与えることを忘れないでください。


その私がより良いニールさんより好きです。Workidout、workidout!
xtofl 2009

最後のパラメーターにはラムダを使用することをお勧めします。
varepsilon 2015

@varepsilon:おそらく良い考えです(最新のC ++コンパイラを使用している場合)が、私はもうC ++にそれほど自信がないので、最近はちょっとCの男です。誰かがそれを改善したいと思って、彼らがそれをすることができると思うならば、先に進んでください:)
Skurmedel 2015年

29

古い質問、新しい答え。C ++ 11には、新しいforループがあります。

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

ここで、schemasはa std::map、namesはstd::vector。です。

これにより、配列(名前)にマップ(スキーマ)のキーが入力されます。に変更s.firsts.secondて、値の配列を取得します。


3
それはあるべきですconst auto &s
Slava 2015

1
@Slavaは、次の範囲に基づいて新しいものを明確にします:私が書いた方法は機能しますが、Slavaが提案したバージョンは、参照を使用してイテレーターオブジェクトをコピーすることを回避し、constを指定するため、より高速で安全です。イテレータを変更するのは危険です。ありがとう。
セス2015

4
最短かつ最もクリーンなソリューション。そしておそらく最速です(受け入れられたソリューションよりも高速であり、@ Aragornxのソリューションよりも高速であることがテストされています)。追加するreserve()と、パフォーマンスがさらに向上します。C ++ 11の登場により、これが受け入れられるソリューションになるはずです。
エイドリアンW

3
これはnames.push_back(s.second);であってはなりません。質問がベクトルのキーではなく値を要求するように?
デビッド

24

ブーストライブラリを使用している場合は、boost :: bindを使用して、次のようにペアの2番目の値にアクセスできます。

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

このソリューションは、ブーストメーリングリストのMichaelGoldshteynからの投稿に基づいています


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)

この関数はunaryOperationinputIterator範囲(beginInputRange- endInputRange)のすべてのアイテムを呼び出します。操作の値はに格納されoutputIteratorます。

マップ全体を操作する場合は、入力範囲としてmap.begin()とmap.end()を使用します。マップ値をベクターに保存したいので、ベクターでback_inserterを使用する必要がありますback_inserter(your_values_vector)。back_inserterは、指定された(パラメーターとして)コレクションの最後に新しい要素をプッシュする特別なoutputIteratorです。最後のパラメーターはunaryOperationです-それは1つのパラメーターのみを取ります-inputIteratorの値。したがって[](auto &kv) { [...] }、lambda:を使用できます。 ここで、&kvはマップアイテムのペアへの単なる参照です。したがって、マップのアイテムの値のみを返したい場合は、単にkv.secondを返すことができます。

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

これは疑問を説明していると思います。


3
こんにちは、コードを理解するのに役立つので、コードと一緒に少し説明を追加してください。コードのみの答えは眉をひそめます。
BhargavRao

1
はい!このコードスニペットは質問を解決する可能性があり、説明を含めると投稿の品質が向上します。あなたは将来読者のために質問に答えていることを忘れないでください、そしてそれらの人々はあなたのコード提案の理由を知らないかもしれません。
J. Chomel 2016

それ以前のラムダではautoがサポートされていないため、これはC ++ 14以降でのみ機能すると思います。明示的な関数シグネチャは引き続き機能します。
turoni 2018

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
新しい要素をpush_backするとvが大きくなるため、v.reserve(m.size())を実行する必要はないと思います。
ドラガンOstojić

11
@DraganOstojić.reserve()は1回の再割り当てのみを引き起こします。要素の数によっては、.push_back()が複数の割り当てを実行して同じサイズになる場合があります。
mskfisher 2017年

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 2009

select2ndは、SGIバージョンのSTLの拡張機能です(非公式です)。関数テンプレートをユーティリティとして追加することは、今では第二の性質です(インスピレーションについてはmake_pair <>()を参照してください)。
マーティンヨーク

2

1つの方法は、ファンクターを使用することです。

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

使用法:

自動vec = MapValuesAsVector(anymap);


私はあなたの考えるVECはの2倍のサイズになりますマップ
dyomas

dyomasに感謝します。サイズ変更の代わりに予約を行うように関数を更新し、正しく機能するようになりました
Jan

2

あるべきだと思った

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

2

STLアルゴリズムの変換関数を使用する必要があります。変換関数の最後のパラメーターは、関数オブジェクト、関数ポインター、またはマップのアイテムをベクトルのアイテムに変換するラムダ関数です。このケースマップには、vectorの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 2018年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.