WRT "Для цикла это нужно?"
Конструкция цикла необходима, если вы хотите избежать использования std::distance()
, так как нужно отслеживать, сколько осталось. (Для контейнеров с произвольным доступом std::distance()
дешево - но для всех остальных это слишком дорого для этого алгоритма.)
WRT i + K / min () выпуск
Не пишите i + K или что-либо, что может вызвать проблемы с обтеканием / переполнением / недостаточным заполнением. Вместо этого отследите, сколько осталось, и вычтите. Это потребует использования min()
.
WRT элегантное решение
Этот алгоритм более «элегантен» в этом:
- Первые два пункта "WRT" выше не являются проблемами.
- не использует никаких внешних библиотек; - он использует только
std::copy_n()
и std::advance()
.
- Он использует аргумент-зависимый поиск, если это необходимо / желательно.
- Используется контейнер
size_type
.
- Эффективно будет работать с любым контейнером.
- Если K <= 0, тогда выбрасывается <code>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;
}
и вызов chunk будет менее красивым, но в целом более полезным (хотя и не в этом случае):
chunker(
v, k,
ostream_iterator<VECTOR::value_type>(cout),
[](ostream_iterator<VECTOR::value_type> o) { cout << endl; return o; }
);
Это оказалось удивительно элегантной рутиной.