使用std算法将容器分区/批处理/块化为相同大小的块

Zac*_*Zac 7 c++ boost stl c++11

我遇到了一种情况,我必须将一组记录批处理到数据库.我想知道如何使用std算法实现这一目标.

给定10002条记录,我希望将其分成100个记录的区间进行处理,其余为2的区间.

希望以下代码能够更好地说明我正在努力实现的目标.我对完全开放的解决方案涉及迭代器,lambdas任何类型的现代C++乐趣.

#include <cassert>
#include <vector>
#include <algorithm>

template< typename T >
std::vector< std::vector< T > > chunk( std::vector<T> const& container, size_t chunk_size )
{
  return std::vector< std::vector< T > >();
}

int main()
{
  int i = 0;
  const size_t test_size = 11;
  std::vector<int> container(test_size);
  std::generate_n( std::begin(container), test_size, [&i](){ return ++i; } );

  auto chunks = chunk( container, 3 );

  assert( chunks.size() == 4 && "should be four chunks" );
  assert( chunks[0].size() == 3 && "first several chunks should have the ideal chunk size" );
  assert( chunks.back().size() == 2 && "last chunk should have the remaining 2 elements" );

  return 0;
}
Run Code Online (Sandbox Code Playgroud)

jog*_*pan 6

在并行化的上下文中,这种范围的分区很常见,我发现制作一个范围的概念是有用的,即一对(从,到)在各种例程和线程之间传递的最小单元.处理.

它比为每个部分制作一个完整子矢量的副本更好,因为它需要更少的内存空间.它比仅维护单个end-iterators更实用,因为每个范围都可以按原样传递给一个线程 - 当它是第一个或最后一个部分时没有特殊情况等.

考虑到这一点,以下是我发现运行良好的例程的简化版本,它是所有现代C++ 11:

#include <cassert>
#include <vector>
#include <utility>
#include <algorithm>
#include <cstdint>

template <typename It>
std::vector<std::pair<It,It>>
  chunk(It range_from, It range_to, const std::ptrdiff_t num)
{
  /* Aliases, to make the rest of the code more readable. */
  using std::vector;
  using std::pair;
  using std::make_pair;
  using std::distance;
  using diff_t = std::ptrdiff_t;

  /* Total item number and portion size. */
  const diff_t total
  { distance(range_from,range_to) };
  const diff_t portion
  { total / num };

  vector<pair<It,It>> chunks(num);

  It portion_end
  { range_from };

  /* Use the 'generate' algorithm to create portions. */    
  std::generate(begin(chunks),end(chunks),[&portion_end,portion]()
        {
          It portion_start
          { portion_end };

          portion_end += portion;
          return make_pair(portion_start,portion_end);
        });

  /* The last portion's end must always be 'range_to'. */    
  chunks.back().second = range_to;

  return chunks;
}

int main()
{
  using std::distance;

  int i = 0;
  const size_t test_size = 11;
  std::vector<int> container(test_size);
  std::generate_n( std::begin(container), test_size, [&i](){ return ++i; } );

  /* This is how it's used: */    
  auto chunks = chunk(begin(container),end(container),3);

  assert( chunks.size() == 3 && "should be three chunks" );
  assert( distance(chunks[0].first,chunks[0].second) == 3 && "first several chunks should have the ideal chunk size" );
  assert( distance(chunks[2].first,chunks[2].second) == 5 && "last chunk should have 5 elements" );

  return 0;
}
Run Code Online (Sandbox Code Playgroud)

它的工作原理与你提出的略有不同:部分尺寸总是向下舍入,因此在你的例子中只得到3个部分,最后部分比其他部分略大(不小一点).这可以很容易地修改(我认为这并不重要,因为通常部分的数量远小于工作项的总数).


备注.在我自己使用与范围相关的模式中,很快就发现实际存储整数(每个指示距离.begin())而不是迭代器通常更好.其原因是,这些整数与实际迭代器之间的转换是一个快速且无害的操作,并且可以不管你是否需要执行iteratorconst_iterator.然而,当你存储迭代器时,你需要一劳永逸地决定你是否使用iterator或者const_iterator,这可能是一种痛苦.


ald*_*ldo 5

问题似乎是一个变化std::for_each,你要操作的"每个"是你的集合的间隔.因此,您更愿意编写一个lambda(或函数),它接受两个迭代器定义每个间隔的开始和结束,并将该lambda /函数传递给您的算法.

这就是我想出来的......

// (Headers omitted)

template < typename Iterator >
void for_each_interval(
    Iterator begin
  , Iterator end
  , size_t interval_size
  , std::function<void( Iterator, Iterator )> operation )
{
  auto to = begin;

  while ( to != end )
  {
    auto from = to;

    auto counter = interval_size;
    while ( counter > 0 && to != end )
    {
      ++to;
      --counter;
    }

    operation( from, to );
  }
}
Run Code Online (Sandbox Code Playgroud)

(我希望这std::advance会照顾counter用于递增的内部循环to,但不幸的是它盲目地超越了结尾[我很想写我自己的smart_advance模板来封装它].如果这样可行,它会减少代码约一半!)

现在为一些代码来测试它...

// (Headers omitted)

int main( int argc, char* argv[] )
{
  // Some test data
  int foo[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
  std::vector<int> my_data( foo, foo + 10 );
  size_t const interval = 3;

  typedef decltype( my_data.begin() ) iter_t;
  for_each_interval<iter_t>( my_data.begin(), my_data.end(), interval,
    []( iter_t from, iter_t to )
    {
      std::cout << "Interval:";
      std::for_each( from, to,
        [&]( int val )
        {
          std::cout << " " << val;
        } );
      std::cout << std::endl;
    } );
}
Run Code Online (Sandbox Code Playgroud)

这会产生以下输出,我认为它代表你想要的:

Interval: 0 1 2
Interval: 3 4 5
Interval: 6 7 8
Interval: 9