WRT «Для этого нужен цикл?»
Конструкция цикла необходима, если вы хотите избежать использования std::distance()
, так как нужно отслеживать, сколько осталось. (Для случайных контейнеров доступа, std::distance()
дешево --but для всех остальных это слишком дорого для этого алгоритма.)
WRT я + K/мин() выпуск
Не пиши я + K или что-нибудь, что может вызвать проблемы с оберткой/перегрузкой/переполнением. Вместо этого отслеживайте, сколько осталось и вычтите. Для этого потребуется использовать min()
.
WRT элегантное решение
Этот алгоритм является более "элегантным" в том, что:
- Первые два "WRT" пунктов выше, не являются вопросами.
- В нем не используются внешние библиотеки; - использует только
std::copy_n()
и std::advance()
.
- Он использует зависящий от аргумента поиск, если это необходимо/желательно.
- Используется контейнер
size_type
.
- Он будет работать эффективно с любым контейнером.
- Если K < = 0, то
std::domain_error
выбрасывается.
Решение является C++ 11, хотя его можно легко преобразовать в C++ 98, если также записывается copy_n()
.
#include <vector>
#include <string>
#include <sstream>
#include <iterator>
#include <iostream>
#include <stdexcept>
#include <algorithm>
template <
typename Container,
typename OutIter,
typename ChunkSepFunctor
>
OutIter chunker(
Container const& c,
typename Container::size_type const& k,
OutIter o,
ChunkSepFunctor sep
)
{
using namespace std;
if (k <= 0)
throw domain_error("chunker() requires k > 0");
auto chunkBeg = begin(c);
for (auto left=c.size(); left != 0;)
{
auto const skip = min(left,k);
o = copy_n(chunkBeg, skip, o);
left -= skip;
advance(chunkBeg, skip);
if (left != 0)
sep();
}
return o;
}
int main()
{
using namespace std;
using VECTOR = vector<string>;
VECTOR v{"a","b","c","d","e"};
for (VECTOR::size_type k = 1; k < 7; ++k)
{
cout << "k = " << k << "..." << endl;
chunker(
v, k,
ostream_iterator<VECTOR::value_type>(cout),
[]() { cout << endl; }
);
}
cout << endl;
}
EDIT: Было бы лучше написать chunker()
так, что sep
функтора получил выходной итератор и вернулся итератор вывода. Таким образом, любые обновления между выводами блоков в отношении выходного итератора могут быть правильно обработаны, а общая процедура намного более гибкая. (Например, это позволит функтору запомнить конечную позицию каждого фрагмента, функтор для копирования фрагментов, пустой контейнер и сброс выходного итератора и т. Д.). Если это нежелательно, то, как и стандартная библиотека, можно имеют более одной перегрузки с различными требованиями sep
или, вообще говоря, полностью исключая аргумент. Это обновленное chunker()
выглядит следующим образом:
template <
typename Container,
typename OutIter,
typename ChunkSepFunctor
>
OutIter chunker(
Container const& c,
typename Container::size_type const& k,
OutIter o,
ChunkSepFunctor sep
)
{
using namespace std;
if (k <= 0)
throw domain_error("chunker() requires k > 0");
auto chunkBeg = begin(c);
for (auto left=c.size(); left != 0;)
{
auto const skip = min(left,k);
o = copy_n(chunkBeg, skip, o);
advance(chunkBeg, skip);
left -= skip;
if (left != 0)
o = sep(o);
}
return o;
}
и вызов чанка будет менее красиво, но как правило, более полезным (хотя и не в этом случае):
chunker(
v, k,
ostream_iterator<VECTOR::value_type>(cout),
[](ostream_iterator<VECTOR::value_type> o) { cout << endl; return o; }
);
Это оказалось бы удивительно элегантная маленькая рутина.
Почему вы не публикуете полный пример? –
@VJovic в примере я показал, что мне действительно нужно, но это более общий вопрос, как запустить алгоритм на каждом куске контейнера отдельно. – bartek
К сожалению, я не могу скомпилировать ваш пример, и я потерял свой хрустальный шар;) –