使用哈希完成简单的数据统计

功能实现

简介

最近在开发工作中,遇到一个需求,需要做一些数据统计相关的工作,就自己写了一个简单的通过哈希散列来做数据统计的算法,刚好最近比较空闲就写篇博客记录一下。

最终实现效果图如下:

效果图

这是考试成绩的统计图,其中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)]++;
        }
    }
posted @ 2021-07-14 15:10  Erosion2020  阅读(159)  评论(0)    收藏  举报