คัดลอกค่าแผนที่เป็นเวกเตอร์ใน STL


86

กำลังดำเนินการผ่านทาง STL ที่มีประสิทธิภาพในขณะนี้ รายการที่ 5 แนะนำว่าโดยปกติแล้วควรใช้ฟังก์ชัน range member กับคู่ขององค์ประกอบเดียว ขณะนี้ฉันต้องการคัดลอกค่าทั้งหมดในแผนที่ (เช่น - ฉันไม่ต้องการกุญแจ) ไปยังเวกเตอร์

วิธีที่สะอาดที่สุดในการทำเช่นนี้คืออะไร?


หากไม่จำเป็นต้องใช้กุญแจก็อาจไม่จำเป็นต้องใช้แผนที่ทั้งหมดเช่นกัน ในกรณีเช่นนี้ให้พิจารณาย้ายค่าจากแผนที่ไปยังเวกเตอร์ตามที่อธิบายไว้ในคำถามนี้
Nykodym

คำตอบ:


61

คุณไม่สามารถใช้ช่วงที่นี่ได้อย่างง่ายดายเนื่องจากตัววนซ้ำที่คุณได้รับจากแผนที่หมายถึงคู่ std :: โดยที่ตัววนซ้ำที่คุณจะใช้ในการแทรกลงในเวกเตอร์หมายถึงวัตถุประเภทที่เก็บไว้ในเวกเตอร์ซึ่งก็คือ (หากคุณกำลังทิ้งคีย์) ไม่ใช่คู่

ฉันไม่คิดว่ามันจะสะอาดกว่าที่เห็นได้ชัด:

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

83
Python ทำให้ฉันเสียจริงๆ :-(
Gilad Naor

1
ดีแม่แบบ อาจให้มันเป็นตัววนรอบเอาต์พุตแทนคอนเทนเนอร์!
xtofl

โซลูชันของ Skurmedel นั้นดีกว่า: ใช้ฟังก์ชัน 'transform' กับ ap -> p.second functor
xtofl

2
ฉันเชื่อมั่นใน Occam's Razor - อย่าแนะนำเอนทิตีโดยไม่จำเป็น ในกรณีของโซลูชันการแปลงเราต้องการฟังก์ชันย่อยซึ่งไม่จำเป็นสำหรับโซลูชันลูปแบบชัดแจ้ง ดังนั้นจนกว่าเราจะได้ฟังก์ชั่นนิรนามฉันจะใช้วิธีแก้ปัญหาของฉัน

3
ระวังการตีความของ Occam's Razor การแนะนำตัวแปรใหม่ที่ไม่ใช่ const "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

ฉันขอแนะนำให้ใช้แลมด้าสำหรับพารามิเตอร์สุดท้าย
varepsilon

@varepsilon: น่าจะเป็นความคิดที่ดี (ถ้ามีคอมไพเลอร์ C ++ สมัยใหม่) แต่ฉันไม่มั่นใจกับ C ++ อีกต่อไปฉันเป็นเพื่อน C ในทุกวันนี้ ถ้าใครอยากปรับปรุงและคิดว่าทำได้ก็เชิญได้เลย :)
Skurmedel

30

คำถามเก่าคำตอบใหม่ ด้วย C ++ 11 เรามีแฟนซีใหม่สำหรับลูป:

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

โดย schemas คือ a 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

25
#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ช่วง ( beginInputRange- endInputRange) outputIteratorมูลค่าของการดำเนินการจะถูกเก็บไว้ใน

หากเราต้องการใช้งานผ่านแผนที่ทั้งหมด - เราใช้ map.begin () และ map.end () เป็นช่วงอินพุตของเรา เราต้องการที่จะเก็บค่าแผนที่ของเราเป็นเวกเตอร์ - ดังนั้นเราต้องใช้ back_inserter back_inserter(your_values_vector)เวกเตอร์ของเรา: back_inserter เป็น outputIterator พิเศษที่พุชองค์ประกอบใหม่ที่ส่วนท้ายของคอลเลกชันที่กำหนด พารามิเตอร์สุดท้ายคือ unaryOperation - ใช้เพียงพารามิเตอร์เดียว - ค่าของ inputIterator ดังนั้นเราจึงสามารถใช้ lambda: [](auto &kv) { [...] }โดยที่ & kv เป็นเพียงการอ้างอิงคู่ของรายการแผนที่ ดังนั้นหากเราต้องการส่งคืนเฉพาะค่าของรายการในแผนที่เราสามารถคืนค่า kv วินาที:

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

ฉันคิดว่านี่เป็นการอธิบายข้อสงสัยใด ๆ


3
สวัสดีโปรดเพิ่มคำอธิบายเล็กน้อยพร้อมกับรหัสเพื่อช่วยให้เข้าใจรหัสของคุณ รหัสคำตอบเท่านั้นที่ขมวดคิ้ว
Bhargav Rao

1
ใช่ ข้อมูลโค้ดนี้อาจช่วยแก้ปัญหาได้รวมถึงคำอธิบายที่ช่วยปรับปรุงคุณภาพของโพสต์ของคุณได้ โปรดจำไว้ว่าคุณกำลังตอบคำถามสำหรับผู้อ่านในอนาคตและบุคคลเหล่านั้นอาจไม่ทราบสาเหตุของการแนะนำโค้ดของคุณ
J. Chomel

ฉันคิดว่าสิ่งนี้ใช้งานได้ตั้งแต่ C ++ 14 เท่านั้นเนื่องจากไม่รองรับ auto ใน lambda ก่อนหน้านั้น ลายเซ็นฟังก์ชันที่ชัดเจนจะยังคงใช้งานได้
turoni

24

หากคุณใช้ไลบรารีบูสต์คุณสามารถใช้ 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 ในรายชื่ออีเมลที่เพิ่มขึ้น


19

การใช้ lambdas สามารถทำได้ดังต่อไปนี้:

{
   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 ()) เพราะ v จะโตขึ้นเมื่อคุณ push_back องค์ประกอบใหม่
Dragan Ostojić

11
@ DraganOstojić .reserve () ทำให้เกิดการจัดสรรใหม่เพียงครั้งเดียว ขึ้นอยู่กับจำนวนขององค์ประกอบ. push_back () อาจทำการจัดสรรหลายรายการเพื่อให้ได้ขนาดเดียวกัน
mskfisher

8

นี่คือสิ่งที่ฉันจะทำ
นอกจากนี้ฉันจะใช้ฟังก์ชัน template เพื่อทำให้การสร้าง 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 เป็นส่วนขยายของ STL ในเวอร์ชัน SGI (ไม่เป็นทางการ) การเพิ่มเทมเพลตฟังก์ชันเนื่องจากยูทิลิตี้เป็นเพียงลักษณะที่สองในตอนนี้ (ดู 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

ชอบ 'แปลงร่าง' เพราะนั่นคือสิ่งที่คุณกำลังทำ: การเปลี่ยนแผนที่เป็นเวกเตอร์โดยใช้ functor ที่ค่อนข้างตรงไปตรงมา
xtofl

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 ฉันได้อัปเดตฟังก์ชันเพื่อทำการสำรองแทนการปรับขนาดและตอนนี้มันทำงานได้อย่างถูกต้อง
Jan 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 constructor

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.