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

STL Algorithm that Takes a Test and Mutate Function

本文关键字:STL 算法 函数 变异 测试      更新时间:2023-10-16

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

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

这可以在不使用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 );
}

Vlad来自莫斯科的解决方案是推荐的简单方法。

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中,您将同时使用它和手工制作的函数对象。

还有另一个提升解决方案:

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

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

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

或使用增强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;
}

您想要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) };
}

这让我可以将这种基于容器的算法与迭代器一起使用。

你会看到我有一个基于Containerchange_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;} )

CCD_ 15通过将两个迭代器绑定到一个CCD_。这个角落的情况更详细,但典型的情况(处理整个容器)变得不那么详细。

此外,一种常见的错误(为beginend命名不同的容器)也变得不那么频繁了。

所有这些最终都与基于迭代器的版本一样高效,甚至更高效。而且,如果您将范围替换为可迭代项(具有不同beginend迭代器类型的范围),则我的change_if只适用于