programing

C ++ 11 범위 기반 for 루프에서 요소의 위치를 ​​찾으십니까?

nasanasas 2020. 11. 1. 18:13
반응형

C ++ 11 범위 기반 for 루프에서 요소의 위치를 ​​찾으십니까?


다음 코드가 있다고 가정합니다.

vector<int> list;
for(auto& elem:list) {
    int i = elem;
}

elem별도의 반복기를 유지하지 않고 벡터에서 의 위치를 ​​찾을 수 있습니까 ?


예, 할 수 있습니다.

비결은 컴포지션을 사용하는 것입니다. 컨테이너를 직접 반복하는 대신 인덱스를 사용하여 "압축"합니다.

전문 지퍼 코드 :

template <typename T>
struct iterator_extractor { typedef typename T::iterator type; };

template <typename T>
struct iterator_extractor<T const> { typedef typename T::const_iterator type; };


template <typename T>
class Indexer {
public:
    class iterator {
        typedef typename iterator_extractor<T>::type inner_iterator;

        typedef typename std::iterator_traits<inner_iterator>::reference inner_reference;
    public:
        typedef std::pair<size_t, inner_reference> reference;

        iterator(inner_iterator it): _pos(0), _it(it) {}

        reference operator*() const { return reference(_pos, *_it); }

        iterator& operator++() { ++_pos; ++_it; return *this; }
        iterator operator++(int) { iterator tmp(*this); ++*this; return tmp; }

        bool operator==(iterator const& it) const { return _it == it._it; }
        bool operator!=(iterator const& it) const { return !(*this == it); }

    private:
        size_t _pos;
        inner_iterator _it;
    };

    Indexer(T& t): _container(t) {}

    iterator begin() const { return iterator(_container.begin()); }
    iterator end() const { return iterator(_container.end()); }

private:
    T& _container;
}; // class Indexer

template <typename T>
Indexer<T> index(T& t) { return Indexer<T>(t); }

그리고 그것을 사용 :

#include <iostream>
#include <iterator>
#include <limits>
#include <vector>

// Zipper code here

int main() {
    std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9};

    for (auto p: index(v)) {
        std::cout << p.first << ": " << p.second << "\n";
    }
}

ideone 에서 볼 수 있지만 for-range 루프 지원이 없기 때문에 덜 예쁘다.

편집하다:

Boost.Range를 더 자주 확인해야한다는 것을 기억했습니다. 불행히도 zip범위는 없지만 perl : boost::adaptors::indexed. 그러나 인덱스를 가져 오려면 반복기에 대한 액세스가 필요합니다. 수치심 : x

그렇지 않으면 counting_range제네릭과 함께 zip흥미로운 일을 할 수 있다고 확신합니다 ...

이상적인 세상에서 나는 상상할 것입니다.

int main() {
    std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9};

    for (auto tuple: zip(iota(0), v)) {
        std::cout << tuple.at<0>() << ": " << tuple.at<1>() << "\n";
    }
}

zip자동으로 참조 튜플의 범위로 뷰를 생성하고 iota(0)단순히 "false"로 범위를 만드는 것을 시작에서 0그냥 카운트 무한대 방향으로 (또는 아니라, 그 유형의 최대 ...).


jrok이 맞습니다 : 범위 기반 for 루프는 그 목적으로 설계되지 않았습니다.

그러나 귀하의 경우 vector요소를 연속적으로 저장하기 때문에 포인터 산술을 사용하여 계산할 수 있습니다 (*)

vector<int> list;
for(auto& elem:list) { 
    int i = elem;
    int pos = &elem-&list[0]; // pos contains the position in the vector 

    // also a &-operator overload proof alternative (thanks to ildjarn) :
    // int pos = addressof(elem)-addressof(list[0]); 

}

그러나 이것은 코드를 난독 화하고 더 취약하게 만들기 때문에 분명히 나쁜 습관입니다 (누군가 컨테이너 유형을 변경 &하거나 연산자를 오버로드 하거나 'auto &'를 'auto'로 바꾸면 쉽게 깨집니다 . 디버깅하는 것이 행운입니다!)

참고 : C ++ 03에서는 벡터, C ++ 11 표준에서는 배열 및 문자열에 대해 연속성이 보장됩니다.


아니요, 당신은 할 수 없습니다 (적어도 노력 없이는 아닙니다). 요소의 위치가 필요한 경우 범위 기반을 사용하면 안됩니다. 가장 일반적인 경우에 대한 편의 도구 일뿐입니다. 각 요소에 대해 일부 코드를 실행합니다. 요소의 위치가 필요한 덜 일반적인 상황에서는 덜 편리한 일반 for루프 를 사용해야합니다 .


C ++ 14를 지원하는 컴파일러가있는 경우 함수 스타일로 수행 할 수 있습니다.

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

template<typename T>
void for_enum(T& container, std::function<void(int, typename T::value_type&)> op)
{
    int idx = 0;
    for(auto& value : container)
        op(idx++, value);
}

int main()
{
    std::vector<std::string> sv {"hi", "there"};
    for_enum(sv, [](auto i, auto v) {
        std::cout << i << " " << v << std::endl;
    });
}

clang 3.4 및 gcc 4.9에서 작동합니다 (4.8에서는 작동하지 않음). 둘 다 설정해야합니다 -std=c++1y. C ++ 14가 필요한 이유 auto는 람다 함수 매개 변수 때문입니다 .


@Matthieu의 답변에 따르면 언급 된 boost :: adaptors :: indexed를 사용하는 매우 우아한 솔루션이 있습니다 .

std::vector<std::string> strings{10, "Hello"};
int main(){
    strings[5] = "World";
    for(auto const& el: strings| boost::adaptors::indexed(0))
      std::cout << el.index() << ": " << el.value() << std::endl;
}

당신은 그것을 시도 할 수 있습니다

이것은 언급 된 "이상적인 세계 솔루션"과 매우 유사하게 작동하며 매우 구문이 있고 간결합니다. el이 경우 의 유형 은와 유사 boost::foobar<const std::string&, int>하므로 참조를 처리하고 복사가 수행되지 않습니다. 엄청나게 효율적입니다 : https://godbolt.org/g/e4LMnJ (코드는 자신의 카운터 변수를 유지하는 것과 동일합니다.)

완전성을 위해 대안 :

size_t i = 0;
for(auto const& el: strings) {
  std::cout << i << ": " << el << std::endl;
  ++i;
}

또는 벡터의 연속 속성을 사용합니다.

for(auto const& el: strings) {
  size_t i = &el - &strings.front();
  std::cout << i << ": " << el << std::endl;
}

첫 번째는 부스트 어댑터 버전 (최적)과 동일한 코드를 생성하고 마지막은 1 명령 더 길다 : https://godbolt.org/g/nEG8f9

참고 : 마지막 요소 만 알고 싶다면 다음을 사용할 수 있습니다.

for(auto const& el: strings) {
  bool isLast = &el == &strings.back();
  std::cout << isLast << ": " << el << std::endl;
}

이것은 모든 표준 컨테이너에서 작동하지만 auto&/ auto const&사용되어야 하지만 (위와 동일) 어쨌든 권장됩니다. 입력에 따라 이것은 매우 빠를 수도 있습니다 (특히 컴파일러가 벡터의 크기를 알고있는 경우).

일반 코드의 안전한 쪽이되도록 &fooby std::addressof(foo)대체하십시오 .


If you insist on using range based for, and to know index, it is pretty trivial to maintain index as shown below. I do not think there is a cleaner / simpler solution for range based for loops. But really why not use a standard for(;;)? That probably would make your intent and code the clearest.

vector<int> list;
int idx = 0;
for(auto& elem:list) {
    int i = elem;
    //TODO whatever made you want the idx
    ++idx;
}

There is a surprisingly simple way to do this

vector<int> list;
for(auto& elem:list) {
    int i = (&elem-&*(list.begin()));
}

where i will be your required index.

This takes advantage of the fact that C++ vectors are always contiguous.


I read from your comments that one reason you want to know the index is to know if the element is the first/last in the sequence. If so, you can do

for(auto& elem:list) {
//  loop code ...
    if(&elem == &*std::begin(list)){ ... special code for first element ... }
    if(&elem == &*std::prev(std::end(list))){ ... special code for last element ... }
//  if(&elem == &*std::rbegin(list)){... (C++14 only) special code for last element ...}
//  loop code ... 
}

EDIT: For example, this prints a container skipping a separator in the last element. Works for most containers I can imagine (including arrays), (online demo http://coliru.stacked-crooked.com/a/9bdce059abd87f91):

#include <iostream>
#include <vector>
#include <list>
#include <set>
using namespace std;

template<class Container>
void print(Container const& c){
  for(auto& x:c){
    std::cout << x; 
    if(&x != &*std::prev(std::end(c))) std::cout << ", "; // special code for last element
  }
  std::cout << std::endl;
}

int main() {
  std::vector<double> v{1.,2.,3.};
  print(v); // prints 1,2,3
  std::list<double> l{1.,2.,3.};
  print(l); // prints 1,2,3
  std::initializer_list<double> i{1.,2.,3.};
  print(i); // prints 1,2,3
  std::set<double> s{1.,2.,3.};
  print(s); // print 1,2,3
  double a[3] = {1.,2.,3.}; // works for C-arrays as well
  print(a); // print 1,2,3
}

Here's a macro-based solution that probably beats most others on simplicity, compile time, and code generation quality:

#include <iostream>

#define fori(i, ...) if(size_t i = -1) for(__VA_ARGS__) if(i++, true)

int main() {
    fori(i, auto const & x : {"hello", "world", "!"}) {
        std::cout << i << " " << x << std::endl;
    }
}

Result:

$ g++ -o enumerate enumerate.cpp -std=c++11 && ./enumerate 
0 hello
1 world
2 !

Tobias Widlund wrote a nice MIT licensed Python style header only enumerate (C++17 though):

GitHub

Blog Post

Really nice to use:

std::vector<int> my_vector {1,3,3,7};

for(auto [i, my_element] : en::enumerate(my_vector))
{
    // do stuff
}

If you want to avoid having to write an auxiliary function while having the index variable local to the loop, you can use a lambda with a mutable variable.:

int main() {
    std::vector<char> values = {'a', 'b', 'c'};
    std::for_each(begin(values), end(values), [i = size_t{}] (auto x) mutable {
        std::cout << i << ' ' << x << '\n';
        ++i;
    });
}

참고URL : https://stackoverflow.com/questions/10962290/find-position-of-element-in-c11-range-based-for-loop

반응형