日撸 Java 三百行(10天,综合任务1)
注意:这里是JAVA自学与了解的同步笔记与记录,如有问题欢迎指正说明
关于消失的第09天
根据老师的任务清单的顺序来看,今天应该是第九天的while循环任务,但是while语句本身与C系列语言使用没有区别,循环语句的思想在第六天的for语句已经讲述了,前面几天通过矩阵也见识了循环语句的部分运用,因此今天跳过第九天直接进入第十天
一、要求与分析
原文对于问题的描述如下:
学生的成绩存放于一个矩阵,其中行表示学生,列表示科目。
如:第 0 行表示第 0 个学生的数学、语文、英语成绩。
要求:
进行学生成绩的随机生成, 区间为 [50, 100]. 找出成绩最好、最差的同学。但有挂科的同学不参加评比。
1.数据结构分析
每个学生的科目都是三科,不会多不会少,因此多个学生之间的记录是定长记录,因此可以考虑使用固定的一个m*3二维数组来记录所有学生的成绩。此外,题目要求记录成绩最好和最差的同学,因此,可以考虑使用一个数组记录每个同学的成绩和,然后通过成绩统计最高成绩与最低成绩。
注:就实现统计最好最差同学来说,这里的求和数组不是必要的,可以在设计学生成绩时就完成相关统计。因此代码是可以简化的,只是此处为了练习java代码内容故尽可能让数据结构完善。
2.方法准备
为避免问题模拟时考虑设置数据的麻烦,这里使用了数据数生成的基本方法。
java提供了Random库,此库中包含了一些系列可用的随机数生成工具。今天我们将使用如下函数:
java Random.nextInt()方法
public int nextInt(int n)
该方法的作用是生成一个随机的int值,该值介于[0,n)的区间,也就是0到n之间的随机int值,包含0而不包含n
有几个注意点是使用这个方法要注意的。
1). 此随机数的生成前闭后开的区间是,因此是无法生成n的
2). 此数据生成最低值是0,若要完成指定[a, b)的生成需要额外设计代码:
a + temRandom.nextInt(b - a);
3). 数据是整型,不包含浮点(虽然成绩兼容浮点值,我们这暂时就整型来说明)
二、代码实现分析
1.初始化与成绩矩阵
代码如下
// Step 1. Generate the date with n student and m course.
// Set these value by yourself.
int n = 10;
int m = 3;
int lowerBound = 50;
int upperBound = 101;
int threshold = 60;
// Here we have to use an object to generate random numbers
Random temRandom = new Random();
int[][] data = new int[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
data[i][j] = lowerBound + temRandom.nextInt(upperBound - lowerBound);
} // Of for j
} // Of for i
初始化定义的上限不是100而是101是因为随机函数的右界是个开区间,因此要取满需要额外加一。
此外,因为数据的大小是不确定的(虽然这里我们开始就给数据确定了值,但是理论上我们的学生数目与科目数目是可扩展的,这里初始值是为了模拟方便),所以采用的是动态分配的方法创建矩阵。
2.创建总成绩数组
代码如下
// Step 2.Compute the total score of each student.
int[] totalScores = new int[n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (data[i][j] < threshold) {
totalScores[i] = 0;
break;
} // Of if
totalScores[i] += data[i][j];
} // Of for j
} // Of for i
创建总成绩数组。再次强调,这个数组不是必须的,此处是为了区分过程为了理清楚原理特别使用的一个存储空间。
当某个学生成绩低于及格线时,其总分无效,依据题意,这里令其总成绩为0。
这里的0更多起了标记这个学生成绩无效的作用。
3.查询成绩极值
代码如下
// Step 3. Find the best and worst student.
// Typical initialization for index: invalid value.
int tempBestIndex = -1;
int tempWorstIndex = -1;
// Typical initialization for best and worst values.
// They must be replaced by valid values.
int tempBestScore = 0;
int tempWorstScore = m * upperBound + 1;
for (int i = 0; i < n; i++) {
// Do not consider failed students.
if (totalScores[i] == 0) {
continue;
} // Of if
if (tempBestScore < totalScores[i]) {
tempBestScore = totalScores[i];
tempBestIndex = i;
} // Of if
// Attention: This if statememt cannot be combined with the last one
// using "else if",because a student can be both the best and the
// worst. I found this bug while setting upperBound = 65.
if (tempWorstScore > totalScores[i]) {
tempWorstScore = totalScores[i];
tempWorstIndex = i;
} // Of if
} // Of for i
这里统计时将总分数为0(标记的不及格/通过)的学生跳过统计,符合题意要求
这里使用了编程中常见得不能再常见的求极值方案——通过给预求极值设定一个反方向的最极限值(说人话就是给最大值设定一个能取的最小值为初始值,给最小值设定一个能取的最大值为初始值)
这里能取的最小值就是0,最大值就是m * upperBound,因此只要保证最大值初始值小于等于0即可,最小值初始值大于等于m * upperBound即可。
但是要说明,这个最值的含义只有在元素集的数目大于0才有意义。
三、代码总览与数据运行分析
代码如下
package basic;
import java.util.Arrays;
import java.util.Random;
/**
* This is the tenth code, also the first task.
*
* @author Xingyi Zhang 1328365276@qq.com
*/
public class Task1 {
/**
*********************
* The entrance of the program.
*
* @param args Not used now.
*********************
*/
public static void main(String args[]) {
task1();
}// Of main
/**
*********************
* Method unit test.
*********************
*/
public static void task1() {
// Step 1. Generate the date with n student and m course.
// Set these value by yourself.
int n = 10;
int m = 3;
int lowerBound = 50;
int upperBound = 101; // Should be 100. I use this value for testing.
int threshold = 60;
// Here we have to use an object to generate random numbers
Random temRandom = new Random();
int[][] data = new int[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
data[i][j] = lowerBound + temRandom.nextInt(upperBound - lowerBound);
} // Of for j
} // Of for i
System.out.println("The data is:\r\n" + Arrays.deepToString(data));
// Step 2.Compute the total score of each student.
int[] totalScores = new int[n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (data[i][j] < threshold) {
totalScores[i] = 0;
break;
} // Of if
totalScores[i] += data[i][j];
} // Of for j
} // Of for i
System.out.println("The total scores are:\r\n" + Arrays.toString(totalScores));
// Step 3. Find the best and worst student.
// Typical initialization for index: invalid value.
int tempBestIndex = -1;
int tempWorstIndex = -1;
// Typical initialization for best and worst values.
// They must be replaced by valid values.
int tempBestScore = 0;
int tempWorstScore = m * upperBound + 1;
for (int i = 0; i < n; i++) {
// Do not consider failed students.
if (totalScores[i] == 0) {
continue;
} // Of if
if (tempBestScore < totalScores[i]) {
tempBestScore = totalScores[i];
tempBestIndex = i;
} // Of if
// Attention: This if statememt cannot be combined with the last one
// using "else if",because a student can be both the best and the
// worst. I found this bug while setting upperBound = 65.
if (tempWorstScore > totalScores[i]) {
tempWorstScore = totalScores[i];
tempWorstIndex = i;
} // Of if
} // Of for i
// Step 4. Output the sudent number and score.
if (tempBestIndex == -1) {
System.out.println("Cannot find best student. All student have failed.");
} else {
System.out.println("The best Student is No." + tempBestIndex + " with scores: "
+ Arrays.toString(data[tempBestIndex]));
} // Of if
if (tempWorstIndex == -1) {
System.out.println("Cannot find worst student. All student have failed.");
} else {
System.out.println("The worst Student is No." + tempWorstIndex + " with scores: "
+ Arrays.toString(data[tempWorstIndex]));
} // Of if
}// Of task1
}// Of class Task1
一次运行结果如下
在一次运行后数据如我们预期那样,对于不及格的学生,其总分是按照0处理的。
为了验证我们预先的特殊情况,我们先将随机生成的分数上限下调到66,让我们的分数随机数更容易落到不及格:
可以发现,因为每个人存在不及格学科,因此大家总分都不及格,因此,这里大家的分数在比较时都跳过了,出现了“无最高分,无最低分”的人为规定条件。
总结
本篇讲述了很多数据处理中比较常见的方法,包括各种极值的计算与分析。要注意的点就是灵活对于数据进行分析,合理利用数据结构即可,属于是基础的表格数据的应用。
但是引起我思考的是其中求极值的方法。本篇代码中我们使用的是一种给极值赋相反的极限值的一种无效赋值法,我提到了“ 数据集合元素要大于0时,极值才有意义 ”。
· 关于求最大最小值的联想
任何最大值最小值的含义都是在元素集大于0才有意义,这个不言而喻。
也是因为这个原因,还有另一种设置初值的方案是——将第一个元素的值设定给初始极值,并由此衍生——只记录最大值的下标而不记录最大值本身,然后下标初始值为0(就是默认为第一个元素的值)。如果这么做了,我们设定的变量似乎就更少了呢,而且,对于随机存取的数组来说,下标的信息熵比元素本身要多***(知道极值下标的话,在知道数据顺序之余还能以O(1)速度立马知道数据,而只知道数据最大值本身的话,那也就知道数据本身而已了)***。
但是事物要辩证看待,本代码使用的无效赋值方法其实可以让数据本身起到一种标签的作用,若比大小时候有特殊处理时(比如这里的0分不统计),通过判断极值是否有效也可以用以断定“ 是不是所有大小判断都已经跳过 ”这种极特殊情景。
当然,这些方法本质上的优化都是微乎其微,并无优劣之分。这里只是一种随意联想而已,而且在编写大型程序时,我们多添加一些变量其实也无关痛痒,可读性才是王道,没必要对数据使用搞得那么谨慎。