2012-03-30 65 views
11

給定向量N元素v = (1, 2, 3, 4, ... , N)返回範圍迭代器遍歷所有大小爲K<N的塊。如果N%K!=0的最後範圍可以小於K將容器劃分爲大塊,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

你爲什麼不發表完整的例子? – 2012-03-30 12:35:16

+1

@VJovic在示例中,我展示了我真正需要的東西,但這是更一般的問題,如何分別在容器的每個塊上運行算法。 – bartek 2012-03-30 12:46:40

+0

不幸的是,我不能編譯你的例子,我失去了我的水晶球;) – 2012-03-30 13:12:49

回答

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 2012-03-30 12:47:53

+2

@bartek:我認爲這裏的一點是,這成爲一種通用算法,您可以在整個代碼中使用,而不是重複相同的邏輯。 – 2012-03-30 12:50:13

+0

@VaughnCato在寫這樣的算法時,可以犯的錯誤多於忘記'adapters :: sliced'中的'min'函數。這就是爲什麼我要求一個只使用泛型算法的解決方案,正如我的例子。 – bartek 2012-03-30 13:06:00

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

使用'std :: advance'和'std :: distance'是一個很好的例子。 不過,'if(std :: distance(chunk_end,end)> k)'部分是相當分心的。我的解決方案更短,但你的不使用外部庫。 – bartek 2012-03-30 13:58:23

+0

if(std :: distance(chunk_end,end)> k)的行不應該是 PBJ 2012-07-17 23:09:10

+0

@大衛是的,你是對的! – BenjaminB 2012-07-18 07:23:42

8

WRT「For循環是否需要?」

如果您想避免使用std::distance(),需要循環構造,因爲需要跟蹤剩餘多少元素。 (對於隨機接入容器,std::distance()是便宜的 - 但對於所有其他實在是太昂貴了該算法。)

WRT I + K /分鐘()問題

不要寫I + K或任何可能導致打包/溢出/下溢問題的事情。而是跟蹤剩餘和減去多少。這將需要使用min()

WRT優雅的解決方案

這種算法是在更 「優雅」:

  1. 兩個率先 「WRT」 上述項目都沒有問題。
  2. 它不使用任何外部庫; - 它只能使用std::copy_n()std::advance()
  3. 如果需要/需要,它會利用參數相關查找。
  4. 它使用容器的size_type
  5. 它將與任何容器有效地工作。
  6. 如果K < = 0,則引發std::domain_error

解決方案是C++ 11,但如果還寫入copy_n(),它可以很容易地轉換爲C++ 98。

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

編輯:倒不如寫chunker()使得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

謝謝Paul的回答。使用'std :: copy_n()'和'std :: advance()'是另一種簡單而優雅的選擇。我喜歡'chunker'簽名和整個算法的一般性。 – bartek 2012-07-12 11:00:19

0

我通過@BenjaminB小修改anwser並添加使用這個函數的例子:

#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 

希望有人會發現它有用。