给定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()) ), "" );
这个解决方案很好但是有几个问题:
for
循环 - 是否需要?i+K
而不是min(i+K, v.size())
算法压缩,则需要额外关注边界情况。这看起来很丑陋,分散注意力。您能提出更优雅的解决方案吗? 通过优雅的解决方案,我的意思是使用通用算法,通过常用库(例如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
答案 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;在那:
std::copy_n()
和std::advance()
。size_type
。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
仿函数接收输出迭代器并返回输出迭代器。这样,可以正确处理输出关于输出迭代器的块之间的任何更新,并且通用例程更加灵活。 (例如,这将允许仿函数记住每个块的结束位置;复制块的仿函数,清空容器,重置输出迭代器;等等)如果这是不希望的,那么就像标准库一样可以有多个具有不同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
时,我们可以正确地迭代所有容器元素,而没有任何超出范围的访问。