2012-03-30 11 views
11

Podano wektor z N elementów v = (1, 2, 3, 4, ... , N) iteratora zasięgu powrotu na wszystkie fragmenty o rozmiarze K<N. Ostatni zakres może być mniejszy niż K, jeśli N%K!=0.Podziel pojemnik na porcje, C++

Na przykład:

v = ("a","b","c","d","e") 

wyświetlacz ciągi

"ab", "cd", "e" 

N=v.size(); 
K=2; 

Jednym z możliwych rozwiązań jest:

for(unsigned int i=0; i<v.size(); i+=K) 
    cout << boost::join(v | boost::adaptors::sliced(i, min(i+K, v.size())), ""); 

To rozwiązanie jest całkiem ok, ale ma kilka problemów:

  1. for pętla - czy jest potrzebna?
  2. Jeśli napiszesz i+K zamiast miażdżyć algorytmem min(i+K, v.size()), należy zwrócić dodatkową uwagę na przypadek graniczny. To wygląda brzydko i rozprasza.

Czy możesz zaproponować bardziej eleganckie rozwiązanie? Przez eleganckie rozwiązanie mam na myśli użycie ogólnego algorytmu, wbudowanego lub udostępnionego przez powszechnie używaną bibliotekę (taką jak boost).

-------------------------- [edycja] ----------------- ---------

Niektórzy z was nie podoba się przykład pracy, oto jest.

#include <iostream> 
#include <vector> 
#include <string> 
#include <boost/range/adaptor/sliced.hpp> 
#include <boost/algorithm/string/join.hpp> 
#include <boost/assign.hpp> //just for fun 

using namespace std; 
using namespace boost::assign; 

int main(int , char **) 
{ 
    const int K = 2; 
    vector<string> v; 
    v += "a","b","c","d","e"; 

    for(unsigned int i=0; i<v.size(); i+=K) 
     cout << boost::algorithm::join( 
        v | boost::adaptors::sliced(i, min(i+K, v.size())), "") 
      << endl; 
} 

wyjściowa:

ab 
cd 
e 
+0

dlaczego nie zamieścisz pełnego przykładu? –

+1

@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

+0

Niestety, nie mogę skompilować twojego przykładu, a ja straciłem kryształową kulę;) –

Odpowiedz

6

Jest to sort-of-generic rozwiązanie z dobrymi wynikami:

template <class T, class Func> 
void do_chunks(T container, size_t K, Func func) { 
    size_t size = container.size(); 
    size_t i = 0; 

    // do we have more than one chunk? 
    if (size > K) { 
     // handle all but the last chunk 
     for (; i < size - K; i += K) { 
      func(container, i, i + K); 
     } 
    } 

    // if we still have a part of a chunk left, handle it 
    if (i % K) { 
     func(container, i, i + i % K); 
    } 
} 
+0

czy mogę to zrobić za pomocą ogólnych algorytmów? – bartek

+2

@bartek: Myślę, że chodzi tutaj o to, że staje się to ogólnym algorytmem, którego można używać w całym kodzie, zamiast powtarzać tę samą logikę. –

+0

@VaughnCato podczas pisania takiego algorytmu można popełnić więcej błędów, niż zapomnieć o funkcji 'min' w' adapterach :: w plasterkach'. Dlatego poprosiłem o rozwiązanie, które wykorzystuje tylko ogólne algorytmy, jak w moim przykładzie. – bartek

8

Nie wiem, czy to bardzo elegancki, ale można użyć iteratory z funkcjami standardowymi z wyprzedzeniem i odległością:

template<typename Iterator, typename Func, typename Distance> 
void chunks(Iterator begin, Iterator end, Distance k ,Func f){ 
    Iterator chunk_begin; 
    Iterator chunk_end; 
    chunk_end = chunk_begin = begin; 

    do{ 
     if(std::distance(chunk_end, end) < k) 
      chunk_end = end; 
     else 
      std::advance(chunk_end, k); 
     f(chunk_begin,chunk_end); 
     chunk_begin = chunk_end; 
    }while(std::distance(chunk_begin,end) > 0); 
} 
+1

+1 za używanie 'std :: advance' i' std :: distance', dobry przykład. Mimo to część 'if (std :: distance (chunk_end, end)> k)' jest dość rozpraszająca. Moje rozwiązania są krótsze, ale Twoja nie korzysta z biblioteki zewnętrznej. – bartek

+0

Czy linia nie ma, jeśli (std :: distance (chunk_end, end)> k) faktycznie jest PBJ

+0

@David Tak, masz rację! – BenjaminB

8

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:

  1. Pierwsze dwa "WRT" produkty powyżej nie są kwestie.
  2. Nie korzysta z żadnych zewnętrznych bibliotek; - korzysta tylko z std::copy_n() i std::advance().
  3. Wykorzystuje zależne od argumentów wyszukiwanie, jeśli jest to potrzebne/pożądane.
  4. Używa kontenera size_type.
  5. Będzie działać skutecznie z każdym kontenerem.
  6. 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.

+0

Dzięki Paul za odpowiedź. Używanie 'std :: copy_n()' i 'std :: advance()' jest kolejną prostą i elegancką opcją. Podoba mi się podpis "chunker" i ogólna ogólna algorytm. – bartek

0

ja trochę zmodyfikowany anwser przez @BenjaminB i dodał przykład użycia tej funkcji:

#include <iostream> 
#include <vector> 

using namespace std; 

template<typename Iterator, typename Func> 
void chunks(Iterator begin, 
      Iterator end, 
      iterator_traits<string::iterator>::difference_type k, 
      Func f) 
{ 
    Iterator chunk_begin; 
    Iterator chunk_end; 
    chunk_end = chunk_begin = begin; 

    do 
    { 
     if(std::distance(chunk_end, end) < k) 
      chunk_end = end; 
     else 
      std::advance(chunk_end, k); 
     f(chunk_begin,chunk_end); 
     chunk_begin = chunk_end; 
    } 
    while(std::distance(chunk_begin,end) > 0); 
} 

int main() { 
    string in_str{"123123123"}; 

    vector<string> output_chunks; 

    auto f = [&](string::iterator &b, string::iterator &e) 
    { 
     output_chunks.emplace_back(b, e); 
    }; 

    chunks(in_str.begin(), in_str.end(), 3, f); 

    for (string a_chunk: output_chunks) 
    { 
     cout << a_chunk << endl; 
    } 

    return 0; 
} 

wynik jest:

123 
123 
123 

nadzieję, że ktoś będzie się przydać.