使用哈希完成简单的数据统计
功能实现
简介
最近在开发工作中,遇到一个需求,需要做一些数据统计相关的工作,就自己写了一个简单的通过哈希散列来做数据统计的算法,刚好最近比较空闲就写篇博客记录一下。
最终实现效果图如下:
这是考试成绩的统计图,其中X轴为分数区间,Y轴为学生人数
需求分析
1、我们需要一个分数区间而每一个分数区间中又包含具体的人数。
2、如果使用数组来做,这并不困难,只是对于前端来说变得更加困难了,因为还要计算区间值。对于后端来说我们最好将数据直接提供给前端,这样前端只负责展示就好了,分工明确才是最好的。
3、采用两个数组的方式,一个用来存储分数区间(String数组),一个用来存储区间中包含的人数,这是最合理的做法了。
存在的问题
区间划分
这需求看起来挺简单的不是吗?可实际上真的是这样吗?
考虑一下更加完善的情况,如果满分不是100分呢?如果满分是150分或者更高呢?如果满分是20分,或者更低呢?那么我们得出了两种情况可能出现的问题。
- 满分很低
- 满分很高
- 零分怎么办?
- 满分怎么表示?
1、如何划分区间?这是一个大问题,比如,当满分为100分时,按照效果图中的区间去划分就行了,这没有什么问题,但是如果满分只有50分,或者更加极端的情况,比如说小于10分,当然一般情况下是很难发生这样的事情,但是我们依旧要考虑到这种情况。
2、如果学生考试考了零分怎么办?我可不想给他单独列出来(我可能会被学生打死),我也是个学渣,没有学渣希望考零分还被公开参观......
WARNING:程序的边界值是一定要思考的,尽可能地让我们设计出来的代码可以适用于更多的场景。
软件设计
整体思路
把整个区间看作是一个大的容器,每个区间都是一个小的容器,如果满分为100时,应该这么划分:
这是容器初始状态:
索引(数组索引) | [0] | [1] | [2] | [3] | [4] | [5] | [6] | [7] | [8] | [9] |
---|---|---|---|---|---|---|---|---|---|---|
x-轴(分数区间) | [0-10] | [11-20] | [21-30] | [31-40] | [41-50] | [51-60] | [61-70] | [71-80] | [81-90] | [90+] |
y-轴(统计人数) | [0] | [0] | [0] | [0] | [0] | [0] | [0] | [0] | [0] | [0] |
数据区间
1、可以看到在索引0处,分数区间包含了11个数,因为还需要包含一个0,我们要处理11个数
2、索引9处,分数区间与别的格式都不同,所以我们针对这个也要做处理
总结出需要单独处理的情况:
- 第一个区间(包含11个数据)
- 最后一个区间(需要展示为90+)
放入数据
如何将数据放入对应的区间内呢?也就是说我们如何将分数通过hash算法将数据散列到合适的位置呢?
假设现在有一个成绩66,如何存入?我自己设计的思路是这样的:
因为一般情况下每个区间的间隔为10,那么通过公式[成绩 / 10]可以得到大致的区间,如 66 / 10 == 6,但是考虑到某些情况下这么做可能不可行,如 60 / 10 == 6 这很明显得到的是不正确的值,存在1个位的误差,观察 66 与 60 的相同之处与不同之处可以得到:60 是 10 的整数倍,而 66 是 10 的整数倍再多6,也就是说66 % 10是包含余数的。可以得到下边这份伪代码:
补充: 如果值是个位,那么我们就不应该再做偏差处理
如果: 总成绩 % 10 无余数 && 总成绩 > 10
偏差一位
否则:
不做偏差处理
编码
在当前思路中,if else语句都是可以被省略掉的
=>凡是if else语句中只包含一条逻辑,并且该逻辑是一个包含返回值的表达式,那么这个if语句就能被三目表达式代替<=
// totalScore为总成绩 scores为成绩表单
public static void Statistics(int totalScore, List<Integer> scores)
{
// 我们要得到的容器大小
int containerLength = totalScore / 10 + (totalScore % 10 == 0 ? 0 : 1);
String[] x = new String[containerLength];
int[] y = new int[containerLength];
int prefix = 0;
// 分数区间(分数区间虽不是核心代码,确要比统计数据更加麻烦)
for(int i = 0; i < x.length; i++)
{
// 得到区间字符串前缀
prefix = i == 0 ? 0 : (i * 10) + 1;
// 判断当前是否是最后一个元素
x[i] = i == x.length - 1 ? (prefix > 0 ? prefix - 1 + "+": "0-" + totalScore) : prefix + "-" + (i + 1) * 10;
}
// 统计人数(整个核心只有这两行代码.....)
for(int i = 0;i < scores.size();i++)
{
// 这一段是为了得到偏差量,这里只有 1 和 0
// ((scores.get(i) % 10 == 0) && (scores.get(i) > 0) ? 1 : 0)
y[(scores.get(i) / 10) - ((scores.get(i) % 10 == 0) && (scores.get(i) > 0) ? 1 : 0)]++;
}
}