Go语言实践[回顾]教程10--学习成绩统计的示例【中】

  • 基于整体需求优化上节代码
  • 需求增加按等级分类统计
  • 本节小结

基于整体需求优化上节代码

  在上一节中,是基于三个基本需求各自独立实现的逻辑,创建了分别完成各自任务的三个函数,然后依次执行。现在我们把上一节代码和需求整体看一下,各需求之间并不是完全独立的,是有一定的关联的。基于需求最后的整体目标,就是输出所有的统计信息,并没有对中间的单独子需求提出单独输出结果的要求。这样我们就可以针对统计及格人数、取前三名分数、整体排序三个子需求的关联性做逻辑调整,使程序运行效率更佳。

  先梳理一下优化的整体逻辑:
  ● 先使用标准库提供正序排序函数对数组进行排序,得到从小到大排列的正序排列的数组和切片
  ● 然后使用 for循环 将正序排序后的切片依次(正序循环)给 用于存放倒序结果的切片 从后向前赋值,这样效率比上一节中要用三个标准库排序函数效率更高,因为不用再排序了,正序结果反着顺序赋一遍值就是倒序结果了。
  ● 在这同一个 for循环 里,直接判断分数是否及格并且及格人数变量没有赋过值?如果成立,说明当前循环的位置以后都是及格的(因为是从小到大的正序排序后的数据),所以用数组长度减去循环计数变量的值,就刚好是后面剩下的元素个数,也就是及格人数,赋值给及格人数变量即可。
  及格人数变量初始化为 0 值,所以第一次判断分数及格能进入判断体内。但是经过刚才的赋值,下一轮循环虽然分数也是及格,但人数变量以不是初始化的值了,第二个条件不成立了,就不会再进入判断体了,及格人数变量就不会再次被修改了。也就是整个循环结束,及格人数变量只会被赋值一次,且是第一次循环到及格的那次。
  ● 上述循环结束后,就得到了一个倒序排列的成绩切片,那我们直接把这个切片中的前三个元素再次切片出来,就是前三名的成绩了。

  下面是根据上面逻辑优化后的代码:

// 统计来自 int 类型数组的数据,文件名 arr_data_count.go
package countimport ("fmt""score_count/data""sort"
)// ArrDataCount  对一维数组进行排序、统计60分及以上人数,前三名分数
func ArrDataCount() {// 获取数组长度赋值给 len 以免后面多处使用重复计算len := len(data.OnlyScore)// 引用数组全部内容给切片 sortArrsortArr := data.OnlyScore[:]// 使用标准库提供的正序排序函数给切片 sortArr 排序sort.Ints(sortArr)// 创建一个与源数据数组同样长同样类型的切片,用于存放倒序结果reverseArr := make([]int, len)passNum := 0 // 用于保存及格人数// 将排序好的数组颠倒顺序,使用for循环综合性能更好// 统计及格数量刚好也要用for循环,干脆放在一个排序后的循环里for i := 0; i < len; i++ {v := sortArr[i]reverseArr[len-i-1] = vif passNum == 0 && v >= 60 {passNum = len - i}}// 对倒序排序后的成绩数组直接切片前三个元素就刚好是前三名的成绩topThree := reverseArr[:3]fmt.Println("及格人数:", passNum)fmt.Printf("及格率为: %d%%\n", passNum*100/len)fmt.Println("前三分数:", topThree)fmt.Println("成绩正序:", sortArr)fmt.Println("成绩倒序:", reverseArr)
}

  第22行,使用 make() 函数直接创建一个长度与源数组长度一致的切片,而不是引用源数组的切片,这样就不会影响源数组本身的数据,且可以使用变量定义长度。

  第28行中的 len,替换了上一节代码该位置的 len(data.OnlyScore) 表达式,而是在第13行提前将 len(data.OnlyScore) 的结果赋值给了 len 变量。这样改动的目的也是优化性能。一方面本次优化代码中多处使用了源数组长度,如果都使用 len(data.OnlyScore),那么势必造成多次计算同一个值。另一方面,即使仅在 for 循环的条件一个地方使用,但实际计算次数是与循环的执行次数相同的,也就是每循环依次都要执行一次判断,执行判断就要计算一次。所以提前将结果保存到变量,避免了重复计算。

  第29行,这里的变量 v,创建的原因与前面创建变量 len 一样,就不多说了。

  第32行,使用 && 这个并且关系运算符,要判断两个条件。passNum == 0 这个条件是为了避免循环到所有及格的元素时都进入判断体的,以免造成 passNum 被多次赋值。

  第32行,[:3] 表示从数组或切片的第一个元素开始,连续取到 3 号索引为止,3 号本身不取,生成新的切片,也就是等于取前三个元素生成新的切片。

需求增加按等级分类统计

  现在需求扩展了,需要在原需求基础上增加按照等级分类统计一下每个分类有多少人。分类标准:100分-90分为A级,89分-75分为B级,74分-60分为C级,59分及其以下为D级。

  延续上面的逻辑,我们怎么实现这新增的需求呢?我们先来看实现后的源代码:

// 统计来自 int 类型数组的数据,文件名 arr_data_count.go
package countimport ("fmt""score_count/data""sort"
)// ArrDataCount  对一维数组进行排序、统计60分及以上人数,前三名分数
func ArrDataCount() {// 获取数组长度赋值给 len 以免后面多处使用重复计算len := len(data.OnlyScore)// 引用数组全部内容给切片 sortArrsortArr := data.OnlyScore[:]// 使用标准库提供的正序排序函数给切片 sortArr 排序sort.Ints(sortArr)// 创建一个与源数据数组同样长同样类型的切片,用于存放倒序结果reverseArr := make([]int, len)passNum := 0 // 用于保存及格人数levelA, levelB, levelC, levelD := 0, 0, 0, 0// 将排序好的数组颠倒顺序,使用for循环综合性能更好// 统计及格数量刚好也要用for循环,干脆放在一个排序后的循环里for i := 0; i < len; i++ {v := sortArr[i]reverseArr[len-i-1] = v// 利用正序排列特点,综合判断统计各分数段if levelD == 0 && v >= 60 {passNum = len - ilevelD = i} else if levelC == 0 && v >= 75 {levelC = i - levelD} else if levelB == 0 && v >= 90 {levelB = i - levelC - levelDlevelA = len - i}}// 对倒序排序后的成绩数组直接切片前三个元素就刚好是前三名的成绩topThree := reverseArr[:3]fmt.Println("及格人数:", passNum)fmt.Printf("及格率为: %d%%\n", passNum*100/len)fmt.Println("前三分数:", topThree)fmt.Println("成绩正序:", sortArr)fmt.Println("成绩倒序:", reverseArr)fmt.Println("A 级人数:", levelA)fmt.Println("B 级人数:", levelB)fmt.Println("C 级人数:", levelC)fmt.Println("D 级人数:", levelD)
}

  第26行,声明4个初始值为 0 的 int 类型的变量,用于保存A、B、C、D四个等级的人数。

  第36~43行,是在循环体内综合统计及格人数和四个等级人数的判断逻辑。这段代码是可以用如下代码代替的:

switch {case v <= 59:levelD++
case v >= 60 && v <= 74:levelC++
case v >= 75 && v <= 89:levelB++
case v >= 90:levelA++
}
passNum = levelA + levelB + levelC

  也或者换成下面这段:

switch {case v <= 59:levelD++
case v >= 60 && v <= 74:levelC++passNum++
case v >= 75 && v <= 89:levelB++passNum++
case v >= 90:levelA++passNum++
}

  也或者把这两段 switch case 语句换成 if else 语句实现,都是没问题的,也相对更好理解。但我上面完整的源代码中为什么没有这样写呢,有何区别?
  仔细看它们之间的计算次数差异,完整源代码中使用的方式,计算次数明显要少于后面两种举例可以替换的通常方式。这种差别如果数据量较小的话,几乎没啥影响,在数据量较大时,对性能的影响就相对明显了。所以这是为了提醒大家,同样完成一个需求,不同的编写逻辑和方式,对运行效率的高低是有差别的,所以流程控制、判断逻辑是很重要的,这与使用哪种编程语言关系不大。所以要深入分析,锻炼自己的编程思维,以便可以通过优化代码实现性能的提升。每处提高一点,一个项目的代码累加到一起可能就提高很多,所以在时间允许的情况下,尽量仔细分析代码逻辑,尽力优化到更优。

  第54~57行,是新增加的打印输出。

本节小结

  以下是对本节涉及的 Go 语言编程内容的归纳总结,方便记忆:

  ● make(),用于创建slice(切片)、map(映射,下一节会涉及)和 channel(后面章节讨论),并返回它们的实例,创建数组格式如下:
  make([]类型, 元素个数),如 make([]int, 5) 表示创建一个有5个int类型元素的切片。
  make([]类型, 元素个数, 预留最大长度),如 make([]int, 5, 10) 表示创建一个有5个int类型元素的切片,但动态增加元素个数到10个之内都不会触发重新分配内存(重新分配内存很影响性能),因为创建时在内存中按照总计10个元素的位置预留的。

  ● 当某个计算表达式多次使用时,建议创建一个变量保存该表达式的值,然后使用变量,不再多处使用同一个表达式。这样可以减少计算次数,提高运行效率。这对 for循环 中的条件语句中的判断值更为重要,如果在该位置使用计算表达式,实际执行时就会计算与循环次数相同的次数,提前将计算结果赋值给变量就不会有此情况。
.
.
上一节:Go/Golang语言学习实践[回顾]教程09–学习成绩统计的示例【上】

下一节:Go/Golang语言学习实践[回顾]教程11–学习成绩统计的示例【下】
.

Go语言实践[回顾]教程10--学习成绩统计的示例【中】相关推荐

  1. Go语言实践[回顾]教程09--学习成绩统计的示例【上】

    Go语言实践[回顾]教程09--学习成绩统计的示例[上] 在数组格式成绩数据中统计及格人数和及格率并取出前三名 创建只有成绩分数的数据源码文件 创建用于统计以 int 数组为数据源的源码文件 修改主文 ...

  2. Go语言实践[回顾]教程11--学习成绩统计的示例【下】

    Go语言实践[回顾]教程11--学习成绩统计的示例[下] 需求未变但源数据结构改为 map 类型 创建 map 类型的数据源文件 创建操作 map 类型数据进行统计排序的源文件 本节小结 需求未变但源 ...

  3. Go语言实践[回顾]教程08--通过时间判断时辰的示例【下】

    Go语言实践[回顾]教程08--通过时间判断时辰的示例[下] 封装函数向模块化开发方向修改 使用单个返回值函数的源代码 使用多个返回值函数的源代码 使用命名返回值函数的源代码 分离文件继续模块化实践 ...

  4. Go语言实践[回顾]教程15--详解Go语言的基本数据类型

    Go语言实践[回顾]教程15--详解Go语言的基本数据类型 布尔型(bool) 整数型(int) 浮点型(float) 复数型(complex) 字符串型(string) 字符型(byte / run ...

  5. Go语言实践[回顾]教程06--通过时间判断时辰的示例【上】

    Go语言实践[回顾]教程06--通过时间判断时辰的示例[上] 示例项目的需求 实现示例需求的源代码 使用 if 判断逻辑实现的源代码 使用 if else if 判断逻辑实现的源代码 使用 switc ...

  6. Go语言实践[回顾]教程23--详解Go语言函数的声明、变参、参数传递

    Go语言实践[回顾]教程23--详解Go语言函数的声明.变参.参数传递 函数的声明(定义) 函数的基本声明格式与调用 函数的变参(不定参) 值传递还是引用地址传递   函数是 Go 语言源代码的基本构 ...

  7. Go语言实践[回顾]教程03--Go语言的编译与运行的命令行

    Go语言实践[回顾]教程03--Go语言的编译与运行的命令行 Go语言是编译型静态语言 如何编译Go语言的源文件 如何执行(运行)编译后的文件 开发中如何编译后立即执行 总结 Go语言是编译型静态语言 ...

  8. Go语言实践[回顾]教程21--详解Go语言的空值、零值、nil

    Go语言实践[回顾]教程21--详解Go语言的空值.零值.nil Go 语言中 零值.空值.nil 概念之我见 基本数据类型的零值 复合数据类型的空值 通过实例体验零值.空值 nil 的差别 nil ...

  9. C语言实践(一)实现成绩输入排序和再插入排序

    C语言实践(一) 2020-09-14 20:35:52 项目一   要求 1.定义一个数组a[11],用以存放学生的成绩. 2.从键盘输入10个学生成绩  3.采用冒泡法,将学生成绩按照从高到低进行 ...

最新文章

  1. VB:如何选定文件或文件夹
  2. 20220211-CTF CRYPTO-base64-凯撒密码-摩丝密码--非常简单的三道题
  3. 编程兴趣真的是由“热情”驱动的吗?
  4. 最小新整数(信息学奥赛一本通-T1231)
  5. mysql存储引擎 索引优化_MySQL存储引擎,索引及基本优化策略
  6. Ubuntu 下安装tomcat和配置eclipse的遇到的问题的一点心得。
  7. 测透51汇编 51单片机所有程序(51学习的纪念)
  8. Hugging Face:成为机器学习界的“GitHub”
  9. 怎么查看笔记本内存条型号_内存条,手把手教你怎么查看内存条的型号
  10. 分析服务用多维度、多场景的用户分层,带您深度玩转精细化游戏运营
  11. odd在c语言中的意思,odd是什么意思
  12. ansible ---- 主机文件编写--在运行时显示自定义主机名称
  13. hive面试题总结(2020最新版)
  14. 好用的工商详细信息API推荐
  15. 【pycharm】WiFi密码破解【简单易学】
  16. 集成MOB 社会分享遇到的坑
  17. 如何准备pmp考试?(含备考资料)
  18. java.sql.SQLException: Unable to load authentication plugin ‘caching_sha2_password‘.
  19. 孙宇晨也许会迟到,但永远不会缺席!
  20. 知识图谱用户输入->摘要的查询语句

热门文章

  1. webApp开发心得
  2. 吴恩达机器学习作业1-线性回归
  3. 20200820美团运维工程师一面面经
  4. python3文件处理_简述 Python3 文件处理
  5. 批处理 IF-ERRORLEVEL使用方法
  6. apk开发语言!程序员如何自我学习和成长?成功入职阿里
  7. C# 数组转换成以指定字符连接的字符串
  8. 广东二级造价工程师《造价管理》真题解析
  9. Python入门(1)
  10. 用python写个华丽的登录页面