在使用lower_bound和upper_bound时,想自定义比较函数,在这个过程中出现了参数定义顺序导致的错误问题,于是查找学习了下自定义比较函数需要符合的规则。
目录
1 lower_bound和upper_bound函数
1.1 lower_bound
1.2 upper_bound
2 问题产生
3 问题研究及原因
4 总结
1 lower_bound和upper_bound函数
1.1 lower_bound
lower_bound
函数返回一个迭代器,指向容器中第一个大于或等于给定值的元素。如果容器中没有这样的元素,则返回容器的 end()
迭代器。
语法:
iterator lower_bound(iterator begin, iterator end, const value_type& value);
iterator lower_bound(iterator begin, iterator end, const value_type& value, Compare compare);
参数:
begin
和end
:指定要搜索的容器的范围。value
:要查找的值。- compare:比较函数。
返回值:
一个迭代器,指向容器中第一个大于或等于 value
的元素。如果容器中没有这样的元素,则返回 end()
迭代器。
示例:
std::vector<int> v = {1, 4, 5, 7, 9};
// 查找第一个大于或等于 4 的元素
auto it = std::lower_bound(v.begin(), v.end(), 4);
// 输出迭代器指向的元素
std::cout << *it << std::endl; // 输出 4
1.2 upper_bound
upper_bound
函数返回一个迭代器,指向容器中第一个大于给定值的元素。如果容器中没有这样的元素,则返回容器的 end()
迭代器。
语法:
iterator upper_bound(iterator begin, iterator end, const value_type& value);
iterator upper_bound(iterator begin, iterator end, const value_type& value,Compare compare);
参数:
begin
和end
:指定要搜索的容器的范围。value
:要查找的值。
返回值:
一个迭代器,指向容器中第一个大于 value
的元素。如果容器中没有这样的元素,则返回 end()
迭代器。
示例:
std::vector<int> v = {1, 4, 5, 7, 9};
// 查找第一个大于 4 的元素
auto it = std::upper_bound(v.begin(), v.end(), 4);
// 输出迭代器指向的元素
std::cout << *it << std::endl; // 输出 5
从上面示例中可以看到二者的区别:
lower_bound
查找第一个大于或等于给定值的元素,- 而
upper_bound
查找第一个大于给定值的元素。
起初,我认为二者的区别仅仅在此,但很快,就遇到了新的问题。
2 问题产生
观察这两行代码:
auto left = lower_bound(intervals.begin(),intervals.end(),newInterval[0],[](auto& a, int b) { return a[1] < b; });
auto right = upper_bound(left,intervals.end(),newInterval[1],[](int b,auto& a) { return a[0] > b; });
或许乍一看没什么特别的,但是强迫症一定能看到问题所在:两个匿名函数定义的参数顺序太不整齐了。
但当我尝试交换参数顺序使其看起来整齐一些,啪的一下,编译出错。
In file included from prog_joined.cpp:1:
In file included from /leetcode/precompiled/headers.h:13:
In file included from /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/cmath:1935:
In file included from /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/specfun.h:45:
In file included from /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_algobase.h:71:
/usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/predefined_ops.h:240:16: error: no matching function for call to object of type '(lambda at prog_joined.cpp:14:70)'
231 | { return bool(_M_comp(__val, *__it)); }
| ^~~~~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_algo.h:2041:8: note: in instantiation of function template specialization '__gnu_cxx::__ops::_Val_comp_iter<(lambda at prog_joined.cpp:14:70)>::operator()<const int, __gnu_cxx::__normal_iterator<std::vector<int> *, std::vector<std::vector<int>>>>' requested here
2032 | if (__comp(__val, __middle))
| ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_algo.h:2108:19: note: in instantiation of function template specialization 'std::__upper_bound<__gnu_cxx::__normal_iterator<std::vector<int> *, std::vector<std::vector<int>>>, int, __gnu_cxx::__ops::_Val_comp_iter<(lambda at prog_joined.cpp:14:70)>>' requested here
2099 | return std::__upper_bound(__first, __last, __val,
| ^
Line 5: Char 22: note: in instantiation of function template specialization 'std::upper_bound<__gnu_cxx::__normal_iterator<std::vector<int> *, std::vector<std::vector<int>>>, int, (lambda at solution.cpp:14:70)>' requested here
5 | auto right = upper_bound(left,intervals.end(),newInterval[1],[](auto& a, int b) { return a[0] > b; });
| ^
Line 5: Char 70: note: candidate function template not viable: no known conversion from 'std::vector<int>' to 'int' for 2nd argument
5 | auto right = upper_bound(left,intervals.end(),newInterval[1],[](auto& a, int b) { return a[0] > b; });
| ^ ~~~~~
1 error generated.
3 问题研究及原因
难道我们定义的匿名函数都不可以随便定义吗?
并非如此,将匿名函数拉出upper_bound和lower_bound放到其他位置,运行的好好的。
那基本可以确定问题处在upper_bound和lower_bound上。
这里要理解像这样一条语句,究竟是如何运行的?编译器怎么知道我们的a和b究竟是什么东西?
auto left = lower_bound(intervals.begin(),intervals.end(),newInterval[0],[](auto& a, int b) { return a[1] < b; });
那么就涉及到了lower_bound的具体实现。
可以想到的是,其内部将intervals这个容器的起始到结尾作为需要比较的对象,newInterval[0]这个值作为目标值。
那它又如何确定a对应的intervals容器还是newInterval[0]这个值?
答案是约定,它需要约定我们传入参数的顺序,也就是出现问题的这个匿名函数参数顺序的地方。
来看lower_bound的源码:
template<class ForwardIt, class T, class Compare>
ForwardIt lower_bound(ForwardIt first, ForwardIt last, const T& value, Compare comp)
{
ForwardIt it;
typename std::iterator_traits<ForwardIt>::difference_type count, step;
count = std::distance(first, last);
while (count > 0)
{
it = first;
step = count / 2;
std::advance(it, step);
if (comp(*it, value))
{
first = ++it;
count -= step + 1;
}
else
count = step;
}
return first;
}
注意这一句:
comp(*it, value)
它约定了比较函数的第一个参数是容器内容,第二个参数是目标值。
那upper_bound呢?
template<class ForwardIt, class T, class Compare>
ForwardIt upper_bound(ForwardIt first, ForwardIt last, const T& value, Compare comp)
{
ForwardIt it;
typename std::iterator_traits<ForwardIt>::difference_type count, step;
count = std::distance(first, last);
while (count > 0)
{
it = first;
step = count / 2;
std::advance(it, step);
if (!comp(value, *it))
{
first = ++it;
count -= step + 1;
}
else
count = step;
}
return first;
}
好嘛,正好是反过来的。
comp(value, *it)
4 总结
- 在自定义比较函数的时候需要知道函数原型,即约定的哪个参数代表什么含义,这样编译器才会知道我们传入的是什么。
- 对于upper_bound,比较函数的第一个参数是目标值,第二个参数是需要比较的容器内容。
- 对于lower_bound,比较函数的第一个参数是需要比较的容器内容,第二个参数是目标值。
- lower_bound和upper_bound的开发维护人员,一定不是强迫症。
- 附一张文心一言画得强迫症,好像emmm......一言难尽