我有一个以随机时间间隔生成数值的数据流。现在我不断地需要获得在一定时间间隔内产生的流的最大值,例如, 100毫秒。
我天真的做法是拥有一对副本和一个最大变量。如果传入的值大于最大值,我清除deque,否则我遍历双端队列,如果现在 - ts大于我的回顾间隔我会忽略它,否则检查它是否大于之前的值(除非它是第一个值)。如果是这样,我保存迭代器。在循环之后,我将deque删除到(不包括我的最大迭代器)并设置新的最大值
我只是想知道是否有更智能更优雅的方法来使用不同的容器。理想情况下,我会坚持使用c ++标准库中的某个容器。
编辑: 有人建议优先队列(答案被删除)。在这种情况下,我是否会创建一对堆并告诉堆按值排序(或者如果不可能,则创建一个带有字段时间戳和值的结构并添加一个>运算符)。然后每当我得到最大值时,我会检查它是否已过期,如果是的话,弹出它并采取新的最大值...是否比我最初的方法更好?
编辑: 值不是唯一的
答案 0 :(得分:3)
== new update ==
使用std :: pair(值)和时间戳的(最大)堆。在c ++ 11中,我讨论的堆是std :: priority_queue。如果您将值类型(而不是时间戳类型)设为对的第一个元素(即
std::pair<val_t,time_t>
而不是
std::pair<time_t,val_t>
),那么你甚至不需要一个自定义比较器,因为std :: pair比较会给你默认的行为(优先比较pair.first,只有在相等的情况下查看pair.second,使用std ::默认情况下更少 - &gt;为你提供第一个值类型的最大堆对数。)
将所有新值插入/推入(最大)堆中。最大的价值永远是最重要的。在轮询时,检查顶部样本的时间戳(sample.second)对now()减去新近窗口的年龄。如果它太旧了,就扔掉它。某些值序列可能导致堆隐藏最大值下的陈旧值。当堆超过特定大小阈值时,将其清空为新的阈值,同时过滤掉过期的值。这应该与新样本的到达率非常不成比例地与新近窗口大小相关。
感谢@chrise建议修改一个好主意,我过早地抛弃了==以前的更新==
我原来的回答只回答了部分问题。我建议使用max_heap(std :: priority_queue,默认情况下使用std :: less - &gt; max heap)来保持最大值。这不包括新近维护。你有两个不同的问题,搜索最大和有条件的成员资格。更改现有堆上的成员资格规则(到期标准)将使堆无效并为您提供难以根本原因的运行时错误。
相反,你可以使用一对对象列表(或deque,可能更好但我用std :: list做的例子)以及remove_if和一个跟踪最大值的谓词。这可以通过两种方式完成,使用lambdas,或者创建一个functor类。使用lambdas看起来像这样:
using sample = std::pair<unsigned,double>;
sample a{ 1,12.2 };
sample b{ 2,11.778 };
sample c{ 3,9.2 };
sample d{ 4,-2.6 };
sample e{ 5,10.1 };
std::list<sample> samples{ d,c,b,a };
cout << "list is: " << samples << endl << endl;
double maxval = -std::numeric_limits<double>::infinity();
unsigned cutoff = now() - timerange;
samples.remove_if([&maxval,cutoff](sample s)
{
//if older than cutoff, remove
if (s.first < cutoff) return true;
//otherwise, keep and update max
maxval = std::max(maxval, s.second);
return false;
});
cout << "max is: " << maxval << ", list is: " << samples << endl << endl;
请参阅http://ideone.com/O6UJPW以获取完整示例。
并使用仿函数类如下所示:
using sample = std::pair<unsigned,double>;
sample a{ 1,12.2 };
sample b{ 2,11.778 };
sample c{ 3,9.2 };
sample d{ 4,-2.6 };
sample e{ 5,10.1 };
std::list<sample> samples{ d,c,b,a };
cout << "original list is: " << samples << endl;
double maxval(-std::numeric_limits<double>::infinity());
//eliminate samples older than 2
using pred = PredRetainMax<sample>;
samples.remove_if(pred(now() - timerange, maxval));
cout << "max is: " << maxval << ", list is: " << samples << endl << endl;
其中谓词看起来像这样
template<class T>
struct PredRetainMax;
template<class Time, class Val>
struct PredRetainMax<std::pair<Time, Val>>
{
PredRetainMax(Time cutoff, Val& m) :_cutoff(cutoff), _max(m) {}
bool operator()(const std::pair<Time, Val>& s)
{
//if older than cutoff, remove
if (s.first < _cutoff) return true;
//otherwise, keep and update max
_max = std::max(_max, s.second);
return false;
}
Val get() { return _max; }
private:
Time _cutoff;
Val& _max;
};
请参阅http://ideone.com/qs153j了解完整示例
仿函数通过对外部maxval的引用进行实例化,因为stl将remove_if谓词作为副本,因此这是一种跟踪maxval的黑客攻击。
==以下原始回复==
使用堆。在c ++ 11中,它被称为std :: priority_queue。将所有新值插入/推入(最大)堆中。最大的价值永远是最重要的。
参见http://en.cppreference.com/w/cpp/container/priority_queue 对于一些有用的用法示例
答案 1 :(得分:3)
如果数据足够小以至于它很容易适合您的CPU缓存(例如,100万float
个值),那么我们就会过度思考这一点。
只需存储std::deque< std::pair<float, timestamp> >
。
push_back()
。pop_front()
,直到清除了所有过期值。然后在整个双端队列上致电std::max_element()
。如果没有缓存未命中,它将具有与涉及priority_queue
和multiset
等的更复杂解决方案相同(或更好)的性能。
答案 2 :(得分:0)
保留两份数据副本:
std::deque< std::pair<timestamp, double> >
std::multiset<double>
当新值到来时:
将其添加到两个容器
通过检查双端队列的前面来清除过时的值。
一个。如果双端队列的第一个值太旧(已过期),请将其从两个容器中删除。
湾重复,直到双端队列中的第一个值不是过期值。
最大值位于多重集的结尾(a.k.a。rbegin()
)。
效果说明:
多集插入和删除是O(log(N)),rbegin()
是O(1)。如果性能确实是一个问题,并且您不介意使用boost,请考虑使用boost::container::flat_multiset
,这是std::multiset
的替代品,但更快(除非您的数据很大)。