C ++查找流数据的最大值

时间:2017-04-04 01:58:52

标签: c++

我有一个以随机时间间隔生成数值的数据流。现在我不断地需要获得在一定时间间隔内产生的流的最大值,例如, 100毫秒。

我天真的做法是拥有一对副本和一个最大变量。如果传入的值大于最大值,我清除deque,否则我遍历双端队列,如果现在 - ts大于我的回顾间隔我会忽略它,否则检查它是否大于之前的值(除非它是第一个值)。如果是这样,我保存迭代器。在循环之后,我将deque删除到(不包括我的最大迭代器)并设置新的最大值

我只是想知道是否有更智能更优雅的方法来使用不同的容器。理想情况下,我会坚持使用c ++标准库中的某个容器。

编辑: 有人建议优先队列(答案被删除)。在这种情况下,我是否会创建一对堆并告诉堆按值排序(或者如果不可能,则创建一个带有字段时间戳和值的结构并添加一个>运算符)。然后每当我得到最大值时,我会检查它是否已过期,如果是的话,弹出它并采取新的最大值...是否比我最初的方法更好?

编辑: 值不是唯一的

3 个答案:

答案 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()
  • 当您需要查询max元素时,请调用pop_front(),直到清除了所有过期值。然后在整个双端队列上致电std::max_element()

如果没有缓存未命中,它将具有与涉及priority_queuemultiset等的更复杂解决方案相同(或更好)的性能。

答案 2 :(得分:0)

保留两份数据副本:

  • std::deque< std::pair<timestamp, double> >
  • std::multiset<double>

当新值到来时:

  1. 将其添加到两个容器

  2. 通过检查双端队列的前面来清除过时的值。

    一个。如果双端队列的第一个值太旧(已过期),请将其从两个容器中删除。

    湾重复,直到双端队列中的第一个值不是过期值。

  3. 最大值位于多重集的结尾(a.k.a。rbegin())。

  4. 效果说明:

    多集插入和删除是O(log(N)),rbegin()是O(1)。如果性能确实是一个问题,并且您不介意使用boost,请考虑使用boost::container::flat_multiset,这是std::multiset的替代品,但更快(除非您的数据很大)。