玩转STL的范围谓词:轻松应对复杂数据逻辑

一、简介

本文介绍了如何使用STL的范围谓词来轻松应对复杂的数据逻辑。范围谓词是STL中强大而灵活的工具,可以帮助开发人员处理各种条件和筛选要素,以提高代码的可读性和性能。

但它们有一个共同点:它们返回一个或多个范围的布尔特征。

二、*_of 系列

STL提供了3种算法,用于指示范围中的所有、部分或全部元素是否满足给定条件。条件本身由谓词表示,也就是说,一个函数指针(或对象)接受范围的一个元素并返回bool值。

这3种算法分别是:

这就是STL,但Boost更进一步提出了以下算法:

在空范围的情况下,它们的行为与它们对应的原生类型完全相同。

三、std::equal

std::equal可以用来比较两个范围,检查元素是否分别相等(比较是通过operator==或自定义比较器完成的)。注意,std::equal接受一个1.5-Range,即第一个范围由开始迭代器和结束迭代器表示,而第二个范围没有结束迭代器:

template<template InputIterator1, template InputIterator2 >
bool equal(InputIterator1 first1, InputIterator1 last1,
           InputIterator2 first2);

因此,该算法会一直运行到第1个范围的结束,即使第2个范围较短,也会进行比较,因为它并不知道第2个范围的长度。

对于std::equal,这既不自然又危险:

  • 这是不自然的,因为如果第一个范围有N个元素,std::equal返回true,只要第二个范围的前N个元素等于第一个范围的N个元素,即使第二个范围比第一个范围有更多的元素。
  • 这是很危险的,因为如果第二个范围比第一个范围短,算法就会越过它的终点,导致未定义的行为。

从C++ 14开始,这个问题得到了纠正,新的std::equal重载接受两个完整的范围,包括开始和结束。

四、std::is_permutation 检查排列

假设有两个集合,如何确定其中一个集合是另一个集合的排列?或者换句话说,如果两个集合包含相同的元素,即使它们的顺序不同,如何确定这一点?

为此,STL提供了std::is_permutation

例如,给定以下集合:

std::vector<int> v1 = {1, 2, 3, 4, 5};
std::vector<int> v2 = {4, 2, 3, 1, 5};
std::vector<int> v3 = {2, 3, 4, 5, 6};

调用std::is_permutation

std::is_permutation(v1.begin(), v1.end(),
                    v2.begin(), v2.end());

这个返回true,而下面的返回false

std::is_permutation(v1.begin(), v1.end(),
                    v3.begin(), v3.end());

因为v3中的元素不同于v1中的元素。

在C++ 14之前,std::is_permutation有一个1.5-Range接口,也就是说它接受第一个范围的beginend,而第二个范围只接受begin迭代器:

std::is_permutation(v1.begin(), v1.end(),
                    v2.begin());

因此,如果第二个集合比第一个小,那么算法会开心地一直查询第二个集合,直到查询到第一个集合的末尾,从而导致未定义的行为。结果是,必须确保第二个集合至少和第一个一样大。

但这在C++ 14中得到了纠正,它为两个集合添加了带有beginend迭代器的重载。

std::is_permutation使用operator==比较元素,并提供一个接受自定义比较器的重载。

4.1、std::is_permutation 的算法复杂度

std::is_permutation的复杂度在最糟糕的情况下为O(n²)。这听起来可能有点不可思议:实际上,STL的算法被认为是用最好的算法复杂度实现的。难道我们可以做得比O(n²)复杂度更好吗?

是的,可以做得比O(n²)复杂度更好,但代价是额外的内存分配。所以这是CPU和内存之间的权衡。

4.2、std::is_permutation 的使用示例

假设有一个函数,它返回一个值的集合(或通过输出迭代器生成),但没有指定这些元素在集合中的位置顺序。如何为这个函数编写单元测试?

无法对预期输出和实际输出进行 EXPECT_EQ 测试,因为无法确切知道输出应该等于什么,也不知道其元素的顺序。

这时就可以使用std::is_permutation

std::vector<int> expected = {1, 2, 3, 4, 5};

std::vector<int> results = f();

EXPECT_TRUE(std::is_permutation(begin(expected), end(expected),
                                begin(results), end(results)));

这样就可以表达期望函数f返回1、2、3、4和5,但顺序可以任意。

五、std::mismatch

这两种算法可以在范围上实现某种排序,可以用它来比较两个范围。更具体地说:

std::mismatch从两个输入范围的开头开始比较它们各自的元素,并以迭代器std::pair的形式返回它们第一个不同的位置:pair的第一个元素是指向第一个范围内第一个不匹配元素的迭代器,pair的第二个元素是指向第二个范围内第一个不匹配元素的迭代器。

它使用operator==(或自定义比较器)执行比较。

template<typename InputIt1, typename InputIt2, typename BinaryPredicate>
std::pair<InputIt1,InputIt2>
    mismatch(InputIt1 first1, InputIt1 last1,
             InputIt2 first2,
             BinaryPredicate p);

注意,std::mismatch也有1.5- range问题,所以确保先通过较短的范围。如果使用它进行比较,这可能会很麻烦。但和std::equal一样,从C++ 14开始,std::mismatch1.5-Range问题得到了解决。

六、std::lexicographical_compare

std::lexicographical_compare实际上提供了对范围的排序,其操作方式与dictionary 提供对字符串的排序相同,因此得名。它使用operator<(或自定义比较器)比较2乘2的元素。

template<typename InputIt1, typename InputIt2, typename Compare>
bool lexicographical_compare(InputIt1 first1, InputIt1 last1,
                             InputIt2 first2, InputIt2 last2,
                             Compare comp );

std::lexicographical_compare需要2个完整的范围,所以它没有1.5个范围的问题。

std::lexicographical_compare非常方便,它允许对包装容器的类进行自然而简单的编码顺序。例如,当处理类似CSV的数据时,设计一个Entry类来表示CSV文件中给定行上用逗号分隔的所有数据块:

class Entry
{
public:
    // ...Entry interface...
    bool operator<(const Entry& other)
    {
        return std::lexicographical_compare(begin(data_), end(data_),
                                            begin(other.data_), end(other.data_));
    }
private:
    std::vector<std::string> data_;
};

这允许以自然的方式对条目进行简单排序,从而实现快速搜索和相关功能(插入等)。它还使Entry与std::mapstd::set等排序关联容器兼容。

七、总结

本文详细介绍了STL的范围谓词的应用和优势。范围谓词是处理复杂数据逻辑的有力工具,通过定义和使用谓词函数可以轻松地筛选和操作数据集合。结合STL的算法,范围谓词可以帮助编写简洁、高效的代码,提高开发效率和代码可读性。
在这里插入图片描述

  • 20
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lion Long

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值