2012-07-04 42 views
21

thể trùng lặp:
Find position of element in C++11 range-based for loop?Python giống như vòng lặp liệt kê trong C++

Tôi có một vector và tôi muốn lặp nó và, đồng thời, có thể truy cập các chỉ mục cho từng phần tử riêng lẻ (tôi cần chuyển cả phần tử và chỉ mục của nó đến một hàm). Tôi đã xem xét hai giải pháp sau:

std::vector<int> v = { 10, 20, 30 }; 

// Solution 1 
for (std::vector<int>::size_type idx = 0; idx < v.size(); ++idx) 
    foo(v[idx], idx); 

// Solution 2 
for (auto it = v.begin(); it != v.end(); ++it) 
    foo(*it, it - v.begin()); 

Tôi đã tự hỏi liệu có thể có giải pháp nhỏ gọn hơn hay không. Một cái gì đó tương tự như của enumerate của Python. Đây là gần nhất mà tôi đã sử dụng vòng lặp C++ 11, nhưng phải xác định chỉ mục bên ngoài vòng lặp trong phạm vi riêng chắc chắn có vẻ giống như một giải pháp tồi tệ hơn 1 hoặc 2:

{ 
    int idx = 0; 
    for (auto& elem : v) 
     foo(elem, idx++); 
} 

Có cách nào (có thể sử dụng Boost) để đơn giản hóa ví dụ mới nhất theo cách mà chỉ mục được tự chứa trong vòng lặp?

+5

Tại sao đơn giản hóa những điều đơn giản? :-) – Kos

+0

Bạn sẽ phải tạo một hàm/đối tượng giống như máy phát điện trả về std :: pair và sử dụng trường đầu tiên và thứ hai của cặp. Bạn có thể sử dụng macro để thực hiện thủ thuật, nhưng không có cách nào tiện dụng và thanh lịch để sử dụng cú pháp giống Python trong C++. Giải pháp thứ hai của bạn có lẽ là điều tốt nhất để làm. – Morwenn

+0

@Kos Tôi khá ổn với giải pháp 2. Chỉ cần tò mò nếu thậm chí còn có một cách đơn giản hơn :) – betabandido

Trả lời

10

Như @Kos nói, đây là một điều đơn giản như vậy mà tôi không thực sự thấy cần phải đơn giản hóa nó hơn nữa và sẽ đích thân chỉ dính vào các truyền thống cho vòng lặp với các chỉ số, ngoại trừ việc tôi muốn mương std::vector<T>::size_type và chỉ cần sử dụng std::size_t:

for(std::size_t i = 0; i < v.size(); ++i) 
    foo(v[i], i); 

tôi không quá quan tâm về giải pháp 2. Nó đòi hỏi (kinda ẩn) lặp truy cập ngẫu nhiên mà sẽ không cho phép bạn dễ dàng trao đổi các container, mà là một trong những kẻ mạnh điểm của vòng lặp. Nếu bạn muốn sử dụng lặp và làm cho nó chung chung (và có thể phải chịu một buổi biểu diễn hit khi vòng lặp là không truy cập ngẫu nhiên), tôi khuyên bạn nên sử dụng std::distance:

for(auto it(v.begin()); it != v.end(); ++it) 
    foo(*it, std::distance(it, v.begin()); 
+3

Cho rằng bất kỳ nỗ lực để có được bất cứ nơi nào gần với liệt kê của Python dường như kết thúc trong một bloat mã lớn, tôi nghĩ rằng nó là tốt hơn để chỉ cần sử dụng bất kỳ của hai giải pháp. – betabandido

1

Một cách là quấn vòng lặp theo chức năng của riêng bạn.

#include <iostream> 
#include <vector> 
#include <string> 

template<typename T, typename F> 
void mapWithIndex(std::vector<T> vec, F fun) { 
    for(int i = 0; i < vec.size(); i++) 
     fun(vec[i], i); 
} 

int main() { 
    std::vector<std::string> vec = {"hello", "cup", "of", "tea"}; 
    mapWithIndex(vec, [](std::string s, int i){ 
     std::cout << i << " " << s << '\n'; 
    }); 
} 
+1

IMO điều này chỉ làm phức tạp thêm những điều ... – SingerOfTheFall

+2

Bạn đưa ra một điểm công bằng. Thông thường, một vòng lặp đơn giản là tốt nhất. Rõ ràng OP không muốn một mặc dù. –

+1

Tôi thực sự muốn đơn giản hơn nữa mã (nếu có thể, tất nhiên). 'cho idx, elem trong liệt kê (v): foo (idx, elem)' dường như với tôi đơn giản hơn bất kỳ giải pháp nào khác được đăng trong câu hỏi của tôi hoặc trong câu trả lời. Nhưng, tất nhiên, đó là một giải pháp Python, và tôi đã yêu cầu một C++. – betabandido

13

Dưới đây là một số loại giải pháp hài hước sử dụng đánh giá lười biếng. Thứ nhất, xây dựng các đối tượng phát enumerate_object:

template<typename Iterable> 
class enumerate_object 
{ 
    private: 
     Iterable _iter; 
     std::size_t _size; 
     decltype(std::begin(_iter)) _begin; 
     const decltype(std::end(_iter)) _end; 

    public: 
     enumerate_object(Iterable iter): 
      _iter(iter), 
      _size(0), 
      _begin(std::begin(iter)), 
      _end(std::end(iter)) 
     {} 

     const enumerate_object& begin() const { return *this; } 
     const enumerate_object& end() const { return *this; } 

     bool operator!=(const enumerate_object&) const 
     { 
      return _begin != _end; 
     } 

     void operator++() 
     { 
      ++_begin; 
      ++_size; 
     } 

     auto operator*() const 
      -> std::pair<std::size_t, decltype(*_begin)> 
     { 
      return { _size, *_begin }; 
     } 
}; 

Sau đó, tạo một hàm wrapper enumerate rằng sẽ suy ra những lập luận mẫu và trả lại máy phát điện:

template<typename Iterable> 
auto enumerate(Iterable&& iter) 
    -> enumerate_object<Iterable> 
{ 
    return { std::forward<Iterable>(iter) }; 
} 

Bây giờ bạn có thể sử dụng chức năng của bạn như vậy:

int main() 
{ 
    std::vector<double> vec = { 1., 2., 3., 4., 5. }; 
    for (auto&& a: enumerate(vec)) { 
     size_t index = std::get<0>(a); 
     double& value = std::get<1>(a); 

     value += index; 
    } 
} 

Triển khai ở trên chỉ là một đồ chơi: nó sẽ hoạt động với cả hai số const và không phải là const lvalue-reference s cũng như các tham chiếu rvalue, nhưng có một chi phí thực cho cái sau, mặc dù, xem xét nó sao chép toàn bộ đối tượng có thể lặp lại nhiều lần. Vấn đề này chắc chắn có thể được giải quyết với các tinh chỉnh bổ sung.

Kể từ C++ 17, tờ khai phân hủy thậm chí cho phép bạn để có cú pháp Python giống như mát để đặt tên cho chỉ số và giá trị trực tiếp trong for initializer:

int main() 
{ 
    std::vector<double> vec = { 1., 2., 3., 4., 5. }; 
    for (auto&& [index, value] a: enumerate(vec)) { 
     value += index; 
    } 
} 

Tôi không có một C++ trình biên dịch 17 tuân thủ trong tầm tay để kiểm tra nó, nhưng tôi hy vọng rằng các auto&& trong phân hủy có thể suy ra indexstd::size_tvaluedouble&.

+0

Mã của bạn sẽ tăng lên khủng khiếp nếu bạn chuyển một tạm thời sang 'liệt kê'. – Xeo

+0

Trình biên dịch nào bạn đang sử dụng? Nó không biên dịch với g ++ 4.6 hoặc 4.7. – betabandido

+0

@Xeo Chẳng vui sao? Có lẽ bạn đúng và tôi không thực sự thấy cách tạo một phiên bản an toàn hơn. Dù sao, sử dụng chức năng đó thậm chí không tiện dụng như giải pháp cũ đơn giản. – Morwenn

Các vấn đề liên quan