Harita değerlerini STL'deki vektöre kopyala


86

Şu anda Etkili STL ile yoluma devam ediyorum. Madde 5, genellikle aralık üye işlevlerini tek öğeli emsallerine göre kullanmanın tercih edildiğini göstermektedir. Şu anda bir haritadaki tüm değerleri (yani - anahtarlara ihtiyacım yok) bir vektöre kopyalamak istiyorum.

Bunu yapmanın en temiz yolu nedir?


Anahtarlara gerek yoksa haritanın tamamına da ihtiyaç duyulmayabilir. Böyle bir durumda değerleri haritadan vektöre bu soruda anlatıldığı gibi taşımayı düşünün .
Nykodym

Yanıtlar:


61

Burada bir aralığı kolayca kullanamazsınız çünkü bir haritadan aldığınız yineleyici bir std :: pair'e başvurur, burada bir vektöre eklemek için kullanacağınız yineleyiciler vektörde depolanan türde bir nesneye atıfta bulunur. (eğer anahtarı atıyorsanız) bir çift değil.

Gerçekten bariz olandan daha temiz olduğunu düşünmüyorum

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

ki bunu bir kereden fazla kullanacak olsaydım muhtemelen bir şablon işlevi olarak yeniden yazardım. Gibi bir şey:

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

83
Python beni gerçekten şımarttı :-(
Gilad Naor

1
Güzel, şablon. Belki ona bir konteyner yerine bir çıktı yineleyicisi verin!
xtofl

Skurmedel'in çözümü daha da güzel: ap -> p.second functor ile 'transform' işlevini kullanın.
xtofl

2
Occam's Razor'a kesinlikle inanıyorum - varlıkları gereksiz yere tanıtmayın. Dönüşüm çözümü durumunda, açık döngü çözümünde ihtiyaç duyulmayan bir yardımcı işleve ihtiyacımız var. Yani isimsiz fonksiyonlar elde edene kadar çözümüme bağlı kalacağım.

3
Occam'ın Razor yorumuna dikkat edin. Yeni bir const olmayan değişken "it" sunmak, sonuçta en güvenli çözüm olmayabilir. STL algoritmalarının bir süredir hızlı ve sağlam olduğu kanıtlanmıştır.
Vincent Robert

62

Muhtemelen std::transformbu amaç için kullanabilirsiniz . Neyin daha okunabilir olduğuna bağlı olarak belki Neils versiyonunu tercih ederim.


Xtofl ile örnek (yorumlara bakın):

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

Çok genel, eğer yararlı bulursanız ona itibar etmeyi unutmayın.


O daha da iyi Neil daha ister. Antrenman, antrenman!
xtofl

Son parametre için lambda kullanmanızı öneririm.
varepsilon

@varepsilon: Muhtemelen iyi bir fikir (eğer biri modern bir C ++ derleyicisindeyse), ama artık C ++ konusunda o kadar da emin değilim, bu günlerde bir çeşit C dostuyum. İyileştirmek isteyen ve yapabileceğini düşünen varsa lütfen devam edin :)
Skurmedel 01

30

Eski soru, yeni cevap. C ++ 11 ile yeni ve şık for döngüsüne sahibiz:

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

Şemalar nerede std::mapve isimler birstd::vector .

Bu, diziyi (adları) haritadaki (şemalar) anahtarlarla doldurur; değer dizisi elde s.firstetmek s.secondiçin olarak değiştirin .


3
O olmalıconst auto &s
Slava

1
@Slava, aralığı temel alan herhangi bir yenisini açıklığa kavuşturmak için: yazdığım yöntem işe yarıyor, ancak, Slava'nın önerdiği sürüm, yineleyici nesnesini bir referans kullanarak kopyalamaktan kaçınarak daha hızlı ve daha güvenlidir ve olacağı için bir sabit belirler yineleyiciyi değiştirmek tehlikelidir. Teşekkürler.
Seth

4
En kısa ve en temiz çözüm. Ve muhtemelen en hızlısı (kabul edilen çözümden ve ayrıca @ Aragornx çözümünden daha hızlı olduğu test edildi). Ekleyin reserve()ve başka bir performans kazancı elde edin. C ++ 11'in gelişiyle birlikte, artık kabul edilen çözüm bu olmalıdır!
Adrian W

3
Bu names.push_back (s.second) olmamalıdır; soru vektördeki anahtarları değil değerleri sorduğunda?
David

24

Yükseltme kitaplıklarını kullanıyorsanız, aşağıdaki gibi çiftin ikinci değerine erişmek için boost :: bind kullanabilirsiniz:

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

Bu çözüm, Michael Goldshteyn'in destek posta listesindeki bir gönderisine dayanmaktadır .


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

Üzgünüm herhangi bir açıklama eklemedim - kodun o kadar basit olduğunu ve herhangi bir açıklama gerektirmediğini düşündüm. Yani:

transform( beginInputRange, endInputRange, outputIterator, unaryOperation)

bu işlev aralıktaki ( - ) unaryOperationher öğeyi çağırır . İşlemin değeri şurada saklanır:inputIteratorbeginInputRangeendInputRangeoutputIterator .

Tüm harita üzerinde çalışmak istiyorsak - girdi aralığımız olarak map.begin () ve map.end () kullanırız. Biz vektör içine Haritamız değerlerini saklamak istediğiniz - bizim vektör üzerinde back_inserter kullanmak zorunda: back_inserter(your_values_vector). Back_inserter, verilen (paremeter olarak) koleksiyonun sonunda yeni öğeleri iten özel outputIterator'dır. Son parametre unaryOperation'dır - yalnızca bir parametre alır - inputIterator değeri. Böylece lambda: kullanabiliriz [](auto &kv) { [...] }, burada & kv yalnızca eşleme öğesinin çiftine bir referanstır. Dolayısıyla, haritanın öğelerinin yalnızca değerlerini döndürmek istiyorsak, basitçe kv.second döndürebiliriz:

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

Sanırım bu herhangi bir şüpheyi açıklıyor.


3
Merhaba, kodunuzu anlamanıza yardımcı olduğu için kodla birlikte biraz açıklama ekleyin. Yalnızca kod yanıtları hoş karşılanmaz.
Bhargav Rao

1
Evet! Bu kod parçacığı soruyu çözebilir, bir açıklama da dahil olmak üzere yayınınızın kalitesini iyileştirmeye gerçekten yardımcı olur. Gelecekte okuyucular için soruyu yanıtlayacağınızı ve bu kişilerin kod önerinizin nedenlerini bilmeyebileceklerini unutmayın.
J. Chomel

Bunun yalnızca C ++ 14'ten başlayarak çalıştığını düşünüyorum, çünkü bundan önceki lambda'larda auto desteklenmiyor. Açık işlev imzası yine de çalışır.
turoni

19

Lambdaları kullanarak aşağıdakileri gerçekleştirebilirsiniz:

{
   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 ()) yapmanız gerektiğini sanmıyorum çünkü yeni öğeleri geri ittikçe v büyüyecek.
Dragan Ostojić

11
@ DraganOstojić .reserve () yalnızca bir yeniden tahsise neden olur. Elemanların sayısına bağlı olarak, .push_back () aynı boyuta ulaşmak için birden çok tahsis gerçekleştirebilir.
mskfisher

8

İşte yapacağım şey.
Ayrıca select2nd'nin yapımını kolaylaştırmak için bir şablon işlevi kullanırdım.

#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
İyi bir. Ve make_select2nd neden stl'de değil?
Mykola Golubyev

select2nd, SGI sürümündeki STL'nin bir uzantısıdır (bu nedenle resmi değildir). İşlev şablonlarını yardımcı program olarak eklemek artık ikinci bir doğadır (ilham için bkz. Make_pair <> ()).
Martin York

2

Bir yol, functor kullanmaktır:

 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 değişkeniyle uğraşmam. sadece for_each içinde bir geçici oluşturun. std :: for_each (myMap.begin (), myMap.end (), CopyMapToVec <std :: string, int> (myVector));
Martin York

'dönüşümü' tercih edin, çünkü yaptığınız şey bu: oldukça basit bir işlev kullanarak bir haritayı bir vektöre dönüştürmek.
xtofl

2

Neden olmasın:

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

kullanım:

auto vec = MapValuesAsVector (anymap);


Ben senin düşünüyorum vec iki katı büyüklüğünde olacak haritası
dyomas

teşekkürler dyomas, işlevi yeniden boyutlandırmak yerine yedek yapacak şekilde güncelledim ve şimdi düzgün çalışıyor
Jan Wilmans

2

Olması gerektiğini düşündüm

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

2

STL algoritmasındaki dönüştürme işlevini kullanmalıyız, dönüştürme işlevinin son parametresi bir işlev nesnesi, işlev işaretçisi veya haritanın öğesini vektör öğesine dönüştüren bir lambda işlevi olabilir. Bu durum haritası, vektör için int türüne sahip öğeye dönüştürülmesi gereken tür çiftine sahip öğelere sahiptir. İşte lambda fonksiyonunu kullandığım çözüm:

#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

Hiç kimse en bariz çözümden bahsetmedi , std :: vector yapıcısını kullanın.

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
Bunun nedeni, çözümünüzün soruya uymamasıdır. Vektör yalnızca değerlerden oluşmalıdır.
ypnos
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.