C++ 如何在通过数组的一次过程中近似计算数组中不同值的计数

C++ 如何在通过数组的一次过程中近似计算数组中不同值的计数,c++,arrays,algorithm,search,C++,Arrays,Algorithm,Search,我有几个巨大的数组(数百万个++成员)。所有这些都是数字数组,它们没有排序(我不能这样做)。有些是uint8\u t,有些是uint16\u t/32/64。我想大致计算这些数组中不同值的计数。条件如下: 速度非常重要,我需要在一个数组中完成这一点,并且必须顺序地通过它(不能来回跳)(我在C++中做这件事,如果这很重要) 我不需要精确的计数。我想知道的是,如果它是一个uint32_t数组,如果有10或20个不同的数字,或者如果有数千或数百万 我有相当多的内存可以使用,但使用的越少越好 数组数据类

我有几个巨大的数组(数百万个++成员)。所有这些都是数字数组,它们没有排序(我不能这样做)。有些是
uint8\u t
,有些是
uint16\u t/32/64
。我想大致计算这些数组中不同值的计数。条件如下:

速度非常重要,我需要在一个数组中完成这一点,并且必须顺序地通过它(不能来回跳)(我在C++中做这件事,如果这很重要)
  • 我不需要精确的计数。我想知道的是,如果它是一个uint32_t数组,如果有10或20个不同的数字,或者如果有数千或数百万
  • 我有相当多的内存可以使用,但使用的越少越好
  • 数组数据类型越小,我需要的精度就越高
  • 我不介意STL,但如果没有它我也能做到,那就太好了(虽然没有提升,对不起)
  • 如果这种方法可以很容易地并行化,那就很酷了(但这不是强制性条件)
  • 完美输出示例:

    ArrayA [uint32_t, 3M members]: ~128 distinct values
    ArrayB [uint32_t, 9M members]: 100000+ distinct values
    ArrayC [uint8_t, 50K members]: 2-5 distinct values
    ArrayD [uint8_t, 700K members]: 64+ distinct values
    
    我理解有些限制似乎不合逻辑,但事实就是这样。 作为旁注,我还想要使用最多和最少的前X(3或10)值,但这要容易得多,我可以自己做。然而,如果有人也有这样的想法,请随意分享


    编辑:关于STL的一点澄清。如果您有使用它的解决方案,请发布它。不使用STL对我们来说只是一种奖励,我们不太喜欢它。然而,如果这是一个好的解决方案,它将被使用

    对于8位和16位的值,您只需制作一个每个值的计数表;每次写入以前为零的表条目时,都会发现一个不同的值


    对于较大的值,如果您对100000以上的计数不感兴趣,
    std::map
    是合适的,只要它足够快。如果这对您来说太慢,您可以编写自己的B-树。

    我很确定您可以通过以下方式完成:

  • 创建一个Bloom过滤器
  • 运行数组,将每个元素插入到过滤器中(这是一个“慢”O(n),因为它需要计算每个值的几个独立正派散列)
  • 计算Bloom过滤器中设置了多少位
  • 从滤波器的密度计算回对不同值数量的估计。我不知道我头脑中的计算方法,但是对布鲁姆过滤器理论的任何处理都会涉及到这一点,因为这对于过滤器在查找时给出假阳性的概率至关重要
  • 假设您同时计算前10个最频繁的值,那么如果有少于10个不同的值,您将确切知道它们是什么,并且不需要估计

    我认为“最常用”的问题是困难的(嗯,内存消耗)。假设您只需要最常用的前1个值。进一步假设数组中有1000万个条目,在前990万个条目之后,到目前为止,您看到的数字中没有一个出现超过10万次。那么到目前为止,您看到的任何值都可能是最常用的值,因为它们中的任何一个都可能在最后运行100k值。更糟糕的是,它们中的任何两个在最后都可能有5万个,在这种情况下,前990万个条目的计数就是它们之间的平局打破者。因此,为了在一次传递中计算出最常用的值,我认为您需要知道在990万次传递中出现的每个值的精确计数。在过去的10万年中,两个值几乎相等,你必须为这种反常的情况做好准备,因为如果发生这种情况,你就不允许倒带并再次检查两个相关的值。最终,您可以开始剔除值——如果有一个值的计数为5000,只剩下4000个条目需要检查,那么您可以剔除任何计数为1000或更少的值。但这并没有多大帮助


    因此,我可能遗漏了一些内容,但我认为在最坏的情况下,“最常用”的问题要求您为所看到的每个值保留一个计数,直到数组的末尾。因此,您也可以使用该计数集合计算出有多少个不同的值。

    一种即使对于较大的值也有效的方法是将它们分散到延迟分配的存储桶中

    假设您使用的是
    32
    位整数,创建
    2**32
    位数组相对来说是不切实际的(
    2**29
    字节,哼哼)。但是,我们可以假设
    2**16
    指针仍然合理(
    2**19
    字节:500kB),因此我们创建
    2**16
    存储桶(空指针)

    因此,最重要的想法是采用“稀疏”方法进行计数,并希望整数不会分散,因此许多桶指针将保持
    null

    typedef std::pair<int32_t, int32_t> Pair;
    typedef std::vector<Pair> Bucket;
    typedef std::vector<Bucket*> Vector;
    
    struct Comparator {
      bool operator()(Pair const& left, Pair const& right) const {
        return left.first < right.first;
      }
    };
    
    void add(Bucket& v, int32_t value) {
      Pair const pair(value, 1);
      Vector::iterator it = std::lower_bound(v.begin(), v.end(), pair, Compare());
      if (it == v.end() or it->first > value) {
        v.insert(it, pair);
        return;
      }
    
      it->second += 1;
    }
    
    void gather(Vector& v, int32_t const* begin, int32_t const* end) {
      for (; begin != end; ++begin) {
        uint16_t const index = *begin >> 16;
    
        Bucket*& bucket = v[index];
    
        if (bucket == 0) { bucket = new Bucket(); }
    
        add(*bucket, *begin);
      }
    }
    
    typedef std::pair对;
    typedef std::向量桶;
    typedef std::向量;
    结构比较器{
    布尔运算符()(成对常数和左、成对常数和右)常数{
    返回left.firstfirst>值){
    v、 插入(它,成对);
    返回;
    }
    它->秒+=1;
    }
    无效聚集(向量和向量、int32_t常量*开始、int32_t常量*结束){
    for(;begin!=end;++begin){
    uint16_t const index=*begin>>16;
    Bucket*&Bucket=v[索引];
    如果(bucket==0){bucket=new bucket();}
    添加(*桶,*开始);
    }
    }
    
    一旦你收集了数据,你就可以很容易地计算出不同值的数量,或者找到顶部或底部

    请注意:

    • 桶的数量完全是custo
      -- number of distinct
      SELECT COUNT(DISTINCT(n)) FROM @tmp
      -- top x
      SELECT TOP 10 n, COUNT(n) FROM @tmp GROUP BY n ORDER BY COUNT(n) DESC