使用STL的范围谓词
一、简介
本文介绍了如何使用STL的范围谓词来轻松应对复杂的数据逻辑。范围谓词是STL中强大而灵活的工具,可以帮助开发人员处理各种条件和筛选要素,以提高代码的可读性和性能。
但它们有一个共同点:它们返回一个或多个范围的布尔特征。
二、*_of 系列
STL提供了3种算法,用于指示范围中的所有、部分或全部元素是否满足给定条件。条件本身由谓词表示,也就是说,一个函数指针(或对象)接受范围的一个元素并返回bool
值。
这3种算法分别是:
std::all_of
:检查范围内的所有元素是否满足给定条件。如果范围为空,则返回true
,因此其语义是更精确地检查是否没有元素不满足条件。std::any_of
:查范围内是否有任何元素满足给定条件。如果范围为空,则返回false
。std::none_of
:检查范围内是否没有元素满足给定条件。如果范围为空,则返回true
。
这就是STL,但Boost更进一步提出了以下算法:
boost::algorithm::one_of
:检查范围中是否只有一个元素满足给定条件。如果范围为空,它(完全如所预期地)返回false
。- Boost还提供了上述每种算法的
*_equal
版本,它们接受一个范围和一个值,并具有与其原生对应的相同行为,条件是元素等于传递的值。比较是用operator==
完成的,不能自定义。boost::algorithm::all_of_equal
:接受一个范围和一个值,并检查范围中的所有元素是否都等于该值。boost::algorithm::any_of_equal
:接受一个范围和一个值,并检查范围中是否有一个元素等于该值。boost::algorithm::none_of_equal
:它接受一个范围和一个值,并检查该范围内是否有任何元素等于该值。boost::algorithm::one_of_equal
:接受一个范围和一个值,并检查范围中是否有恰好一个元素等于该值。
在空范围的情况下,它们的行为与它们对应的原生类型完全相同。
三、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
接口,也就是说它接受第一个范围的begin
和end
,而第二个范围只接受begin
迭代器:
std::is_permutation(v1.begin(), v1.end(),
v2.begin());
因此,如果第二个集合比第一个小,那么算法会开心地一直查询第二个集合,直到查询到第一个集合的末尾,从而导致未定义的行为。结果是,必须确保第二个集合至少和第一个一样大。
但这在C++ 14中得到了纠正,它为两个集合添加了带有begin
和end
迭代器的重载。
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::mismatch
的1.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::map
、std::set
等排序关联容器兼容。
七、总结
本文详细介绍了STL的范围谓词的应用和优势。范围谓词是处理复杂数据逻辑的有力工具,通过定义和使用谓词函数可以轻松地筛选和操作数据集合。结合STL的算法,范围谓词可以帮助编写简洁、高效的代码,提高开发效率和代码可读性。