将容器划分为块,C ++

时间:2012-03-30 12:24:05

标签: c++ c++11

给定N个元素v = ( 1, 2, 3, 4, ... , N )的向量,返回范围迭代器,覆盖所有大小为K<N的块。如果K,则最后一个范围可能小于N%K!=0

例如:

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())算法压缩,则需要额外关注边界情况。这看起来很丑陋,分散注意力。
  3. 您能提出更优雅的解决方案吗? 通过优雅的解决方案,我的意思是使用通用算法,通过常用库(例如boost)构建或提供。

    -------------------------- [edit] ------------------ --------

    你们当中有些人想到了工作榜样,就在这里。

    #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
    

5 个答案:

答案 0 :(得分:9)

我不知道它是否非常优雅,但你可以使用标准函数advance和distance的迭代器:

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 :(得分:8)

WRT&#34;需要循环吗?&#34;

如果想要避免使用std::distance(),则需要循环结构,因为需要跟踪剩余的数量。 (对于随机访问容器,std::distance()很便宜 - 但对于所有其他容器,此算法的成本太高。)

WRT i + K / min()问题

不要写i + K或任何可能导致包装/上溢/下溢问题的内容。而是跟踪剩余和减去的数量。这需要使用min()

WRT优雅解决方案

这种算法更加优雅&#34;在那:

  1. 前两个&#34; WRT&#34;上述项目不是问题。
  2. 它不使用任何外部库; - 仅使用std::copy_n()std::advance()
  3. 如果需要/需要,它会利用与参数相关的查找。
  4. 它使用容器size_type
  5. 它可以有效地与任何容器一起使用。
  6. 如果K <= 0则抛出std::domain_error
  7. 解决方案是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仿函数接收输出迭代器并返回输出迭代器。这样,可以正确处理输出关于输出迭代器的块之间的任何更新,并且通用例程更加灵活。 (例如,这将允许仿函数记住每个块的结束位置;复制块的仿函数,清空容器,重置输出迭代器;等等)如果这是不希望的,那么就像标准库一样可以有多个具有不同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; }
    );
    

    这已经证明是一个令人惊讶的优雅小例程。

答案 2 :(得分:7)

这是一种具有良好性能的通用解决方案:

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

答案 3 :(得分: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

希望有人会发现它很有用。

答案 4 :(得分:0)

很抱歉回答迟到,但是似乎没有人提出这一解决方案:

template <typename Cont, typename Func, typename Sep>
void do_chunks(const Cont& cont, size_t K, Func f, Sep sep, char c='\n') {
    size_t size = cont.size();
    for (int i = 0; i < K; ++i) {
        for (int j = i*size / K, n = (i + 1)*size / K; j < n; ++j) {
            f(cont[j]);
        }
        sep(c);
    }
}

std::vector<int> m = {
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
};
do_chunks(
    m, 
    3, 
    [](const auto& x) { std::cout << x << " "; }, 
    [](char c)        { std::cout << c; }
);

输出:

1 2 3
4 5 6 7
8 9 10 11

因此,恰好在i == K - 1 (i + 1)*size / K == size时,我们可以正确地迭代所有容器元素,而没有任何超出范围的访问。