基本上我需要跟踪大量的计数器。我可以按名称递增或递减每个计数器。最简单的方法是使用哈希表,使用counter_name
作为key
,将其对应的count
作为value
的{{1}}。
计数器不需要100%准确,key
的近似值很好。所以我想知道是否有任何概率数据结构可以将N个计数器的空间复杂度降低到低于O(N),有点类似于HyperLogLog通过仅给出近似结果来减少计数N个项目的内存需求。有什么想法吗?
答案 0 :(得分:2)
在我看来,你要找的东西是Count-min sketch。
读取元素流a1,a2,a3,...,可以有a的元素 很多重复的元素,在任何时候它都会给你答案 以下问题:到目前为止你看过多少个ai元素。
基本上你的独特元素可以被射入你的计数器。 Countmin sketch允许您调整参数以交换记忆以获得准确性。
P.S。我描述了一些其他流行的probabilistic data structures here。
答案 1 :(得分:1)
Stefan Haustein的说法是正确的,名字可能比计数器占用更多的空间,你可以按照他的建议对某些名字进行优先排序,但如果没有,你可以考虑如何最好地存储这些名字。如果它们相当短(例如8个字符或更少),您可以考虑使用封闭的散列表将它们直接存储在存储桶中。如果它们很长,你可以将它们连续存储(NUL终止)在一块内存中,并在哈希表中将偏移存储到它们的第一个字符的那个块中。
对于计数器本身,可以使用概率方法来节省空间,如下所示:
template <typename T, typename Q = unsigned>
class Approx_Counter
{
public:
Approx_Counter() : n_(0) { }
Approx_Counter& operator++()
{
if (n_ < 2 || rand() % (operator Q()) == 0)
++n_;
return *this;
}
operator Q() const { return n_ < 2 ? n_ : 1 << n_; }
private:
T n_;
};
然后你可以使用例如Approx_Counter<unsigned char, unsigned long>
。如果你愿意,可以换掉rand()
用于C ++ 11生成器。
这个想法很简单:
n_
为0
时,++
绝对不会被调用n_
为1
时,++
肯定已被调用一次n_ >= 2
时,表示++
可能已被调用约2 n _ 次为了使最后一个含义与实际进行的++
调用的数量保持一致,每次调用都有1比2 n _ 再次实际递增n_
的机会。 / p>
只需确保您的rand()
或替代值返回的值远远大于您要跟踪的最大计数器值,否则您将过于频繁地获得rand() % (operator Q()) == 0
并且不正确地增加。
也就是说,如果你有一个指针或偏移量,那么一个较小的计数器并没有多大帮助,所以你也想要将计数器挤进水桶,另一个原因就是喜欢你自己的封闭散列实现,如果你真的需要收紧内存使用但想要坚持使用哈希表(trie是另一种可能性)。
以上在计数器空间中仍然是O(N),只是具有较小的常数。真的&lt;在O(N)选项中,您需要考虑密钥是否/如何相关,这样增加计数器可能会合理地影响多个密钥。到目前为止,您还没有向我们提供任何见解。
答案 2 :(得分:0)
名称可能比计数器占用更多空间。
拥有固定数量的计数器并且只保留计数最高的计数器,加上某种LRU机制以允许新计数器升至顶部怎么样?我想这真的取决于你的用例...