두 개 이상의 컨테이너를 동시에 반복하는 가장 좋은 방법은 무엇입니까?


114

C ++ 11은 컨테이너를 반복하는 여러 방법을 제공합니다. 예를 들면 :

범위 기반 루프

for(auto c : container) fun(c)

std :: for_each

for_each(container.begin(),container.end(),fun)

그러나 같은 크기의 두 개 이상의 컨테이너를 반복하여 다음과 같은 작업을 수행하는 데 권장되는 방법은 무엇입니까?

for(unsigned i = 0; i < containerA.size(); ++i) {
  containerA[i] = containerB[i];
}

1
어떤 약 transform에 존재 #include <algorithm>?
Ankit Acharya 2015 년

할당 루프 정보 : 둘 다 벡터이거나 유사한 containerA = containerB;경우 루프 대신 사용 하십시오.
emlai 2011


답변:


53

파티에 늦게. 그러나 나는 인덱스를 반복 할 것입니다. 그러나 고전적인 for루프가 아니라 for인덱스에 대한 범위 기반 루프를 사용합니다.

for(unsigned i : indices(containerA)) {
    containerA[i] = containerB[i];
}

indices인덱스에 대해 (게으른 평가 된) 범위를 반환하는 간단한 래퍼 함수입니다. 구현은 간단하지만 여기에 게시하기에는 너무 길기 때문에 GitHub에서 구현을 찾을 수 있습니다 .

이 코드는 수동 클래식 루프 를 사용하는 것만 큼 ​​효율적for 입니다.

이 패턴이 데이터에서 자주 발생하는 경우 zip두 개의 시퀀스를 구성하고 쌍을 이룬 요소에 해당하는 튜플 범위를 생성하는 다른 패턴을 사용 하는 것이 좋습니다.

for (auto& [a, b] : zip(containerA, containerB)) {
    a = b;
}

의 구현은 zip독자를위한 연습으로 남겨졌지만 indices.

(C ++ 17 이전에는 대신 다음을 작성해야합니다.)

for (auto items&& : zip(containerA, containerB))
    get<0>(items) = get<1>(items);

2
counting_range를 높이는 것과 비교하여 인덱스 구현의 이점이 있습니까? 간단히 사용할 수 있습니다boost::counting_range(size_t(0), containerA.size())
SebastianK

3
@SebastianK이 경우의 가장 큰 차이점은 구문입니다.이 경우에는 객관적으로 사용하는 것이 더 좋습니다. 또한 단계 크기를 지정할 수 있습니다. 링크 된 Github 페이지, 특히 README 파일을 참조하십시오.
Konrad Rudolph 2014

귀하의 아이디어는 매우 좋으며 나는 그것을 본 후에 만 ​​counting_range를 사용했습니다. clear upvote :) 그러나 이것이 (재) 구현에 추가적인 가치를 제공하는지 궁금합니다. 예 : 성능 관련. 물론 더 좋은 구문이지만 동의하지만이 단점을 보완하기 위해 간단한 생성기 함수를 작성하는 것으로 충분합니다.
SebastianK

@SebastianK 나는 코드를 작성할 때 라이브러리를 사용하지 않고 고립되어 살기에 충분히 간단하다고 생각했다는 것을 인정합니다. 이제는 아마도 Boost.Range 주위의 래퍼로 작성할 것입니다. 즉, 내 라이브러리의 성능은 이미 최적입니다. 이것이 의미하는 바는 내 indices구현 을 사용하면 수동 루프 를 사용 하는 것과 동일한 컴파일러 출력이 생성 된다는 것 for입니다. 오버 헤드가 전혀 없습니다.
Konrad Rudolph 2014

어쨌든 부스트를 사용하기 때문에 제 경우에는 더 간단 할 것입니다. 저는 이미 부스트 범위에 대해이 래퍼를 작성했습니다. 코드 한 줄로 된 함수 만 있으면됩니다. 그러나 부스트 범위의 성능도 최적이면 관심이 있습니다.
SebastianK

38

구체적인 예를 들어,

std::copy_n(contB.begin(), contA.size(), contA.begin())

보다 일반적인 경우 zip_iterator에는 범위 기반 for 루프에서 사용할 수 있도록하는 작은 함수와 함께 Boost.Iterator 's를 사용할 수 있습니다 . 대부분의 경우 다음과 같이 작동합니다.

template<class... Conts>
auto zip_range(Conts&... conts)
  -> decltype(boost::make_iterator_range(
  boost::make_zip_iterator(boost::make_tuple(conts.begin()...)),
  boost::make_zip_iterator(boost::make_tuple(conts.end()...))))
{
  return {boost::make_zip_iterator(boost::make_tuple(conts.begin()...)),
          boost::make_zip_iterator(boost::make_tuple(conts.end()...))};
}

// ...
for(auto&& t : zip_range(contA, contB))
  std::cout << t.get<0>() << " : " << t.get<1>() << "\n";

라이브 예.

그러나 본격적인들이 generic을 위해, 당신은 아마 더 같은 것을 원하는 멤버가없는 배열과 사용자 정의 형식에 대해 올바르게 작동합니다, begin()/ end()그러나 않는begin/ end자신의 네임 스페이스 기능을. 또한 사용자가 기능을 const통해 구체적으로 액세스 할 수 있습니다 zip_c....

그리고 나처럼 멋진 오류 메시지를 옹호 하는 사람이라면 임시 컨테이너가 함수에 전달되었는지 확인하고 그렇다면 멋진 오류 메시지를 출력하는 this를 원할 것입니다 zip_....


1
감사! 그러나 한 가지 질문은 왜 auto &&를 사용합니까? &&를 의미합니까?
memecs

@memecs : 나는 통해 읽어 보시기 바랍니다 이 질문에 aswell로, 나의이 대답 좀 공제 및 참조가 이루어집니다 무너 방법을 설명합니다. 참고 auto정확히 템플릿 매개 변수와 동일하게 작동하고, T&&템플릿에, 그래서 첫 번째 링크에 설명 된대로 보편적 인 기준입니다 auto&& v = 42으로 추론 될 것입니다 int&&auto&& w = v;다음과 같이 추론된다 int&. 이를 통해 lvalue와 rvalue를 일치시키고 둘 다 사본을 만들지 않고도 변경 가능하도록 할 수 있습니다.
Xeo

@Xeo :하지만 foreach 루프에서 auto && over auto &의 장점은 무엇입니까?
Viktor Sehr 2013 년

@ViktorSehr : .NET에서 생성 된 것과 같은 임시 요소에 바인딩 할 수 있습니다 zip_range.
Xeo

23
@Xeo 예제에 대한 모든 링크가 끊어졌습니다.
kynan

34

왜 아무도 이것을 언급하지 않았는지 궁금합니다.

auto ItA = VectorA.begin();
auto ItB = VectorB.begin();

while(ItA != VectorA.end() || ItB != VectorB.end())
{
    if(ItA != VectorA.end())
    {
        ++ItA;
    }
    if(ItB != VectorB.end())
    {
        ++ItB;
    }
}

추신 : 컨테이너 크기가 일치하지 않으면 if 문 안에 코드를 넣어야합니다.


9

헤더에 제공된대로 여러 컨테이너를 사용하여 특정 작업 을 수행하는 방법 에는 여러 가지algorithm있습니다. 예를 들어, 여러분이 제공 한 예제 std::copy에서 명시적인 for 루프 대신 사용할 수 있습니다 .

반면에 일반적인 for 루프 이외의 여러 컨테이너를 일반적으로 반복하는 기본 제공 방법은 없습니다. 있기 때문에 이것은 놀라운 일이 아니다 많은 반복하는 방법은. 생각해보십시오. 한 컨테이너를 한 단계, 한 컨테이너를 다른 단계로 반복 할 수 있습니다. 또는 한 용기를 끝까지 통과 한 다음 다른 용기의 끝까지 들어가는 동안 삽입을 시작합니다. 또는 다른 용기를 완전히 통과 한 다음 다시 시작할 때마다 첫 번째 용기의 한 단계; 또는 다른 패턴; 또는 한 번에 두 개 이상의 용기; 기타 ...

그러나 가장 짧은 컨테이너 길이까지만 두 개의 컨테이너를 반복하는 고유 한 "for_each"스타일 함수 를 만들고 싶다면 다음과 같이 할 수 있습니다.

template <typename Container1, typename Container2>
void custom_for_each(
  Container1 &c1,
  Container2 &c2,
  std::function<void(Container1::iterator &it1, Container2::iterator &it2)> f)
{
  Container1::iterator begin1 = c1.begin();
  Container2::iterator begin2 = c2.begin();
  Container1::iterator end1 = c1.end();
  Container2::iterator end2 = c2.end();
  Container1::iterator i1;
  Container1::iterator i2;
  for (i1 = begin1, i2 = begin2; (i1 != end1) && (i2 != end2); ++it1, ++i2) {
    f(i1, i2);
  }
}

분명히 비슷한 방식으로 원하는 모든 종류의 반복 전략을 만들 수 있습니다.

물론 내부 for 루프를 직접 수행하는 것이 이와 같은 사용자 지정 함수를 작성하는 것보다 더 쉽다고 주장 할 수 있습니다. 그리고 한두 번만 수행 할 경우 옳습니다. 그러나 좋은 점은 이것이 매우 재사용이 가능하다는 것입니다. =)


루프 전에 반복자를 선언해야하는 것 같습니까? 나는 이것을 시도했다 : for (Container1::iterator i1 = c1.begin(), Container2::iterator i2 = c2.begin(); (i1 != end1) && (i2 != end2); ++it1, ++i2)그러나 컴파일러가 소리 쳤다. 왜 이것이 유효하지 않은지 설명 할 수 있습니까?
David Doria

@DavidDoria for 루프의 첫 번째 부분은 단일 문입니다. 동일한 문에서 유형이 다른 두 변수를 선언 할 수 없습니다. for (int x = 0, y = 0; ...작동하지만 작동 for (int x = 0, double y = 0; ...)하지 않는 이유를 생각해보십시오 .
wjl

1
..하지만 std :: pair <Container1 :: iterator, Container2 :: iterator> its = {c1.begin (), c2.begin ()};
lorro

1
주목할 또 다른 점은 이것이 C ++ 14로 쉽게 가변 화 될 수 있다는 것입니다typename...
wjl

8

2 개의 컨테이너에서만 동시에 반복해야하는 경우 부스트 범위 라이브러리에 표준 for_each 알고리즘의 확장 버전이 있습니다. 예 :

#include <vector>
#include <boost/assign/list_of.hpp>
#include <boost/bind.hpp>
#include <boost/range/algorithm_ext/for_each.hpp>

void foo(int a, int& b)
{
    b = a + 1;
}

int main()
{
    std::vector<int> contA = boost::assign::list_of(4)(3)(5)(2);
    std::vector<int> contB(contA.size(), 0);

    boost::for_each(contA, contB, boost::bind(&foo, _1, _2));
    // contB will be now 5,4,6,3
    //...
    return 0;
}

하나의 알고리즘에서 2 개 이상의 컨테이너를 처리해야하는 경우 zip을 사용해야합니다.


훌륭한! 어떻게 찾았 어? 어디에도 문서화되지 않은 것 같습니다.
Mikhail

4

또 다른 솔루션은 람다에서 다른 컨테이너의 반복기 참조를 캡처하고 그에 대해 사후 증분 연산자를 사용하는 것입니다. 예를 들어 간단한 사본은 다음과 같습니다.

vector<double> a{1, 2, 3};
vector<double> b(3);

auto ita = a.begin();
for_each(b.begin(), b.end(), [&ita](auto &itb) { itb = *ita++; })

람다 내부에서 무엇이든 할 수 있으며 ita증가시킬 수 있습니다. 이것은 여러 용기 케이스로 쉽게 확장됩니다.


3

범위 라이브러리는이 기능과 기타 매우 유용한 기능을 제공합니다. 다음 예제에서는 Boost.Range 를 사용합니다 . Eric Niebler의 rangev3 는 좋은 대안이 될 것입니다.

#include <boost/range/combine.hpp>
#include <iostream>
#include <vector>
#include <list>

int main(int, const char*[])
{
    std::vector<int> const v{0,1,2,3,4};
    std::list<char> const  l{'a', 'b', 'c', 'd', 'e'};

    for(auto const& i: boost::combine(v, l))
    {
        int ti;
        char tc;
        boost::tie(ti,tc) = i;
        std::cout << '(' << ti << ',' << tc << ')' << '\n';
    }

    return 0;
}

C ++ 17은 구조화 된 바인딩을 사용하여이를 더욱 향상시킵니다.

int main(int, const char*[])
{
    std::vector<int> const v{0,1,2,3,4};
    std::list<char> const  l{'a', 'b', 'c', 'd', 'e'};

    for(auto const& [ti, tc]: boost::combine(v, l))
    {
        std::cout << '(' << ti << ',' << tc << ')' << '\n';
    }

    return 0;
}

이 프로그램은 g ++ 4.8.0으로 컴파일되지 않습니다. delme.cxx:15:25: error: no match for 'operator=' (operand types are 'std::tuple<int&, char&>' and 'const boost::tuples::cons<const int&, boost::tuples::cons<const char&, boost::tuples::null_type> >') std::tie(ti,tc) = i; ^
syam

std :: tie를 boost : tie로 변경 한 후 컴파일되었습니다.
syam

구조적 바인딩이있는 버전 (MSVC 19.13.26132.0및 Windows SDK 버전 사용 10.0.16299.0)에 대해 다음 컴파일 오류가 발생합니다 . error C2679: binary '<<': no operator found which takes a right-hand operand of type 'const boost::tuples::cons<const char &,boost::fusion::detail::build_tuple_cons<boost::fusion::single_view_iterator<Sequence,boost::mpl::int_<1>>,Last,true>::type>' (or there is no acceptable conversion)
pooya13

구조화 된 바인딩과 함께 작동하지 않습니다 boost::combine: stackoverflow.com/q/55585723/8414561
데브 널에게

2

나도 조금 늦었다. 그러나 이것을 사용할 수 있습니다 (C 스타일 가변 함수) :

template<typename T>
void foreach(std::function<void(T)> callback, int count, ...) {
    va_list args;
    va_start(args, count);

    for (int i = 0; i < count; i++) {
        std::vector<T> v = va_arg(args, std::vector<T>);
        std::for_each(v.begin(), v.end(), callback);
    }

    va_end(args);
}

foreach<int>([](const int &i) {
    // do something here
}, 6, vecA, vecB, vecC, vecD, vecE, vecF);

또는 이것 (함수 매개 변수 팩 사용) :

template<typename Func, typename T>
void foreach(Func callback, std::vector<T> &v) {
    std::for_each(v.begin(), v.end(), callback);
}

template<typename Func, typename T, typename... Args>
void foreach(Func callback, std::vector<T> &v, Args... args) {
    std::for_each(v.begin(), v.end(), callback);
    return foreach(callback, args...);
}

foreach([](const int &i){
    // do something here
}, vecA, vecB, vecC, vecD, vecE, vecF);

또는 (중괄호로 묶인 이니셜 라이저 목록 사용) :

template<typename Func, typename T>
void foreach(Func callback, std::initializer_list<std::vector<T>> list) {
    for (auto &vec : list) {
        std::for_each(vec.begin(), vec.end(), callback);
    }
}

foreach([](const int &i){
    // do something here
}, {vecA, vecB, vecC, vecD, vecE, vecF});

또는 다음과 같이 벡터를 결합 할 수 있습니다. 두 벡터를 결합하는 가장 좋은 방법은 무엇입니까? 그런 다음 큰 벡터를 반복합니다.


0

다음은 하나의 변형입니다.

template<class ... Iterator>
void increment_dummy(Iterator ... i)
    {}

template<class Function,class ... Iterator>
void for_each_combined(size_t N,Function&& fun,Iterator... iter)
    {
    while(N!=0)
        {
        fun(*iter...);
        increment_dummy(++iter...);
        --N;
        }
    }

사용 예

void arrays_mix(size_t N,const float* x,const float* y,float* z)
    {
    for_each_combined(N,[](float x,float y,float& z){z=x+y;},x,y,z);    
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.