获取位于特定区间的已排序值列表的子列表的最短方法
Shortest way to obtain a sublist of a sorted list of values lying in a certain interval
今天我问自己,在排序向量std::vector<double>
中获取所有值的最短代码是什么,这些值大于或等于a
,小于或等于b
。
我的第一个方法是这样的:
#include <vector>
#include <algorithm>
#include <iterator>
#include <iostream>
// Returns all values in sortedValues being greater equal start and smaller equal end;
std::vector<double> cutValues(const std::vector<double>& sortedValues, double start, double end) {
std::vector<double> ret;
auto startIter=std::lower_bound(sortedValues.begin(), sortedValues.end(), start);
auto stopIter = std::upper_bound(sortedValues.begin(), sortedValues.end(), end);
std::copy(startIter, stopIter, std::back_inserter(ret));
return ret;
}
int main(int argc, char **args) {
{
auto ret = cutValues({ 0.1,0.2,0.3 }, 0.1, 0.3);
std::copy(ret.begin(), ret.end(), std::ostream_iterator<double>(std::cout, ","));
std::cout << std::endl;
}
{
auto ret = cutValues({ 0.12,0.2,0.31 }, 0.1, 0.3);
std::copy(ret.begin(), ret.end(), std::ostream_iterator<double>(std::cout, ","));
std::cout << std::endl;
}
{
auto ret = cutValues({ 0.1,0.2,0.3 }, 0.2, 0.2);
std::copy(ret.begin(), ret.end(), std::ostream_iterator<double>(std::cout, ","));
std::cout << std::endl;
}
}
我的第二个想法非常简单,如下所示:
std::vector<double> cutValues2(const std::vector<double>& sortedValues, double start, double end) {
std::vector<double> ret;
std::copy_if(sortedValues.begin(), sortedValues.end(), std::back_inserter(ret), [&start, &end](auto v) { return v >= start && v <= end; });
return ret;
}
但是考虑到仅从非常大的载体中去除一小部分的情况,它可能有一些效率问题。
现在我问自己,有没有更好的方法可以做到这一点?
第一个版本稍有改动:
std::vector<double> cutValues(const std::vector<double>& sortedValues, double start, double end) {
auto startIter = std::lower_bound(sortedValues.begin(), sortedValues.end(), start);
auto stopIter = std::upper_bound(startIter, sortedValues.end(), end);
return std::vector<double>(startIter, stopIter);
}
不是更短,但通常更有效:使用std::lower_bound
找到"有趣"区间的开始,只要元素小于最大值,就继续复制;这样你就可以快速定位(O(log N((开始,即使是大向量,你也不会浪费时间再次进行二叉搜索寻找区间的结束 - 无论如何你都会在复制循环中找到它。
无论如何,可能的额外比较实际上是免费的,因为比较本身非常便宜double
分支被很好地预测(在副本结束之前总是相同的(,并且依赖于缓存中已有的数据;与额外的二进制搜索相比,后者在内存中"跳来跳去"(更差的缓存局部性(并且具有更难预测的分支。
std::vector<double> cutValues(const std::vector<double>& sortedValues, double minv, double maxv) {
std::vector<double> ret;
auto end = sortedValues.end();
for(auto it = std::lower_bound(sortedValues.begin(), end, minv);
it != end && *it <= max; ++it) {
ret.push_back(*it);
}
return ret;
}
这可以以类似于使用 STL 算法的cutValues2
方式重写,但我不确定它是否有多大的改进。
std::vector<double> cutValues(const std::vector<double>& sortedValues, double minv, double maxv) {
std::vector<double> ret;
std::copy_if(
std::lower_bound(sortedValues.begin(), sortedValues.end(), minv),
sortedValues.end(),
std::back_inserter(ret),
[=](double v) { return v <= maxv; });
return ret;
}
你可以编写你的函数 STL 风格,让它与任何类型的任何容器一起工作(恕我直言,它会更优雅(。
template <typename ForwardIt, typename T>
auto bounded_range(ForwardIt first, ForwardIt last, T lb, T ub) {
return std::pair{std::lower_bound(first, last, lb), std::upper_bound(first, last, ub)};
}
std::vector<double> v1{0.1, 0.1, 0.2, 0.3, 0.3, 0.5, 0.7, 0.9};
auto [beg, end] = bounded_range(std::begin(v), std::end(v), 0.3, 0.5);
std::vector<double> v2{beg, end};
// Or using 'std::make_from_tuple'.
auto v3 = std::make_from_tuple<std::vector<double>>(bounded_range(std::begin(v), std::end(v), 0.3, 0.5));
注意:示例对类模板和结构化绑定使用 C++17 模板参数推导。
一旦你知道你的间隔从哪里开始,就没有必要再次查看孔向量来找到结束迭代器。改为在start_iterator
后开始搜索
auto startIter = std::lower_bound(sortedValues.begin(), sortedValues.end(), start);
if (startIter == sortedValues.end()) {
// either the last element was inside the interval (return it) or not (return empty vector)
return *(--startIter) == start ? {start} : {};
}
auto stopIter = std::upper_bound(++startIter, sortedValues.end(), end);
// start from here
std::copy(--startIter, stopIter, std::back_inserter(ret));
其他优化将取决于内容。 例如,如果您知道区间内的最后一个元素位于向量的末尾,那么向后迭代它会更有意义。
您也可以考虑先检查间隔,以防止在 a 大于或等于 b 时不必要地执行。
相关文章:
- 没有为自己的结构调用列表推回方法
- Qt - QVector 和模型视图 - 从列表视图获取自定义类的最佳方法是什么?
- 如何将类成员方法的参数列表自动填充写入可变参数?
- 如何将成员函数作为参数传递并在派生对象上执行方法列表
- 有什么方法可以将元素添加到列表中,如图所示?
- 如何获取列表的每个对象并调用getName方法来打印其名称
- 处理从列表中删除指向对象的指针的"healthy"方法是什么?
- 从列表/向量制作嵌套 for 循环的最佳方法是什么?
- 生成包含给定类型的 N 个参数的可变参数列表的最佳方法?
- C++中有没有一种方法可以通过指定列表中的每个成员变量来构造对象
- 在 c++ 中迭代对列表的正确方法?
- 获取位于特定区间的已排序值列表的子列表的最短方法
- 提供初始值设定项列表构造函数的有效方法
- C++ - 类方法函数指针unordered_map的初始化器列表
- gRPC trace.cc 的方法 TraceFlagList::Add(TraceFlag* flag) 使其链接列表
- 对列表类中的泛型方法禁用编译器警告 2100,该泛型方法可能包含指针,也可能不包含指针
- 方法的参数列表出错
- 参数列表中的模板方法"=0"
- 在初始化列表中初始化数组的更好方法
- g++4.8.2上列表方法参数默认初始化时出错