Sao chép các giá trị bản đồ thành vectơ trong STL


85

Đang làm việc theo cách của tôi thông qua STL hiệu quả vào lúc này. Mục 5 gợi ý rằng nên sử dụng các hàm thành viên phạm vi cho các đối tác phần tử đơn lẻ của chúng. Tôi hiện muốn sao chép tất cả các giá trị trong bản đồ (tức là - tôi không cần khóa) vào một vectơ.

Cách sạch nhất để làm điều này là gì?


Nếu không cần chìa khóa, thì toàn bộ bản đồ cũng có thể không cần. Trong trường hợp này, hãy xem xét việc di chuyển các giá trị từ bản đồ sang vector như được mô tả trong câu hỏi này .
Nykodym

Câu trả lời:


61

Bạn không thể dễ dàng sử dụng một phạm vi ở đây vì trình vòng lặp bạn nhận được từ bản đồ tham chiếu đến cặp std ::, trong đó các trình vòng lặp mà bạn sẽ sử dụng để chèn vào một vectơ tham chiếu đến một đối tượng thuộc loại được lưu trữ trong vectơ, đó là (nếu bạn đang loại bỏ chìa khóa) không phải là một cặp.

Tôi thực sự không nghĩ rằng nó sạch hơn nhiều so với điều hiển nhiên:

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

mà tôi có thể sẽ viết lại dưới dạng một hàm mẫu nếu tôi định sử dụng nó nhiều lần. Cái gì đó như:

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 đã thực sự chiều chuộng tôi :-(
Gilad Naor

1
Đẹp, mẫu. Có thể cung cấp cho nó một trình lặp đầu ra thay vì một vùng chứa!
xtofl

Giải pháp của Skurmedel thậm chí còn hay hơn: sử dụng hàm 'biến đổi' với bộ chức năng ap -> p.second.
xtofl

2
Tôi là một người tin tưởng chắc chắn vào Occam's Razor - không giới thiệu các thực thể một cách liên tục. Trong trường hợp của giải pháp biến đổi, chúng ta cần một hàm phụ không cần thiết đối với giải pháp vòng lặp rõ ràng. Vì vậy, cho đến khi chúng tôi nhận được các hàm không tên, tôi sẽ gắn bó với giải pháp của mình.

3
Hãy coi chừng cách diễn giải của Occam's Razor. Cuối cùng, giới thiệu một biến không phải const mới "nó" có thể không phải là giải pháp an toàn nhất. Thuật toán STL đã được chứng minh là nhanh và mạnh trong một thời gian dài.
Vincent Robert

62

Bạn có thể sử dụng std::transformcho mục đích đó. Tôi có thể thích phiên bản Neils hơn, tùy thuộc vào những gì dễ đọc hơn.


Ví dụ của xtofl (xem nhận xét):

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

Rất chung chung, hãy nhớ ghi công cho anh ta nếu bạn thấy nó hữu ích.


tôi thậm chí còn thích hơn của Neil. Tập luyện, tập luyện!
xtofl

Tôi đề nghị sử dụng lambda cho tham số cuối cùng.
varepsilon

@varepsilon: Có lẽ là một ý tưởng hay (nếu một người đang sử dụng trình biên dịch C ++ hiện đại), nhưng tôi không còn tự tin với C ++ nữa, tôi gần như là một chàng trai C. Nếu bất cứ ai muốn cải thiện nó và nghĩ rằng họ có thể làm điều đó, xin vui lòng đi trước :)
Skurmedel

29

Câu hỏi cũ, câu trả lời mới. Với C ++ 11, chúng ta có vòng lặp for mới lạ mắt:

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

trong đó các lược đồ là a std::mapvà tên là một std::vector.

Điều này điền vào mảng (tên) với các khóa từ bản đồ (lược đồ); thay đổi s.firstthành s.secondđể nhận một mảng giá trị.


3
Nó phải làconst auto &s
Slava.

1
@Slava để làm rõ cho bất kỳ ai mới đối với phạm vi dựa trên: cách tôi đã viết nó hoạt động, tuy nhiên, phiên bản mà Slava đề xuất nhanh hơn và an toàn hơn vì tránh sao chép đối tượng trình lặp bằng cách sử dụng tham chiếu và chỉ định một const vì nó sẽ là nguy hiểm khi sửa đổi trình lặp. Cảm ơn.
Seth

4
Giải pháp ngắn nhất và sạch nhất. Và có lẽ là nhanh nhất (được kiểm tra là nhanh hơn giải pháp được chấp nhận và cũng nhanh hơn giải pháp của @ Aragornx). Thêm reserve()và bạn sẽ nhận được một hiệu suất khác. Với sự ra đời của C ++ 11, giờ đây sẽ là giải pháp được chấp nhận!
Adrian W

3
Đây không phải là names.push_back (s.second); như câu hỏi yêu cầu các giá trị, không phải các khóa trong một vectơ?
David

24

Nếu bạn đang sử dụng các thư viện tăng cường , bạn có thể sử dụng boost :: bind để truy cập giá trị thứ hai của cặp như sau:

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

Giải pháp này dựa trên một bài đăng của Michael Goldshteyn trên danh sách gửi thư tăng cường .


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

Xin lỗi vì tôi đã không thêm bất kỳ lời giải thích nào - tôi nghĩ rằng mã đó quá đơn giản nên không cần bất kỳ lời giải thích nào. Vì thế:

transform( beginInputRange, endInputRange, outputIterator, unaryOperation)

hàm này gọi unaryOperationmọi mục từ inputIteratorphạm vi ( beginInputRange- endInputRange). Giá trị của hoạt động được lưu trữ vào outputIterator.

Nếu chúng tôi muốn vận hành toàn bộ bản đồ - chúng tôi sử dụng map.begin () và map.end () làm phạm vi đầu vào của chúng tôi. Chúng tôi muốn để lưu trữ các giá trị bản đồ của chúng tôi vào vector - vì vậy chúng ta phải sử dụng back_inserter trên vector của chúng tôi: back_inserter(your_values_vector). Back_inserter là outputIterator đặc biệt đẩy các phần tử mới vào cuối tập hợp (dưới dạng paremeter) đã cho. Tham số cuối cùng là unaryOperation - nó chỉ nhận một tham số - giá trị của inputIterator. Vì vậy, chúng ta có thể sử dụng lambda : [](auto &kv) { [...] }, where & kv chỉ là một tham chiếu đến cặp của mục bản đồ. Vì vậy, nếu chúng ta chỉ muốn trả về các giá trị của các mục của bản đồ, chúng ta có thể chỉ cần trả về kv.second:

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

Tôi nghĩ rằng điều này giải thích bất kỳ nghi ngờ nào.


3
Xin chào, hãy thêm một chút giải thích cùng với mã vì nó giúp hiểu mã của bạn. Câu trả lời chỉ có mã được cau mày.
Bhargav Rao

1
Đúng! đoạn mã này có thể giải quyết câu hỏi, bao gồm một lời giải thích thực sự giúp cải thiện chất lượng bài đăng của bạn. Hãy nhớ rằng bạn đang trả lời câu hỏi cho người đọc trong tương lai và những người đó có thể không biết lý do cho đề xuất mã của bạn.
J. Chomel

Tôi nghĩ rằng điều này chỉ hoạt động bắt đầu từ C ++ 14, vì tự động không được hỗ trợ trong lambda trước đó. Chữ ký chức năng rõ ràng sẽ vẫn hoạt động.
turoni,

19

Sử dụng lambdas, người ta có thể thực hiện những điều sau:

{
   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
Tôi không nghĩ bạn cần v.reserve (m.size ()) vì v sẽ phát triển khi bạn push_back các phần tử mới.
Dragan Ostojić

11
@ DraganOstojić .reserve () chỉ gây ra một lần tái phân bổ. Tùy thuộc vào số lượng phần tử, .push_back () có thể thực hiện nhiều phân bổ để có cùng kích thước.
mskfisher

8

Đây là những gì tôi sẽ làm.
Ngoài ra, tôi sẽ sử dụng một hàm mẫu để làm cho việc xây dựng select2nd dễ dàng hơn.

#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
Tốt. Và tại sao make_select2nd không có trong stl?
Mykola Golubyev

select2nd là phần mở rộng của STL trong phiên bản SGI (không chính thức). Việc thêm các mẫu hàm làm tiện ích chỉ là bản chất thứ hai (xem make_pair <> () để biết thêm cảm hứng).
Martin York

2

Một cách là sử dụng 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);
}

Tôi sẽ không bận tâm với biến aConverter. chỉ cần tạo tạm thời trong for_each. std :: for_each (myMap.begin (), myMap.end (), CopyMapToVec <std :: string, int> (myVector));
Martin York

thích 'biến đổi' hơn, vì đó là những gì bạn đang làm: chuyển đổi bản đồ thành vectơ bằng cách sử dụng một trình điều khiển khá đơn giản.
xtofl

2

Tại sao không:

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

sử dụng:

auto vec = MapValuesAsVector (anymap);


Tôi nghĩ vec của bạn sẽ có kích thước gấp đôi bản đồ
dyomas

nhờ dyomas, tôi đã cập nhật các chức năng để làm một dự trữ thay vì thay đổi kích thước và bây giờ nó hoạt động một cách chính xác
Jan Wilmans

2

Tôi nghĩ nó phải là

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

2

Chúng ta nên sử dụng hàm biến đổi từ thuật toán STL, tham số cuối cùng của hàm biến đổi có thể là một đối tượng hàm, con trỏ hàm hoặc một hàm lambda chuyển đổi mục của bản đồ thành mục của vector. Bản đồ trường hợp này có các mục có cặp kiểu cần chuyển đổi thành mục có kiểu int cho vectơ. Đây là giải pháp của tôi mà tôi sử dụng hàm lambda:

#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

Ngạc nhiên là không ai đề cập đến giải pháp rõ ràng nhất , sử dụng hàm tạo vectơ std ::.

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
Đó là bởi vì giải pháp của bạn không phù hợp với câu hỏi. Vectơ chỉ nên bao gồm các giá trị.
ypnos
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.