คีย์ซ้ำในแผนที่ C ++


122

มีวิธีทำซ้ำบนคีย์ไม่ใช่คู่ของแผนที่ C ++ หรือไม่?


แนวคิดในการทำให้ตัววนซ้ำไปยังค่าคือการใช้มันในอัลกอริทึม STL ตัวอย่างเช่นการตัดกันของคีย์ของสองแผนที่ โซลูชันที่เกี่ยวข้องกับ Boost ไม่อนุญาตให้ทำเช่นนี้เนื่องจากจะสร้าง Boost iterator คำตอบที่แย่ที่สุดได้รับคะแนนโหวตมากที่สุด!

คำตอบ:


70

หากคุณต้องการซ่อนค่าที่ตัววนซ้ำ "ของจริง" ส่งกลับมา (เช่นคุณต้องการใช้ตัวทำซ้ำคีย์กับอัลกอริทึมมาตรฐานเพื่อให้มันทำงานบนคีย์แทนการจับคู่) ให้ดูที่ Boost transform_iterator

[เคล็ดลับ: เมื่อดูเอกสาร Boost สำหรับคลาสใหม่โปรดอ่าน "ตัวอย่าง" ในตอนท้ายก่อน จากนั้นคุณมีโอกาสเล่นกีฬาในการค้นหาว่าในโลกที่เหลือกำลังพูดถึงอะไร :-)]


2
ด้วยการเพิ่มคุณสามารถเขียน BOOST_FOREACH (คีย์ const key_t, the_map | boost :: adapters :: map_keys) {do something} boost.org/doc/libs/1_50_0/libs/range/doc/html/range/reference/…
rodrigob

120

แผนที่เป็นคอนเทนเนอร์ที่เชื่อมโยงกัน ดังนั้นตัววนซ้ำจึงเป็นคู่ของคีย์วาล หากคุณต้องการเพียงคีย์คุณสามารถละเว้นส่วนค่าจากคู่ได้

for(std::map<Key,Val>::iterator iter = myMap.begin(); iter != myMap.end(); ++iter)
{
Key k =  iter->first;
//ignore value
//Value v = iter->second;
}

แก้ไข:ในกรณีที่คุณต้องการเปิดเผยเฉพาะคีย์ออกไปข้างนอกคุณสามารถแปลงแผนที่เป็นเวกเตอร์หรือคีย์และเปิดเผยได้


แต่มันเป็นความคิดที่ไม่ดีจริงๆที่จะให้ตัวทำซ้ำของเวกเตอร์ออกไป
Naveen

อย่าให้ตัวทำซ้ำ เพียงใส่กุญแจในเวกเตอร์
aJ

5
คุณอาจต้องการทำสิ่งนี้แทน: const Key& k(iter->first);
strickli

17
สองสิ่งนี้สามารถตอบคำถามของ OP std::vector<Key> v(myMap.begin(), myMap.end())ตรงกับคำตอบที่เขารู้อยู่แล้วและไม่ได้มองหาประการที่สองวิธีนี้จะไม่ช่วยให้คุณถ้าคุณต้องการที่จะทำสิ่งที่ชอบ:
Andreas Magnusson

อย่าแปลงคีย์เป็นเวกเตอร์ การสร้างเวกเตอร์ใหม่จะเอาชนะจุดประสงค์ของการวนซ้ำซึ่งควรจะรวดเร็วและไม่จัดสรรอะไรเลย อีกทั้งจะช้าสำหรับชุดใหญ่
Kevin Chen

85

ด้วย C ++ 11 ไวยากรณ์การวนซ้ำเป็นเรื่องง่าย คุณยังคงวนซ้ำหลายคู่ แต่การเข้าถึงคีย์นั้นง่ายมาก

#include <iostream>
#include <map>

int main()
{
    std::map<std::string, int> myMap;

    myMap["one"] = 1;
    myMap["two"] = 2;
    myMap["three"] = 3;

    for ( const auto &myPair : myMap ) {
        std::cout << myPair.first << "\n";
    }
}

29
คำถามเดิมบอกอย่างชัดเจนว่า "ไม่ใช่คู่"
Ian

41

ไม่มี Boost

คุณสามารถทำได้โดยการขยายตัววนซ้ำ STL สำหรับแผนที่นั้น ตัวอย่างเช่นการแมปสตริงกับ ints:

#include <map>
typedef map<string, int> ScoreMap;
typedef ScoreMap::iterator ScoreMapIterator;

class key_iterator : public ScoreMapIterator
{
  public:
    key_iterator() : ScoreMapIterator() {};
    key_iterator(ScoreMapIterator s) : ScoreMapIterator(s) {};
    string* operator->() { return (string* const)&(ScoreMapIterator::operator->()->first); }
    string operator*() { return ScoreMapIterator::operator*().first; }
};

คุณยังสามารถใช้ส่วนขยายนี้ในเทมเพลตเพื่อเป็นวิธีแก้ปัญหาทั่วไป

คุณสามารถใช้ iterator ของคุณเหมือนที่คุณจะใช้ iterator รายการยกเว้นคุณ iterating กว่าแผนที่ของและbegin()end()

ScoreMap m;
m["jim"] = 1000;
m["sally"] = 2000;

for (key_iterator s = m.begin(); s != m.end(); ++s)
    printf("\n key %s", s->c_str());

16
+1: ในที่สุดใครที่อ่านบิต "ไม่ใช่คู่"! ไชโยสิ่งนี้ช่วยฉันประหยัดเวลาในการขุดสเป็ค!
Mark K Cowan

1
และด้านล่างของโซลูชันเทมเพลตและฉันได้เพิ่ม Value iterator
degski

เชื่อมโยงคำถามของคุณจากฉัน
เอียน

template<typename C> class key_iterator : public C::iteratorฯลฯ
Gabriel

38

ด้วย C ++ 17คุณสามารถใช้การผูกแบบมีโครงสร้างภายในแบบอิงตามช่วงสำหรับลูป (ปรับคำตอบของ John H. ให้สอดคล้องกัน):

#include <iostream>
#include <map>

int main() {
    std::map<std::string, int> myMap;

    myMap["one"] = 1;
    myMap["two"] = 2;
    myMap["three"] = 3;

    for ( const auto &[key, value]: myMap ) {
        std::cout << key << '\n';
    }
}

น่าเสียดายที่มาตรฐาน C ++ 17 กำหนดให้คุณต้องประกาศvalueตัวแปรแม้ว่าคุณจะไม่ได้ใช้งานก็ตาม ( std::ignoreเนื่องจากจะใช้std::tie(..)ไม่ได้ผลโปรดดูการสนทนานี้ )

ดังนั้นคอมไพเลอร์บางตัวอาจเตือนคุณเกี่ยวกับvalueตัวแปรที่ไม่ได้ใช้! คำเตือนเวลาคอมไพล์เกี่ยวกับตัวแปรที่ไม่ได้ใช้เป็นสิ่งที่ไม่ควรพลาดสำหรับรหัสการผลิตใด ๆ ในความคิดของฉัน ดังนั้นสิ่งนี้อาจใช้ไม่ได้กับเวอร์ชันคอมไพเลอร์บางเวอร์ชัน


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

ตั้งแต่ C ++ 17 คุณสามารถใช้ [[might_unused]] ซึ่งจะระงับการเตือน เช่นนี้:for ([[maybe_unused]] const auto &[key, v_not_used] : my_map) { use(key); }
arhuaco

15

ด้านล่างของโซลูชันเทมเพลตทั่วไปที่เอียนอ้างถึง ...

#include <map>

template<typename Key, typename Value>
using Map = std::map<Key, Value>;

template<typename Key, typename Value>
using MapIterator = typename Map<Key, Value>::iterator;

template<typename Key, typename Value>
class MapKeyIterator : public MapIterator<Key, Value> {

public:

    MapKeyIterator ( ) : MapIterator<Key, Value> ( ) { };
    MapKeyIterator ( MapIterator<Key, Value> it_ ) : MapIterator<Key, Value> ( it_ ) { };

    Key *operator -> ( ) { return ( Key * const ) &( MapIterator<Key, Value>::operator -> ( )->first ); }
    Key operator * ( ) { return MapIterator<Key, Value>::operator * ( ).first; }
};

template<typename Key, typename Value>
class MapValueIterator : public MapIterator<Key, Value> {

public:

    MapValueIterator ( ) : MapIterator<Key, Value> ( ) { };
    MapValueIterator ( MapIterator<Key, Value> it_ ) : MapIterator<Key, Value> ( it_ ) { };

    Value *operator -> ( ) { return ( Value * const ) &( MapIterator<Key, Value>::operator -> ( )->second ); }
    Value operator * ( ) { return MapIterator<Key, Value>::operator * ( ).second; }
};

เครดิตทั้งหมดตกเป็นของเอียน ... ขอบคุณเอียน


11

คุณกำลังมองหาmap_keysซึ่งคุณสามารถเขียนสิ่งต่างๆเช่น

BOOST_FOREACH(const key_t key, the_map | boost::adaptors::map_keys)
{
  // do something with key
}

1
BOOST_FOREACH(const key_t& key, ...
strickli

5

นี่คือตัวอย่างวิธีการใช้Transform_iteratorของ Boost

#include <iostream>
#include <map>
#include <iterator>
#include "boost/iterator/transform_iterator.hpp"

using std::map;
typedef std::string Key;
typedef std::string Val;

map<Key,Val>::key_type get_key(map<Key,Val>::value_type aPair) {
  return aPair.first;
}

typedef map<Key,Val>::key_type (*get_key_t)(map<Key,Val>::value_type);
typedef map<Key,Val>::iterator map_iterator;
typedef boost::transform_iterator<get_key_t, map_iterator> mapkey_iterator;

int main() {
  map<Key,Val> m;
  m["a"]="A";
  m["b"]="B";
  m["c"]="C";

  // iterate over the map's (key,val) pairs as usual
  for(map_iterator i = m.begin(); i != m.end(); i++) {
    std::cout << i->first << " " << i->second << std::endl;
  }

  // iterate over the keys using the transformed iterators
  mapkey_iterator keybegin(m.begin(), get_key);
  mapkey_iterator keyend(m.end(), get_key);
  for(mapkey_iterator i = keybegin; i != keyend; i++) {
    std::cout << *i << std::endl;
  }
}

4

เมื่อไม่มีความชัดเจนbeginและendเป็นสิ่งจำเป็นเช่นสำหรับการวนซ้ำแบบช่วงสามารถรับการวนซ้ำบนคีย์ (ตัวอย่างแรก) หรือค่า (ตัวอย่างที่สอง) ได้ด้วย

#include <boost/range/adaptors.hpp>

map<Key, Value> m;

for (auto k : boost::adaptors::keys(m))
  cout << k << endl;

for (auto v : boost::adaptors::values(m))
  cout << v << endl;

1
ควรอยู่ในมาตรฐาน
มอร์ดาชัย

3

คุณต้องการทำสิ่งนี้หรือไม่?

std::map<type,type>::iterator iter = myMap.begin();
std::map<type,type>::iterator iter = myMap.end();
for(; iter != endIter; ++iter)
{
   type key = iter->first;  
   .....
}

ใช่ฉันรู้ปัญหาคือฉันมีคลาส A {public: // ฉันต้องการให้ตัววนซ้ำผ่านคีย์ของแผนที่ส่วนตัวที่นี่ private: map <>};
Bogdan Balan

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

3

หากคุณต้องการตัววนซ้ำที่ส่งคืนคีย์ที่คุณต้องใช้ในการรวมตัววนซ้ำของแผนที่ในคลาสของคุณเอง คุณสามารถประกาศคลาส iterator ใหม่ตั้งแต่เริ่มต้นเช่นที่นี่โดยใช้โครงสร้างตัวช่วยที่มีอยู่ คำตอบนี้แสดงวิธีใช้ Boost transform_iteratorเพื่อรวมตัววนซ้ำในอันที่ส่งคืนค่า / คีย์เท่านั้น


2

คุณทำได้

  • สร้างคลาส iterator ที่กำหนดเองโดยรวมไฟล์ std::map<K,V>::iterator
  • การใช้งานstd::transformของคุณmap.begin()จะmap.end() มีboost::bind( &pair::second, _1 )functor
  • เพียงแค่ไม่สนใจ->secondสมาชิกในขณะที่การทำซ้ำกับforวง

2

คำตอบนี้เหมือนกับของ rodrigob ยกเว้นไม่มีBOOST_FOREACH. คุณสามารถใช้ช่วงของ c ++ ตามแทนได้

#include <map>
#include <boost/range/adaptor/map.hpp>
#include <iostream>

template <typename K, typename V>
void printKeys(std::map<K,V> map){
     for(auto key : map | boost::adaptors::map_keys){
          std::cout << key << std::endl;
     }
}

0

หากไม่มี Boost คุณก็ทำได้เช่นนี้ คงจะดีถ้าคุณสามารถเขียนตัวดำเนินการแคสต์แทน getKeyIterator () ได้ แต่ฉันไม่สามารถคอมไพล์ได้

#include <map>
#include <unordered_map>


template<typename K, typename V>
class key_iterator: public std::unordered_map<K,V>::iterator {

public:

    const K &operator*() const {
        return std::unordered_map<K,V>::iterator::operator*().first;
    }

    const K *operator->() const {
        return &(**this);
    }
};

template<typename K,typename V>
key_iterator<K,V> getKeyIterator(typename std::unordered_map<K,V>::iterator &it) {
    return *static_cast<key_iterator<K,V> *>(&it);
}

int _tmain(int argc, _TCHAR* argv[])
{
    std::unordered_map<std::string, std::string> myMap;
    myMap["one"]="A";
    myMap["two"]="B";
    myMap["three"]="C";
    key_iterator<std::string, std::string> &it=getKeyIterator<std::string,std::string>(myMap.begin());
    for (; it!=myMap.end(); ++it) {
        printf("%s\n",it->c_str());
    }
}

0

สำหรับลูกหลานและเนื่องจากฉันพยายามหาวิธีสร้างช่วงทางเลือกคือใช้boost :: adapters :: transform

นี่คือตัวอย่างเล็ก ๆ :

#include <boost/range/adaptor/transformed.hpp>
#include <iostream>
#include <map>

int main(int argc, const char* argv[])
{
  std::map<int, int> m;
  m[0]  = 1;
  m[2]  = 3;
  m[42] = 0;

  auto key_range =
    boost::adaptors::transform(
      m,
      [](std::map<int, int>::value_type const& t) 
      { return t.first; }
    ); 
  for (auto&& key : key_range)
    std::cout << key << ' ';
  std::cout << '\n';
  return 0;
}

หากคุณต้องการวนซ้ำค่าต่างๆให้ใช้t.secondในแลมด้า


0

คำตอบที่ดีมากมายที่นี่ด้านล่างเป็นแนวทางโดยใช้คำตอบสองสามข้อซึ่งให้คุณเขียนสิ่งนี้:

void main()
{
    std::map<std::string, int> m { {"jim", 1000}, {"sally", 2000} };
    for (auto key : MapKeys(m))
        std::cout << key << std::endl;
}

หากนั่นคือสิ่งที่คุณต้องการมาตลอดนี่คือรหัสสำหรับ MapKeys ():

template <class MapType>
class MapKeyIterator {
public:
    class iterator {
    public:
        iterator(typename MapType::iterator it) : it(it) {}
        iterator operator++() { return ++it; }
        bool operator!=(const iterator & other) { return it != other.it; }
        typename MapType::key_type operator*() const { return it->first; }  // Return key part of map
    private:
        typename MapType::iterator it;
    };
private:
    MapType& map;
public:
    MapKeyIterator(MapType& m) : map(m) {}
    iterator begin() { return iterator(map.begin()); }
    iterator end() { return iterator(map.end()); }
};
template <class MapType>
MapKeyIterator<MapType> MapKeys(MapType& m)
{
    return MapKeyIterator<MapType>(m);
}

0

ฉันได้นำคำตอบของ Ian ไปใช้กับแผนที่ทุกประเภทและแก้ไขการอ้างอิงสำหรับ operator*

template<typename T>
class MapKeyIterator : public T
{
public:
    MapKeyIterator() : T() {}
    MapKeyIterator(T iter) : T(iter) {}
    auto* operator->()
    {
        return &(T::operator->()->first);
    }
    auto& operator*()
    {
        return T::operator*().first;
    }
};

-1

ฉันรู้ว่านี่ไม่ได้ตอบคำถามของคุณ แต่ทางเลือกหนึ่งที่คุณอาจต้องการดูก็คือการมีเวกเตอร์สองตัวที่มีดัชนีเดียวกันกับข้อมูลที่ "เชื่อมโยง" ..

ดังนั้นใน ..

std::vector<std::string> vName;

std::vector<int> vNameCount;

หากคุณต้องการนับชื่อตามชื่อคุณเพียงแค่ทำอย่างรวดเร็วสำหรับการวนซ้ำบน vName.size () และเมื่อคุณพบว่าเป็นดัชนีสำหรับ vNameCount ที่คุณกำลังมองหา

แน่นอนว่านี่อาจไม่ได้ให้ฟังก์ชันทั้งหมดของแผนที่แก่คุณและอาจจะดีกว่าหรือไม่ดีกว่า แต่มันอาจจะง่ายกว่าถ้าคุณไม่รู้คีย์และไม่ควรเพิ่มการประมวลผลมากเกินไป

จำไว้ว่าเมื่อคุณเพิ่ม / ลบจากสิ่งที่คุณต้องทำจากสิ่งอื่น ๆ ไม่เช่นนั้นสิ่งต่างๆจะบ้าไปแล้ว: P

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