Lặp lại các khóa trong bản đồ C ++


122

Có cách nào để lặp lại các khóa, không phải các cặp của bản đồ C ++ không?


Ý tưởng trong việc đưa một trình vòng lặp đến các giá trị là sử dụng nó trong các thuật toán STL, ví dụ, giao điểm của các khóa của hai bản đồ. Giải pháp liên quan đến Boost không cho phép điều này, bởi vì nó sẽ tạo ra một trình lặp Boost. Câu trả lời tệ nhất nhận được nhiều phiếu bầu nhất!

Câu trả lời:


70

Nếu bạn thực sự cần ẩn giá trị mà trình vòng lặp "thực" trả về (ví dụ: vì bạn muốn sử dụng trình vòng lặp khóa của mình với các thuật toán tiêu chuẩn, để chúng hoạt động trên các phím thay vì các cặp), thì hãy xem Boost's biến_ngoài .

[Mẹo: khi xem tài liệu về Boost cho một lớp mới, trước tiên hãy đọc "ví dụ" ở cuối. Sau đó, bạn có cơ hội thể thao để tìm hiểu xem phần còn lại của nó đang nói về cái quái gì :-)]


2
Với tăng bạn có thể viết BOOST_FOREACH (const key_t chủ chốt, the_map | boost :: adapter :: map_keys) {do something} boost.org/doc/libs/1_50_0/libs/range/doc/html/range/reference/...
rodrigob

120

bản đồ là vùng chứa kết hợp. Do đó, trình lặp là một cặp khóa, val. NẾU bạn chỉ cần khóa, bạn có thể bỏ qua phần giá trị từ cặp.

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

CHỈNH SỬA:: Trong trường hợp bạn chỉ muốn để lộ các phím ra bên ngoài thì bạn có thể chuyển đổi bản đồ thành vectơ hoặc các phím và hiển thị.


Nhưng sau đó sẽ thực sự là một ý tưởng tồi nếu để lộ trình lặp của vectơ ra bên ngoài.
Naveen 18/09/09

Không để lộ trình lặp. Chỉ cần cung cấp các khóa trong vector
aJ.

5
Bạn có thể muốn làm điều này thay vì: const Key& k(iter->first);
strickli

17
Hai điều, đây trả lời câu hỏi của OP với chính xác câu trả lời anh đã biết và đã không tìm kiếm, thứ hai phương pháp này sẽ không giúp bạn nếu bạn muốn làm một cái gì đó như: std::vector<Key> v(myMap.begin(), myMap.end()).
Andreas Magnusson

Không chuyển đổi các khóa thành vectơ. Tạo một vectơ mới đánh bại mục đích của việc lặp, được cho là nhanh chóng và không phân bổ bất cứ thứ gì. Ngoài ra, nó sẽ chậm đối với các bộ lớn.
Kevin Chen

85

Với C ++ 11, cú pháp lặp rất đơn giản. Bạn vẫn lặp lại các cặp, nhưng chỉ cần truy cập vào khóa là dễ dàng.

#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
Câu hỏi ban đầu nói rõ ràng là "không phải các cặp".
Ian

41

Không có Boost

Bạn có thể làm điều này bằng cách mở rộng trình lặp STL cho bản đồ đó. Ví dụ: ánh xạ chuỗi thành int:

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

Bạn cũng có thể thực hiện tiện ích mở rộng này trong một mẫu để có giải pháp chung hơn.

Bạn sử dụng trình vòng lặp của mình giống hệt như bạn sử dụng trình vòng lặp danh sách, ngoại trừ việc bạn đang lặp lại trên bản đồ 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: Cuối cùng, ai đó đã đọc bit "không phải cặp"! Chúc mừng, điều này đã giúp tôi tiết kiệm thời gian tìm hiểu thông số kỹ thuật!
Mark K Cowan

1
Và bên dưới giải pháp được tạo mẫu, và tôi đã thêm trình lặp Giá trị.
degski

đã liên kết câu hỏi của bạn từ câu hỏi của tôi.
Ian

template<typename C> class key_iterator : public C::iterator, v.v.
Gabriel

38

Với C ++ 17, bạn có thể sử dụng liên kết có cấu trúc bên trong vòng lặp for dựa trên phạm vi (điều chỉnh câu trả lời của John H. cho phù hợp):

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

Thật không may, tiêu chuẩn C ++ 17 yêu cầu bạn khai báo valuebiến, ngay cả khi bạn không sử dụng nó ( std::ignorevì một biến sẽ sử dụng cho std::tie(..)không hoạt động, hãy xem thảo luận này ).

Do đó, một số trình biên dịch có thể cảnh báo bạn về valuebiến không sử dụng ! Cảnh báo thời gian biên dịch liên quan đến các biến không sử dụng là điều không nên đối với bất kỳ mã sản xuất nào trong tâm trí tôi. Vì vậy, điều này có thể không áp dụng cho một số phiên bản trình biên dịch nhất định.


bạn không thể gán nó cho std :: ignore về nguyên tắc? Điều đó thực sự làm tổn hại đến hiệu quả trong mã đã biên dịch hay nó thực sự không đánh giá được gì? (Tôi không có nghĩa là trong các ràng buộc mà là một hành động trong vòng lặp)
KotoroShinoto

Kể từ C ++ 17, bạn cũng có thể sử dụng [[could_unused]]. Điều này ngăn chặn cảnh báo. Như thế này:for ([[maybe_unused]] const auto &[key, v_not_used] : my_map) { use(key); }
arhuaco

15

Dưới đây là giải pháp mẫu tổng quát hơn mà Ian đã giới thiệu ...

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

Tất cả các khoản tín dụng được chuyển cho Ian ... Cảm ơn Ian.


11

Bạn đang tìm kiếm map_keys , với nó, bạn có thể viết những thứ như

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

Đây là một ví dụ về cách thực hiện bằng cách sử dụng biến đổi của 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

Khi không cần rõ ràng beginendcần thiết, tức là đối với vòng lặp phạm vi, vòng lặp qua các khóa (ví dụ đầu tiên) hoặc các giá trị (ví dụ thứ hai) có thể nhận được với

#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
nên ở std
Mordachai

3

Bạn muốn làm điều này?

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

Vâng, tôi biết, vấn đề là tôi có một lớp A {public: // tôi muốn hiển thị một trình lặp trên các khóa của bản đồ riêng tư ở đây private: map <>};
Bogdan Balan

Trong trường hợp đó, tôi nghĩ bạn có thể tạo danh sách std :: bằng cách sử dụng std :: trasnform và chỉ chọn các khóa từ bản đồ. Sau đó, bạn có thể hiển thị trình vòng lặp danh sách vì việc chèn thêm phần tử vào danh sách sẽ không làm mất hiệu lực của các trình vòng lặp hiện có.
Naveen 18/09/09

3

Nếu bạn cần một trình vòng lặp chỉ trả về các khóa bạn cần để bọc trình vòng lặp của bản đồ trong lớp của riêng bạn cung cấp giao diện mong muốn. Bạn có thể khai báo một lớp trình lặp mới từ đầu như ở đây , sử dụng các cấu trúc trình trợ giúp hiện có. Câu trả lời này cho thấy cách sử dụng Boost transform_iteratorđể bọc trình vòng lặp trong một trình chỉ trả về các giá trị / khóa.


2

Bạn có thể

  • tạo một lớp trình lặp tùy chỉnh, tổng hợp std::map<K,V>::iterator
  • sử dụng std::transformcủa bạn map.begin()để map.end() có một boost::bind( &pair::second, _1 )functor
  • chỉ bỏ qua ->secondthành viên trong khi lặp với một forvòng lặp.

2

Câu trả lời này giống như rodrigob ngoại trừ không có BOOST_FOREACH. Bạn có thể sử dụng phạm vi của c ++ để thay thế.

#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

Nếu không có Boost, bạn có thể làm như thế này. Sẽ rất tuyệt nếu bạn có thể viết toán tử ép kiểu thay vì getKeyIterator (), nhưng tôi không thể biên dịch nó.

#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

Đối với hậu thế, và vì tôi đang cố gắng tìm cách tạo ra một phạm vi, một giải pháp thay thế là sử dụng boost :: adapter :: biến đổi

Đây là một ví dụ nhỏ:

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

Nếu bạn muốn lặp lại các giá trị, hãy sử dụng t.secondtrong lambda.


0

Rất nhiều câu trả lời hay ở đây, dưới đây là một cách tiếp cận sử dụng một vài trong số chúng cho phép bạn viết điều này:

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

Nếu đó là những gì bạn luôn muốn, thì đây là mã cho 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

Tôi đã chấp nhận câu trả lời của Ian để làm việc với tất cả các loại bản đồ và đã sửa trả lại một tham chiếu cho 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

Tôi biết điều này không trả lời được câu hỏi của bạn, nhưng một tùy chọn bạn có thể muốn xem xét là chỉ có hai vectơ có cùng chỉ mục là thông tin "được liên kết" ..

Vì vậy, trong ..

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

std::vector<int> vNameCount;

nếu bạn muốn đếm tên theo tên, bạn chỉ cần thực hiện nhanh vòng lặp for qua vName.size () và khi bạn tìm thấy đó là chỉ mục cho vNameCount mà bạn đang tìm kiếm.

Chắc chắn rằng điều này có thể không cung cấp cho bạn tất cả các chức năng của bản đồ và tùy thuộc vào có thể tốt hơn hoặc có thể tốt hơn, nhưng có thể dễ dàng hơn nếu bạn không biết các phím và không nên thêm quá nhiều xử lý.

Chỉ cần nhớ khi bạn thêm / xóa từ cái này, bạn phải làm điều đó từ cái kia nếu không mọi thứ sẽ trở nên điên rồ heh: P

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.