Есть ли способ перебрать два контейнера, не используя два цикла for - PullRequest
0 голосов
/ 24 января 2019

Есть ли способ перебрать два контейнера (один за которым следует другой), не используя два цикла for.

Я собираюсь сделать что-то подобное

vector<int> a{ 1,2,3 };
vector<int> b{ 4,5,6 };

auto it = a.begin();
auto end = b.end();

for (; it != end; ++it)
{
    if (it == a.end())
    {
        it = b.begin();
    }
    // do something with *it
}

, чтобыprint

1 2 3 4 5 6

(конечно, это не работает. Объяснение в этом answer )

Я не хочу писать два для циклов и дублироватькод внутри цикла.Есть ли способ перебрать a с последующим b с помощью одного цикла for?

Единственное, о чем я могу думать, это либо скопировать / переместить второй контейнер в первый, либо создать новый векторобъединяя a и b, а затем итерируйте по нему.Я тоже не хочу этого делать, потому что это будет означать дорогостоящие операции копирования.

Ответы [ 9 ]

0 голосов
/ 24 января 2019

Если вам хочется написать собственный текст, вам поможет следующее:

template<class ForwardItr>
struct range {
    ForwardItr beg;
    ForwardItr end;
};

template<class ForwardItr, class F>
void concat_ranges(range<ForwardItr> r1, range<ForwardItr> r2, F f) {
    auto run = [&f](range<ForwardItr> r) {
        for(auto itr = r.beg; itr != r.end; ++itr){
            f(*itr);
        }
    };
    run(r1);
    run(r2);
};

Пример: https://gcc.godbolt.org/z/8tPArY

0 голосов
/ 05 июня 2019

Даже один цикл for() не требует печати этих контейнеров. Если вы используете std::copy следующим образом,

#include <iostream>

#include <vector>

#include <iterator>
#include <algorithm>

int main(int , char *[])
{
    std::vector< int> a{ 1, 2, 3};
    std::vector< int> b{ 4, 5, 6};

    std::copy( a.begin(), a.end(), std::ostream_iterator< int>( std::cout, " "));
    std::copy( b.begin(), b.end(), std::ostream_iterator< int>( std::cout, " "));

    std::cout<< std::endl;

    return 0;
}

вывод: 1 2 3 4 5 6

Лучше всего использовать библиотеку stl, и для печати контейнера не требуется писать код.

По вашему вопросу Я не хочу писать два цикла for и дублировать код внутри цикла. Есть ли способ перебрать повторяемый b с одним циклом for?

Способ избежать дублирования кода - это написать функции, которые можно использовать из нескольких мест, например, если вы не хотите использовать std::copy и хотите написать свой собственный код для печати этих контейнеров (что не рекомендуется) тогда вы можете написать следующую функцию,

template< typename ForwardIterator>
void print( ForwardIterator begin, ForwardIterator end, const std::string& separator)
{
    while( begin != end)
    {
        std::cout<< *begin<< separator;
        ++begin;
    }
}

затем вызовите print функцию,

print( a.begin(), a.end(), std::string( " "));
print( b.begin(), b.end(), std::string( " "));
0 голосов
/ 24 января 2019

еще один способ сделать это с помощью диапазона усиления

#include <vector>
#include <iostream>

#include <boost/range.hpp>
#include <boost/range/join.hpp>

int main()
{
  std::vector<int> a{ 1,2,3 };
  std::vector<int> b{ 4,5,6 };

  for(auto& x : boost::join(a, b)) {
      std::cout << x << " ";
  }
  std::cout << std::endl;
}
0 голосов
/ 24 января 2019

Нашел легкий «традиционный» способ сделать это.

for (int i = 0; i < 2; i++)
{
    auto it = (i == 0) ? a.begin() : b.begin();
    auto end = (i == 0) ? a.end() : b.end();
    for (; it != end; ++it)
    {
        // do something with *it
    }
}
0 голосов
/ 24 января 2019

Boost Range и алгоритмы стандартной библиотеки - это решения, которые следует отдавать предпочтение из-за их лучшего дизайна.

Однако, просто ради полноты, если вы действительно хотите применить идею, лежащую в основесвой дизайн вы можете кодировать следующим образом:

std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = {4, 5, 6};

for (auto it = v1.begin(); it != v2.end();) {
  if (it == v1.end()) {
    it = v2.begin();
  } else {
  // {
    // use of *it
  // }
    ++it;
  }
}

Демо здесь

0 голосов
/ 24 января 2019

Чтобы получить наиболее оптимальный код, желательно избегать ненужных проверок в каждом цикле.Поскольку наиболее эффективный код состоит в выполнении двух циклов, можно приблизиться к нему, изменив все состояние переменных, участвующих в цикле (итератор и сторож), (я полагаю, именно так реализован конкатенированный диапазон... так я и сделал):

vector<int> a{ 1,2,3 };
vector<int> b{ 4,5,6 };

auto it = a.begin();
auto end = a.end();

for (;[&](){
         if (it==end){
            if (end==a.end()) {
              it=b.begin();
              end=b.end();
              return true;
              }
            else return false;
            }
          return true;
          }();
    ++it)
{
   //loop content
}
0 голосов
/ 24 января 2019

Вы можете использовать boost :: range :: join примерно так:

#include <boost/range/join.hpp>

...

std::vector<int> a{ 1,2,3 };
std::vector<int> b{ 4,5,6 };

for (auto i : boost::range::join(a, b))
{
    ...
}
0 голосов
/ 24 января 2019

Использование range-v3 , ваш переход ко всем вещам, связанным с диапазоном в C ++ 17 или более ранних версиях:

for (int i : view::concat(a, b)) {
    std::cout << i << ' ';
}
0 голосов
/ 24 января 2019

Ну ... ваша ошибка - это двойное равенство, когда вам нужно одно равное.

Я имею в виду, а не

if (it == a.end())
{
    it == b.begin();
} //   ^^ Wrong!

но

if (it == a.end())
{
    it = b.begin();
} //   ^ correct

Но я не думаю, что это хорошая идея: мы уверены, что a.end() != b.end()?

Ваш код зависит от этого.

...