WRT "Czy potrzebna jest pętla?"
Konstrukcja pętli jest potrzebna, jeśli chce się uniknąć używania std::distance()
, ponieważ trzeba śledzić, ile zostało. (Dla losowych pojemników dostępu, std::distance()
jest tani --but dla wszystkich innych jest to zbyt kosztowne dla tego algorytmu.)
WRT I + K/min() problem
Nie pisz i + K lub cokolwiek, co może powodować problemy z pakowaniem/over/underflow. Zamiast tego śledź ile zostało i odejmij. Będzie to wymagało użycia min()
.
WRT eleganckie rozwiązanie
Ten algorytm jest bardziej "elegancki", że:
- Pierwsze dwa "WRT" produkty powyżej nie są kwestie.
- Nie korzysta z żadnych zewnętrznych bibliotek; - korzysta tylko z
std::copy_n()
i std::advance()
.
- Wykorzystuje zależne od argumentów wyszukiwanie, jeśli jest to potrzebne/pożądane.
- Używa kontenera
size_type
.
- Będzie działać skutecznie z każdym kontenerem.
- Jeśli K < = 0, to zostanie zgłoszony
std::domain_error
.
Rozwiązaniem jest C++ 11, chociaż można go łatwo przekonwertować na C++ 98, jeśli pisze się również 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: Byłoby lepiej napisać chunker()
tak że sep
funktor otrzymał iteracyjnej wyjściowy i zwrócony iterator wyjściowego. W ten sposób wszelkie aktualizacje pomiędzy fragmentami wyjściowymi dotyczące iteratora wyjściowego mogą być poprawnie obsługiwane, a ogólna rutyna znacznie bardziej elastyczna. (Na przykład, pozwoli to funktorowi zapamiętać końcową pozycję każdej porcji, funktor kopiuje porcje, opróżnia pojemnik i resetuje wyjściowy iterator, itp.) Jeśli jest to niepożądane, to tak jak w Bibliotece standardowej można mieć więcej niż jedno przeciążenie z różnymi wymaganiami, lub całkowicie wyeliminować argument. Ta zaktualizowana chunker()
wygląda następująco:
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;
}
i wezwanie do fragmentu byłoby mniej ładne, ale ogólnie bardziej przydatne (choć nie w tym przypadku):
chunker(
v, k,
ostream_iterator<VECTOR::value_type>(cout),
[](ostream_iterator<VECTOR::value_type> o) { cout << endl; return o; }
);
ten okazał się być zaskakująco elegancka mała rutyna.
dlaczego nie zamieścisz pełnego przykładu? –
@VJovic w przykładzie pokazałem, czego naprawdę potrzebuję, ale jest to bardziej ogólne pytanie, jak uruchomić algorytm dla każdego fragmentu kontenera osobno. – bartek
Niestety, nie mogę skompilować twojego przykładu, a ja straciłem kryształową kulę;) –