2012-03-30 4 views
11

Заданный вектор N Элементы v = (1, 2, 3, 4, ... , N) Итератор диапазона возврата на все куски размером K<N. Последний диапазон может быть меньше K, если N%K!=0.Разделите контейнер на куски, C++

Например:

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

дисплей строки

"ab", "cd", "e" 

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

Одно из возможных решений:

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

Это решение вполне нормально, но у него есть несколько проблем:

  1. for петля - это необходимо?
  2. если вы напишете i+K вместо min(i+K, v.size()) алгоритм подавляет, нужно обратить дополнительное внимание на граничный случай. Это выглядит уродливо и отвлекает.

Можете ли вы предложить более элегантное решение? К элегантным решениям я имею в виду использование общего алгоритма, встроенный или предоставляемый обычно используемой библиотекой (например, boost).

-------------------------- [изменить] ----------------- ---------

Некоторые из вас выиграли рабочий пример, вот оно.

#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; 
} 

Выход:

ab 
cd 
e 
+0

Почему вы не публикуете полный пример? –

+1

@VJovic в примере я показал, что мне действительно нужно, но это более общий вопрос, как запустить алгоритм на каждом куске контейнера отдельно. – bartek

+0

К сожалению, я не могу скомпилировать ваш пример, и я потерял свой хрустальный шар;) –

ответ

6

Это своего рода-оф-общее решение с хорошей производительностью:

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

Могу ли я это сделать с использованием общих алгоритмов? – bartek

+2

@bartek: Я думаю, здесь дело в том, что это становится общим алгоритмом, который вы можете использовать во всем своем коде вместо повторения той же логики. –

+0

@VaughnCato при написании такого алгоритма можно сделать больше ошибок, чем просто забыть о функции 'min' в' adaptors :: sliced'. Вот почему я попросил решение, которое использует только общие алгоритмы, как в моем примере. – bartek

8

Я не знаю, если это очень элегантный, но вы можете использовать итераторы со старшими стандартными функциями и расстояние:

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 для использования 'std :: advance' и' std :: distance', хороший пример. Тем не менее, часть 'if (std :: distance (chunk_end, end)> k)' 'довольно отвлекает. Мои решения короче, но ваша не использует внешнюю библиотеку. – bartek

+0

Не следует ли строка if (std :: distance (chunk_end, end)> k) на самом деле быть PBJ

+0

@ Давид Да, ты прав! – BenjaminB

8

WRT «Для этого нужен цикл?»

Конструкция цикла необходима, если вы хотите избежать использования std::distance(), так как нужно отслеживать, сколько осталось. (Для случайных контейнеров доступа, std::distance() дешево --but для всех остальных это слишком дорого для этого алгоритма.)

WRT я + K/мин() выпуск

Не пиши я + K или что-нибудь, что может вызвать проблемы с оберткой/перегрузкой/переполнением. Вместо этого отслеживайте, сколько осталось и вычтите. Для этого потребуется использовать min().

WRT элегантное решение

Этот алгоритм является более "элегантным" в том, что:

  1. Первые два "WRT" пунктов выше, не являются вопросами.
  2. В нем не используются внешние библиотеки; - использует только std::copy_n() и std::advance().
  3. Он использует зависящий от аргумента поиск, если это необходимо/желательно.
  4. Используется контейнер size_type.
  5. Он будет работать эффективно с любым контейнером.
  6. Если 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; } 
); 

Это оказалось бы удивительно элегантная маленькая рутина.

+0

Спасибо, Пол за ваш ответ. Использование 'std :: copy_n()' и 'std :: advance()' является еще одним простым и элегантным вариантом. Мне нравится подпись «chunker» и общий алгоритм алгоритма. – bartek

0

Я немного модифицирована anwser по @BenjaminB и добавил пример использования этой функции:

#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; 
} 

Результат является:

123 
123 
123 

Надежда кто-то найдет это полезным.

Смежные вопросы