【问题标题】:Splitting an STL list based on a condition根据条件拆分 STL 列表
【发布时间】:2014-05-21 22:08:21
【问题描述】:

我有一个 std::list 如下所示(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&lt;std::list&lt;int&gt; &gt;,如下所示

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

在不使用 templist 的情况下,在 c++ 中实现上述任务的最有效方法是什么?任何帮助表示赞赏。

【问题讨论】:

  • @Lilshieste 我以最天真的方式做到了 - 遍历原始列表,同时维护一个临时列表。如果我点击了一个大于 500 的数字,我将临时列表 push_back 到向量并清除其内容。我想知道我是否可以在不创建临时列表的情况下做到这一点。
  • boost::algorithm::find_all 可能不会出错。
  • 你能分享任何代码吗?它可能有助于保持与您手头任务相关的答案。 (我们或许可以帮助您调整现有方法。)
  • 是否需要原列表保持不变?如果没有,您可以使用std::list::splice 以便宜的方式将段切分到所需的子列表中。
  • 必须使用std::list吗?它是解决问题的好容器,这种情况极为罕见。哦,还有 C++11 支持吗?

标签: c++ algorithm list vector stl


【解决方案1】:

虽然这个解决方案确实使用了一个临时的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 的大小,要么我们模拟 C++11 的 move 效率并小心增长和 swap 机制包含lists。您的 std 库实现可能已经对此进行了伪造,但我不确定 swap-resize 优化在旧库中的普遍程度。

将事情减少到单次通过可能值得第二个 C++03 和 C++11 解决方案使用的对数分配:遍历 std::list 是缓存未命中的练习。

【讨论】:

  • 顺便说一句,我建议不要将std::list 用作一般规则。尤其是在 C++11 中,std::vector 调整大小要便宜得多(感谢move 支持)。
  • 哦,我忘了:谢谢你的回答:)
  • (我会用auto it = originallist.begin()替换C++11中的const_iterator it= originallist.begin(),然后你可以去掉typedef。)对于不同的分配器,我不确定是否移动一个list 清空源列表,所以我建议使用 move 而不是 swap 可能不是最好的。
【解决方案2】:

第三版

此版本使用std::list::splice 并移动迭代器直到找到delimiter 或到达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

【讨论】:

  • OP 在开头有一个std::list,而不是std::vector
  • OP 以 "我有一个如下所示的 std::list" 开头
  • @dyp 好的,我们有一个列表
  • 现在您可以通过拼接而不是复制来提高效率 (push_back)。
  • splice 通过放置迭代器来工作。元素被插入到迭代器指向的元素之前。可以使用,但这是简化版。
【解决方案3】:

试试下面的

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

【讨论】:

  • 请注意,bind2nd 在 C++11 中已弃用(不过,OP 不使用 C++11 标记)。
  • 也可以拼接列表;这可能更有效。
【解决方案4】:
  1. 遍历数字并获取需要拆分列表的位置之间的距离。

  2. 对每个拆分位置使用List中的拼接函数:

    lst.splice( newLst.begin(), newLst, lst.begin(), lst.begin() + sliceLength);
    

    http://www.cplusplus.com/reference/list/list/splice/

(注意拼接会破坏原来的列表)

【讨论】:

  • lst.begin() + sliceLength 可能不起作用,因为列表迭代器不是 RandomAccessIterator。即使它有效,也可以通过 not 再次迭代 sliceLength 步骤来轻松优化。
  • 列表迭代器确实是双向迭代器。它不支持添加。你每天都会学到一些东西。 cplusplus.com/reference/iterator/BidirectionalIterator
猜你喜欢
  • 2010-10-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-20
  • 2017-07-03
  • 2019-08-18
相关资源
最近更新 更多