读过《编程珠玑》(<Programming Pearls>)的人应该还对开篇的Case记忆犹新,大概的场景是:

作者的一位在电话公司工作的朋友想要统计一段时间内不同的电话号码的个数,电话号码的数量很大,当时的内存很小,所以不能把所有的电话号码全部放到内存来去重统计,他的朋友很苦恼。

作者聪明的想到了用bit数组来解决问题,每个电话号码可以映射为bit数组的index,bit数组初始状态所有位为0,所有电话号码逐一处理:将bit数组对应位置为1,处理完之后统计bit数组中有多少个1即可。

示例:[0,1,0,0,0,1,0,...] 这个bit数组表示2和5存在

不得不说这种想法非常精妙,即减少了内存占用(8位电话号码如果全部放到内存需要381M(每个电话号码存成Integer占4Byte计算),而使用bit数组只需要11M),而且只需要两次循环就可以得到结果;

这是一个基数计数的问题,Cardinality:estimating the number of distinct elements.

1 Bitmap

上边提到的方式是使用bitmap,思路是将dataset中的每一个element映射到一个bit位,不允许冲突,所需要的内存空间大概为基数*1bit(上例中是100,000,000bit),并且计数精准;

但是当基数非常大时,即使bitmap内存也放不下!

好消息是如果不要求计数精准(允许一定范围内的误差),可以采用概率估算算法:

2 LC:Linear Counting

公式

其中:n是估算值,m是bitmap大小,Vn是bitmap中0出现的比率,比如0.1;

原理

使用固定大小的hashtable来存放dataset,可以想见:

  • dataset的基数越小,hashtable越空,当基数为0时hashtable所有的key都是空的
  • dataset的基数越大,hashtable越满,冲突也越多,当基数无穷大时hashtable所有的key都被占用并且每个key都有大量冲突

所以可以根据hashtable中key被占用的情况来估算dataset的基数,这里主要用到了对数曲线的特性(0<x<1这一段):

过程

初始化一个bitmap,所有位为0,dataset的每一个element都hash到bitmap的一个bit位,hash之后将对应的bit位置为1,hash允许冲突,最后根据bitmap中0的数量和bitmap大小由公式来估算基数;

注意:虽然LC用的也是bitmap,但是相比原始的bitmap算法,LC的bitmap大小可以比基数小很多,因为LC的映射允许冲突,另外可以设置bitmap大小来决定误差的大小;

推导过程详见参考

参考:A linear-time probabilistic counting algorithm for database applications

3 LLC:LogLog Counting

公式


其中:m是桶的数量,M为桶的集合,k是用于分桶的位数,ρ为bit数组中第一个为1的下标即index,E是估算值;

原理

来看抛硬币的过程,抛硬币的过程是伯努利Bernoulli过程,每次的结果要么是0,要么是1,并且概率均为1/2,假设一次抛硬币game定义为抛到1为止,可能第一次就抛到1(P=0.5),也可能前边抛了i次0最后才抛到1(P=1-0.5^i);抛硬币game玩的次数越多,越容易出现前边很多次都是0的情况,这是因为开头连续出现的0的个数越多,出现概率越小,需要尝试伯努利过程的次数就越多,所以可以利用概率根据结果(开头出现0的个数,即i)来反推出条件(game玩了多少次,即n=2^i,这个也很容易理解,比如彩票的中奖概率是1/10000,即平均买10000张彩票才能中一次奖,反过来说,如果有人中了一次奖,他很大概率上应该买了10000张彩票);

但是由于随机性的存在导致误差较大,所以通过将dataset分为m份,每份单独统计,最后取算数平均值的方式来降低随机性从而减小误差;

过程

dataset的每一个element先映射到一个bit数组,比如32位bit数组,将这个bit数组的1到k位的值作为桶的bucket_index(即第几个桶),将k+1到32位中第一个为1的index作为value放到桶中,如果桶里已经有value,桶会保存一个最大的value,数据集元素全部映射完之后,将所有桶的value取算数平均值,根据n=2^i,这样可以得到每个桶内的基数,再乘以m可以得到整个dataset的基数,公式最前边的α是修正参数;

比如k=2,则m=2^k=4,即4个桶,dataset中一个element映射的bit数组为[1,0,0,0,0,1,0,...],取前两位[1,0]对应的值是2,即第2个桶,取第3位之后的数组[0,0,0,1,0,...]可见第一个位1的index是3,将3放到桶2中,以此类推;

推导过程详见参考

参考:Loglog Counting of Large Cardinalities

4 HLLC:Hyper LogLog Counting

公式

原理

同LLC,只有一点不同:取均值的时候不使用算数平均数而改用调和平均数

推导过程详见参考

参考:HyperLogLog: the analysis of a near-optimal cardinality estimation algorithm

5 最优实践

公式

过程

在大量实践中根据各个参数和结果的情况进行调优,综合使用HLLC和LC等算法

参考:HyperLogLog in Practice: Algorithmic Engineering of a State of The Art Cardinality Estimation Algorithm

总结

目前HLLC在很多开源组件中都有应用,比如redis、druid等

其他:

https://research.neustar.biz/2012/10/25/sketch-of-the-day-hyperloglog-cornerstone-of-a-big-data-infrastructure/

【原创】大叔算法分享(4)Cardinality Estimate 基数计数概率算法的更多相关文章

  1. 【原创】大叔算法分享(5)聚类算法DBSCAN

    一 简介 DBSCAN:Density-based spatial clustering of applications with noise is a data clustering algorit ...

  2. 【原创】大叔经验分享(83)impala执行多个select distinct

    impala在一个select中执行多个count distinct时会报错,比如执行 select key, count(distinct column_a), count(distinct col ...

  3. 解读Cardinality Estimation<基数估计>算法(第一部分:基本概念)

    基数计数(cardinality counting)是实际应用中一种常见的计算场景,在数据分析.网络监控及数据库优化等领域都有相关需求.精确的基数计数算法由于种种原因,在面对大数据场景时往往力不从心, ...

  4. 【原创】经验分享:一个小小emoji尽然牵扯出来这么多东西?

    前言 之前也分享过很多工作中踩坑的经验: 一个线上问题的思考:Eureka注册中心集群如何实现客户端请求负载及故障转移? [原创]经验分享:一个Content-Length引发的血案(almost.. ...

  5. 基数计数——HyperLogLog

    所谓的基数计数就是统计一组元素中不重复的元素的个数.如统计某个网站的UV,或者用户搜索网站的关键词数量:再如对一个网站分别统计了三天的UV,现在需要知道这三天的UV总量是多少,怎么融合多个统计值. 1 ...

  6. 【原创】大叔案例分享(4)定位分析--见证scala的强大

    一 场景分析 定位分析广泛应用,比如室外基站定位,室内蓝牙beacon定位,室内wifi探针定位等,实现方式是三点定位 Trilateration 理想情况 这种理想情况要求3个基站‘同时’采集‘准确 ...

  7. 【原创】大叔经验分享(51)docker报错Exited (137)

    docker container启动失败,报错:Exited (137) *** ago,比如 Exited (137) 16 seconds ago 这时通过docker logs查不到任何日志,从 ...

  8. 【原创】大叔经验分享(50)hue访问mysql(librdbms)

    cloudera manager安装hue后想开启访问mysql(librdbms)需要在这里配置(hue_safety_valve.ini) 添加配置如下 [librdbms] # The RDBM ...

  9. 【原创】大叔经验分享(49)hue访问hdfs报错/hue访问oozie editor页面卡住

    hue中使用hue用户(hue admin)访问hdfs报错: Cannot access: /. Note: you are a Hue admin but not a HDFS superuser ...

随机推荐

  1. 使用Spring表达式语言进行装备--SpEL

    本文主要想记录最近的两个使用spring框架实现通过配置文件装备Bean,以及使用SpEL装备Bean. 1.使用配置文件装备Bean: 当我们写某些Bean的时候是希望这个Bean当中的属性是可以通 ...

  2. Django+Vue打造购物网站(五)

    注册和登陆 drf的认证 http://www.django-rest-framework.org/api-guide/authentication/ settings.py文件的配置 INSTALL ...

  3. BZOJ 1491: [NOI2007]社交网络(Floyd+暴力乱搞)

    题面: https://www.lydsy.com/JudgeOnline/problem.php?id=1491 题解: 先看数据范围,n<=100..欸可以乱搞了 首先因为小学学过的乘法原理 ...

  4. Eclipse 添加 lib (导入 .jar 包)

    1.将要添加的 jar 包直接拖到 WEB-INF/lib 目录里. 2.在项目上右键,依次选择[Build Path]--[Configure Build Path...]-- [Libraries ...

  5. Pandas系列(一)-Series详解

    一.初始Series Series 是一个带有 名称 和索引的一维数组,既然是数组,肯定要说到的就是数组中的元素类型,在 Series 中包含的数据类型可以是整数.浮点.字符串.Python对象等. ...

  6. solr的域

    solrhom相当于存放数据的地方,里面一个个文件相当于数据库,每个数据库里面有两个文件夹,一个data,一个conf,conf下有一个schema.xml文件,配置域,相当于表的字段. solr中内 ...

  7. Angular记录(1)

    文档资料 箭头函数--MDN:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_fun ...

  8. NOI-OJ 2.2 ID:8758 2的幂次方表示

    思路 可以把任意一个数转化为2^a+2^b+2^c+...+2^n 例如137的二进制为10001001,这就等效于2^7+2^3+2^0 以上结果如何通过程序循环处理呢?需要把数字n分解为上述公式, ...

  9. 第十六节:语法总结(3)(C#6.0和C#7.0新语法)

    一. C# 6.0 新语法 1. 自动属性初始化可以赋值 /// <summary> /// 自动属性初始化 /// </summary> public class UserI ...

  10. vue 开发微信小程序

    介绍 mpvue (github 地址请参见)是一个使用 Vue.js 开发小程序的前端框架.框架基于 Vue.js 核心,mpvue 修改了 Vue.js的 runtime 和 compiler 实 ...