根据条件拆分STL列表

Splitting an STL list based on a condition

本文关键字:STL 列表 拆分 条件      更新时间:2023-10-16

我有一个std::列表,看起来像这样(x标记表示小于500的数字)

x,x,x,x,503,x,x,x,510,x,x,x,502,x,x,x,x,x,x,600 - std::list<int> originallist

我希望将列表拆分为列表std::vector<std::list<int> >的向量,如下所示

1st element of vector: x,x,x,x,503
2nd element of vector: x,x,x,510
...
...
last element of vector: x,x,x,x,x,x,600
我现在的代码如下:
list<int> templist; vector<list<int> > v;
for(list<int>::iterator lit=originallist.begin(); lit!=oriniallist.end(); ++lit) {
    if (*lit > 500) {
        templist.push_back(*lit);v.push_back(templist); templist.clear(); continue;
    }
    templist.push_back(*lit);
}

在c++中不使用templist实现上述任务的最有效方法是什么?

虽然此解决方案确实使用临时std::list,但它不分配列表节点元素,并且在c++ 03情况下只分配1个内存(c++ 11情况下根据返回值的大小分配对数内存)

这是一个c++ 03的解决方案。c++ 11的解决方案可以一次完成。

bool big_as_500( int x ) {return x>=500;}
std::vector< std::list< int > > do_stuff( std::list<int>& original_list ) {
  // we have to do this, because resizing the return value involves lots of allocations
  // and stuff in C++03, so make sure we get the size right by precalculating it:
  std::size_t count = std::count_if( originallist.begin(), originallist.end(), big_as_500 );
  std::vector< std::list< int > > result;
  result.reserve(count+1); 
  typedef std::list<int>::const_iterator const_iterator;
  std::list< int > current;
  for(const_iterator it= originallist.begin(); it!=originallist.end();/*nothing*/) {
    ++it; // about to invalidate it! (or move lists)
    current.splice( current.end(), originallist, originallist.begin() ); // O(1) no memory allocation
    if (big_as_500(current.back())) {
      result.push_back( std::list<int>() );
      current.swap( result.back() );
    }
  }
  // original problem does not specify what to do if the original list does not end
  // with an element "big_as_500", so I'll just drop them
  return result; // rely on NRVO to eliminate the copy here, if your compiler does not
  // support it, take result as a reference parameter.
}

c++ 11解决方案:

std::vector< std::list< int > > do_stuff( std::list<int>& original_list ) {
  std::vector< std::list< int > > result;
  typedef std::list<int>::const_iterator const_iterator;
  std::list< int > current;
  for(const_iterator it= originallist.begin(); it!=originallist.end();/*nothing*/) {
    ++it;// about to become invalid/in wrong list
    current.splice( current.end(), originallist, originallist.begin() ); // O(1) no memory allocation
    if (current.back() >= 500) {
      result.emplace_back( std::move(current) );
    }
  }
  // original problem does not specify what to do if the original list does not end
  // with an element "big_as_500", so I'll just drop them
  return result; // will NRVO, or move, so no worries
}

在c++ 11中,调整大小是相对便宜的,所以我们很好。

现在,我们可以在c++ 03中得到真正的幻想,并模仿c++ 11所做的一切,并一次完成所有这些。

template<typename T, typename A>
void efficient_grow_by_1( std::vector<T,A>& make_one_bigger ) {
  if (make_one_bigger.size()+1 > make_one_bigger.capacity() )
  {
    std::vector<T, A> swap_vec;
    swap_vec.reserve( (make_one_bigger.size()+1)*5/3 );
    for (std::vector<T, A>::iterator it = make_one_bigger.begin(); it != make_one_bigger.end(); ++it ) {
      using std::swap;
      swap_vec.push_back();
      std::swap( *it, swap_vec.back() );
    }
    swap_vec.swap( make_one_bigger );
  }
  make_one_bigger.push_back();
}
void do_stuff( std::list<int>& original_list, std::vector< std::list< int > >& result ) {
  typedef std::list<int>::const_iterator const_iterator;
  std::list< int > current;
  for(const_iterator it= originallist.begin(); it!=originallist.end();) {
    ++it;
    current.splice( current.end(), originallist, originallist.begin() ); // O(1) no memory allocation
    if (current.back()>=500) {
      efficient_grow_by_1(result);
      current.swap( result.back() );
    }
  }
  // original problem does not specify what to do if the original list does not end
  // with an element "big_as_500", so I'll just drop them
}

这是相当疯狂的,所以我建议升级你的编译器。

这里的技巧是我们用每次一个元素的splice填充"临时"列表。因为(最多?许多?)std::list::splice的实现最终不得不遍历元素以对它们进行计数(这在c++ 11中是必需的,在c++ 03中很常见),当我们确定要将哪些元素放入下一个块时,一次执行一个,这是相当有效的。每个节点直接来自输入列表,并被收集到临时列表中(没有内存分配)。

一旦我们建立了这个列表,我们直接将其swap放入 lists的输出vector中。这避免了任何内存分配,除了需要保存(相对较小的)list的基本数据。

在c++ 03中,我们要么做一个两步解决方案并预先计算std::vector的输出有多大,要么在包含的list s上通过谨慎的增长和swap机制模拟c++ 11 move的效率。你的std库实现可能已经做到了这一点,但我不确定swap - resize优化在旧库中有多常见。

将事情减少到一次传递可能值得第二个c++ 03和c++ 11解决方案使用的对数数量的分配:遍历std::list是对缓存失败的练习。

第三版

此版本使用std::list::splice并移动迭代器,直到找到delim iter或到达end()

#include <iostream>
#include <list>
#include <vector>
std::vector< std::list<int> > & split( std::list<int>  v,
                   int delim, std::vector< std::list<int> >& elems) {
    auto it = v.begin();
    while ( it != v.end()) {
        std::list<int> l;
        while ( it != v.end() && *it < delim) {
            ++it;
        }
        if( it != v.end()) {
            l.splice( l.begin(), v, v.begin(), ++it);
            it = v.begin();
        } else {
            l.splice( l.begin(), v, v.begin(), it);
        }
        elems.push_back( l);
    }
    return elems;
}

std::vector< std::list<int> > split( const std::list<int>  &v, int delim) {
    std::vector< std::list<int> > elems;
    split( v, delim, elems);
    return elems;
}

用法:

int main() {
    std::list<int> v = { 1, 2, 3, 503, 5, 6, 502, 7, 510, 3, 500, 6, 7};
    std::vector< std::list<int> > vl;
    vl = split( v, 500);
    int i = 0;
    while( i < vl.size()) {
        std::list<int>::const_iterator it = vl[ i].begin();
        while( it !=  vl[ i].end())
            std::cout << *it++;
        std::cout << std::endl;
        ++i;
    }
    return 0;
}
http://ideone.com/VRpGft

打印:123503年

56502年

7510年

3500年

67年

第一个版本

此版本使用std::list::splice .

#include <iostream>
#include <list>
#include <vector>
std::vector< std::list<int> > & split( std::list<int>  v,
                   int delim, std::vector< std::list<int> >& elems) {
    auto it = v.begin();
    while ( it != v.end()) {
        std::list<int> l;
        auto it3 = l.begin();
        while ( it != v.end() && *it < delim) {
            l.splice( it3, v, it);
            it = v.begin();
        }
        if( it != v.end()) {
            l.splice( it3, v, it);
            it = v.begin();
        }
        elems.push_back( l);
    }
    return elems;
}

std::vector< std::list<int> > split( const std::list<int>  &v, int delim) {
    std::vector< std::list<int> > elems;
    split( v, delim, elems);
    return elems;
}

用法:

int main() {
    std::list<int> v = { 1, 2, 3, 503, 5, 6, 502, 7, 510, 3, 500, 5, 9};
    std::vector< std::list<int> > vl;
    vl = split( v, 500);
    int i = 0;
    while( i < vl.size()) {
        std::list<int>::const_iterator it = vl[ i].begin();
        while( it !=  vl[ i].end())
            std::cout << *it++;
        ++i;
    }
    return 0;
}

打印:

123503565027510350059

http://ideone.com/1xMehy

第二版

这是简化版本,不使用std::list::splice函数。该函数将元素放在迭代器前面,因此必须稍微改变循环。

#include <iostream>
#include <list>
#include <vector>
std::vector< std::list<int> > & split( const std::list<int>  & v,
                   int delim, std::vector< std::list<int> >& elems) {
    std::list<int>::const_iterator it = v.begin();
    while ( it != v.end()) {
        std::list<int> l;
        while ( it != v.end() && *it < delim) {
            l.push_back( *it++);
        }
        if( it != v.end()) l.push_back( *it++);
        elems.push_back( l);
    }
    return elems;
}

std::vector< std::list<int> > split( const std::list<int>  &v, int delim) {
    std::vector< std::list<int> > elems;
    split( v, delim, elems);
    return elems;
}

用法:

int main() {
    std::list<int> v = { 1, 2, 3, 503, 5, 6, 502, 7, 510, 3, 500, 5, 9};
    std::vector< std::list<int> > vl;
    vl = split( v, 500);
    int i = 0;
    while( i < vl.size()) {
        std::list<int>::const_iterator it = vl[ i].begin();
        while( it !=  vl[ i].end())
            std::cout << *it++;
        ++i;
    }
    return 0;
}

打印:

123503565027510350059

http://ideone.com/MBmlLE

试试下面的

#include <vector>
#include <list>
#include <algorithm>
#include <functional>
//...
auto first = YourList.begin();
while ( first != YourList.end() )
{
   auto last = std::find_if( first, YourList.end(), std::bind2nd( std::greater<int>(), 500 ) );
   if ( last != YourList.end() ) ++last;
   YourVector.push_back( std::list<int>( first, last ) );
   first = last;
} 
  1. 循环遍历数字并获得需要拆分列表的位置之间的距离

  2. 使用List中的splice函数对每个分割位置:

    lst.splice( newLst.begin(), newLst, lst.begin(), lst.begin() + sliceLength);
    
    http://www.cplusplus.com/reference/list/list/splice/

(注意,splice将破坏原始列表)