采用测试和变异函数的STL算法

时间:2014-02-21 15:40:48

标签: c++ algorithm c++11 stl lambda

我想要的是这种行为:void change_if( ForwardIterator first, ForwardIterator last, UnaryPredicate test, UnaryOperation op )

使用for循环实现这一目标的最佳方法是什么?还是有一些我还不知道的STL魔法?

5 个答案:

答案 0 :(得分:7)

这可以在不使用boost的情况下完成,但应用标准算法std::for_each 我不建议使用boost来完成这么简单的任务。在项目中包含boost来执行这样一个简单的任务只是一个愚蠢的事。您可以将boost用于此类任务,前提是它已包含在您的项目中。

std::for_each( first, last, []( const T &x ) { if ( test( x ) ) op( x ); } );

或者,如果要更改序列的元素

,则可以删除限定符const
std::for_each( first, last, []( T &x ) { if ( test( x ) ) op( x ); } );

有时当使用序列的整个范围时,使用基于for语句的范围而不是算法更简单,因为使用具有lambda表达式的算法有时会使代码更不可读

for ( auto &x : sequence )
{
   if ( test( x ) ) op( x );
}

或者

for ( auto &x : sequence )
{
   if ( test( x ) ) x = op( x );
}

答案 1 :(得分:4)

Vlad from Moscow的解决方案是推荐的方法,因为它很简单。

std::transform标准算法与lambda的“看似虔诚”的使用:

std::transform(first, last, first, [](auto elem) {
   return test(elem) ? op(elem) : elem;
});

实际上导致性能下降,因为所有元素将被分配给,而不仅仅是那些满足谓词的元素。要仅修改谓词元素,还需要像kiwi在答案中提到的boost::filter_iterator之类的内容。

请注意,我在lambda中使用了带有auto的C ++ 14语法。对于C ++ 11,您需要decltype(*first)iterator_traits<ForwardIterator>::value_type之类的内容。在C ++ 98/03中,你可以使用它和手工制作的函数对象。

答案 2 :(得分:3)

另一个提升解决方案:

http://www.boost.org/doc/libs/1_55_0/libs/iterator/doc/filter_iterator.html

只需在过滤后的迭代器上调用std :: transform。

答案 3 :(得分:2)

std::for_each( first, last, []( T &x ) { if ( test( x ) ) op( x ); } );

或使用boost lambda:

#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
#include <boost/lambda/if.hpp>

std::for_each( v.begin(), v.end(), 
                   if_( test() )[ op() ] 
                 );

或者:

std::vector<int>::iterator it = v.begin();
while ( it != v.end()) {
  if ( test( *it)) op(*it);
  ++it;
}

答案 4 :(得分:1)

您希望change_if是一个简单的循环吗?

template<typename ForwardIterator, typename UnaryPredicate>
void change_if( ForwardIterator first, ForwardIterator last, UnaryPredicate test, UnaryOperation op ) {
  for(; first!=last; ++first)
    if (test(*first)) *first=op(std::move(*first));
}

或者只是写上面的循环。我建议实际写change_if并调用它,因为虽然上面的代码很短,但我会发现change_if调用比仅仅删除上面的代码更清楚,而不是更清楚。

我也喜欢编写基于容器的重载:

template<typename Container, typename UnaryPredicate>
void change_if( Container&& c, UnaryPredicate test, UnaryOperation op ) {
  for(auto& v : std::forward<Container>(c))
    if (test(v)) v=op(std::move(v));
}

但我也有:

template<typename Iterator>
struct range {
  Iterator b, e;
  Iterator begin() const { return b; }
  Iterator end() const { return e; }
};
template<typename Iterator0, typename Iterator1>
range<typename std::decay<Iterator0>::type> make_range(Iterator0&& b, Iterator1&& e) {
  static_assert(
    std::is_convertible< Iterator1, typename std::decay<Iterator0>::type >::value,
    "end must be compatible with begin iterator type"
  );
  return { std::forward<Iterator0>(b), std::forward<Iterator1>(e) };
}

允许我将这种基于容器的算法与迭代器一起使用。

您会看到我有Container change_if?它实际上是基于范围的change_if

它被称为:

change_if( myVect, [](int x){return (x%2)==0;}, [](int x){return x/2;} );

在容器上,而不是一对迭代器。但是,如果您只想更改容器的前半部分,则它不起作用:因此乍一看,基于容器(基于范围的)算法的用处不大。

make_range将迭代器转换为范围。所以你可以:

change_if( make_range( myVec.begin(), myVec.begin()+myVec.size()/2 ), [](int x){return (x%2)==0;}, [](int x){return x/2;} )

make_range通过将两个迭代器捆绑到一个range<>对象中,无法直接将2个迭代器传递给基于范围的算法。这个角落的情况更加冗长,但典型的情况(处理整个容器)变得不那么冗长。

另外,常见的一种错误(为beginend命名一个不同的容器)的频率要低得多。

所有这些最终都比基于迭代器的版本高效或更高效。而且,如果您使用iterables(具有不同beginend迭代器类型的范围)替换您的范围,我的change_if只能正常工作