读过《编程珠玑》(<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. IS创新之路 -- 都昌公司赋能型HIT企业发展之路

    ◆◆前言 近日,上海瑞金医院对我司表示:“我院从2000年开始自主开发医院信息系统,走出了一条可持续的信息化发展之路.已建成五大系统,284个子系统.但我院仍然坚持在努力推进以电子病历为核心医院信息化 ...

  2. Python Revisited Day 05(模块)

    目录 5.1 模块与包 5.1.1 包 5.2 Python 标准库概览 5.2.1 字符串处理 io.StringIO 类 5.2.3 命令行设计 5.2.4 数学与数字 5.2.5 时间与日期 5 ...

  3. MySQL之 InnoDB记录结构(转自掘金小册 MySQL是怎样运行的,版权归作者所有!)

    以下内容来自掘金小册 MySQL 是怎样运行的:从根儿上理解 MySQL 版权归原作者所有! 页是MySQL中磁盘和内存交互的基本单位,也是MySQL是管理存储空间的基本单位. 指定和修改行格式的语法 ...

  4. Vim简明学习

    前面的话 在linux云服务器上的编辑器默认是vim,类似于windows系统中的记事本,页面简洁,但入门不易.本文将介绍Vim的基础使用 操作模式 vim编辑器有三种模式: 1.命令模式(等待用户输 ...

  5. 使用System.out.printf()输出日志重定向到文件后显示混乱问题

    写了一个小工具,使用System.out.printf()输出日志,以方便使用者查看,在终端显示没有问题,但重定向到文件就有问题了,会出现一些很奇怪的乱序现象. 上网查询资料,判断应该是跟重定向和Li ...

  6. oracle表的操作SQL语句

    这篇文章的内容包括:表的増删改查,字段的増删改查,主键.外键.唯一.非空.默认约束的増删改   查看自己用户的所有表: select * from user_tab_comments;    www. ...

  7. 【bfs】麻将游戏

    题目 来自:yinzm的blog 在一种"麻将"游戏中,游戏是在一个有W*H格子的矩形平板上进行的.每个格子可以放置一个麻将牌,也可以不放(如图所示).玩家的目标是将平板上的所有可 ...

  8. macOS修改Dock隐藏速度

    延迟时间 修改延迟时间改为0,默认为1. defaults write com.apple.dock autohide-delay -int 0; killall Dock 修改为浮点数值,例如0.1 ...

  9. JS学习笔记Day10

    一.设置或获取元素对象中(标签中)的属性和自定义属性 对象.属性 对象['属性'] 对象.getAttribute('属性名') 对象.setAttribute('属性名','属性值'); 对象.re ...

  10. (BFS) leetcode 279. Perfect Squares

    Given a positive integer n, find the least number of perfect square numbers (for example, 1, 4, 9, 1 ...