为了加快运算,之前评分都采用了其他人写的 Visual Basic 程序。界面大致是这样的:
选手数量:__________ 评委数量:__________
分数¹:____________________
[按钮] 输出结论 校验²:~~~~~~~~~~
选手结果:~~~~~~~~~~ 评委结果:~~~~~~~~~~
¹分数:各分数中间用逗号隔开,全角逗号将自动转为半角。就像这样:1,2,3
²校验:会输出所有分数的平均分。
个人觉得,这种程序的普适性较好,可以实现多场景的计分;不过,它不能实现边输入边查错,最近就有连续输错好几次,单输入就耗去了很长时间的情形。鉴于笔者和同学们最近在学习 C++ 程序设计,尽管目前只能写出控制台程序(Console Application),但仍然有自己另写程序的想法。
计分规则是这样的:
评委得到纸条,按照选手编号(而非出场顺序)为选手打分。这个“分数”实际上是一种排名;所以 1 代表最好。一个评委不能为不同的选手打相同的名次;也就是说,像 12335 这样的评分是无效的。
在最终算总分的时候,需要减去一个最高分、一个最低分——抱歉笔者没能在刚开始就解释清楚。
若想了解写程序的更多动机,读者可以跳转到文末。
好了,话不多说,先上源代码:
/*
vs 编译程序做到不依赖 Visual C++ 库
https://blog.csdn.net/u010177286/article/details/41044665
dev-cpp 编译程序做到不依赖库
https://zhidao.baidu.com/question/209400460.html
二维向量,大约在 35 行
https://blog.csdn.net/yuanjilai/article/details/7321484
本程序使用 Visual Studio 2019 开发
*/
//本程序结合了多位同学的智慧,在此一并表示感谢
#include <fstream>
#include <iomanip>
#include <iostream>
#include <vector>
using namespace std;
int main() {
system("title 心理课分数计算 by 电子信息类 2019");
system("color 70");
cout << "\n\n输入【选手】的数量,\n完成后按下回车。\n";
int n, m;
cin >> n;
cout << "\n输入【评委】的数量,\n完成后按下回车。\n";
cin >> m;
system("cls");
cout << "\n【选手】人数为 " << n << ",\n【评委】人数为 " << m
<< ",\n若有误,请重新打开程序。\n\n";
if (n >= 10 || n <= 0) { //若选手个数超出 10 个,提醒换用程序
if (m <= 0)
cout << "错误:【评委】数量非法\n\n";
cout << "错误:【选手】数量超出个位数\n若实际确实超出,敬请换用程序。";
cout << "\n --------";
cout << "\n\n 换用程序:\n 输入分数时注意用逗号隔开。\n "
"评委间分数无需按回车。\n 例如:\n 1,5,4,3,2,5,4,2,1,3\n";
cout << "\n现可直接关闭窗口。";
while (1) {
getchar();
}
}
else if (m <= 0) {
cout << "错误:【评委】数量非法\n\n";
while (1) {
getchar();
}
}
cout << "假设有 i 个选手,请按下面的样子,每输入 i "
"个就按下【回车】。\n数字间【不需】用空格隔开。\n15342\n24153\n";
cout << "\n----- 核对后 开始你的输入 -----\n";
getchar();
const int m_2 = m + 2, n_ = n, m_1 = m + 1, n_1 = n + 1;
int a[m_2][n_] = { 0 }; // m+1 行、n 列的二维向量,其中 a[0]
// 存放选手最低分、a[m+1] 存放选手最高分
int pw[m_1][2] = { 0 }; //vector<vector<int>> pw(m + 1, vector<int>(2, 1)); //存储评委“得分”
int pw_o[m_1] = { 0 }; //vector<int> pw_o(m + 1);
int b[n_1] = { 0 }; //vector<int> b(n + 1); //作用是检查评委打分是否有重复
int score[n_] = { 0 }; //vector<int> score(n, 0); //各选手的分数总和
int score_s[n_] = { 0 }; //vector<int> score_s(n);
int score_r[n_] = { 0 }; //vector<int> score_r(n, 1); // s=sort, t=temp, p=prev, r=rank
int score_t, score_p, left, right, mid = 0;
// vs 里似乎不能通过 const int 实现“自定”大小的数组,故改用 vector
int cur = 0, num = 0, wrong = 0;
for (int i = 0; i < n; ++i) {
a[0][i] = n;
a[m + 1][i] = score_r[i] = 1;
score[i] = 0;
}
for (int i = 0; i <= m; ++i) {
pw[i][1] = 1;
}
while (1) {
//每轮中数据的“初始化”
++cur;
if (cur == m + 1) {
break;
}
cout << "评委" << setw(2) << cur << '/' << m << ":";
a[cur][n] = a[cur][n + 1] = num = wrong = 0;
for (int i = 1; i <= n; ++i) {
b[i] = 0;
}
//输入模块,不打扰用户的输入
for (; num < n; ++num) {
a[cur][num] = getchar() - '0';
if (b[a[cur][num]] || a[cur][num] <= 0 || a[cur][num] > n ||
a[cur][num] == -38) {
wrong = 1;
break;
}
else
++b[a[cur][num]];
}
if (!wrong)
a[cur][num] = getchar() - '0';
//对错误输入的处理
if (a[cur][num] != -38)
wrong = 1;
if (wrong == 1) {
--cur;
cout << " *** 输入有误 ***\n";
}
}
//减去最高分、最低分
for (int i = 0; i < n; ++i) {
//统计;边输入边统计将在输入最后一组数据时出问题……
for (cur = 1; cur <= m; ++cur) {
score[i] += a[cur][i];
if (a[0][i] > a[cur][i])
a[0][i] = a[cur][i];
if (a[m + 1][i] < a[cur][i])
a[m + 1][i] = a[cur][i];
}
}
for (int i = 0; i < n; ++i) {
//相减
score[i] -= a[0][i];
score[i] -= a[m + 1][i];
}
//score_s = score;
for (int i = 0; i < n; ++i)
score_s[i] = score[i];
mid = 0;
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
if (score_s[i] > score_s[j]) { // score_s 系顺序排序
score_t = score_s[i];
score_s[i] = score_s[j];
score_s[j] = score_t;
}
}
if (i == 0)
score_p = score_s[i];
else if (score_s[i] == score_p)
score_r[i] = score_r[i - 1];
else {
score_r[i] = i + 1;
score_p = score_s[i];
}
}
system("cls");
cout << "\n\n去除了 1 个最高分、1 个最低分……\n";
cout << "\n----- 心理健康教育 【选手】榜 -----\n";
for (int i = 0; i < n; ++i) {
cout << "选手 " << i + 1 << ":总等级 " << setw(3) << score[i] << " 排名 ";
//中序查找法找到对应数字以便输出排名
left = 0, right = n - 1;
while (left <= right) {
mid = (left + right) / 2;
if (score[i] == score_s[mid])
break;
else if (score[i] > score_s[mid])
++left;
else
--right;
}
b[i] = score_r[mid]; //将排名存起来,以便计算评委榜
cout << setw(2) << score_r[mid] << '\n';
}
for (int i = 1; i <= m; ++i) {
pw_o[i] = 0;
for (int j = 0; j < n; ++j)
pw_o[i] += ((b[j] - a[i][j]) * (b[j] - a[i][j]));
pw[i][0] = pw_o[i];
}
for (int i = 1; i <= m; ++i) {
for (int j = i + 1; j <= m; ++j)
if (pw[i][0] > pw[j][0]) {
score_t = pw[i][0];
pw[i][0] = pw[j][0];
pw[j][0] = score_t;
}
if (i == 1)
score_p = pw[i][0];
else if (score_p == pw[i][0])
pw[i][1] = pw[i - 1][1];
else {
pw[i][1] = i;
score_p = pw[i][0];
}
}
cout << "\n----- 心理健康教育 【评委】榜 -----\n";
for (int i = 1; i <= m; ++i) {
cout << "评委" << setw(2) << i << " (";
for (int j = 0; j < n; ++j) {
cout << a[i][j];
}
cout << "):方差 " << setw(3) << pw_o[i] << " 排名 ";
//中序查找法找到对应数字以便输出排名
left = 1, right = m;
while (left <= right) {
mid = (left + right) / 2;
if (pw_o[i] == pw[mid][0])
break;
else if (pw_o[i] > pw[mid][0])
++left;
else
--right;
}
cout << setw(2) << pw[mid][1] << '\n';
}
cout << "\n请在【记录好结果】后,选择“直接关窗口”或“按下回车”。\n\n【测试功能"
"】若想做一致性检验,你可以按下回车。\n然后,可将弹窗内容复制到“换用"
"的程序”上。";
getchar();
system("cd /d %temp%");
fstream file_w("NEUQ_DX_2019_output.txt", ios::out);
for (int i = 1; i <= m; ++i) //评委数量为 m
for (int j = 0; j < n; ++j) { //选手数量为 n
if (!(i == 1 && j == 0))
file_w << ',';
file_w << a[i][j];
}
file_w.close();
system("notepad.exe NEUQ_DX_2019_output.txt");
return 0;
}
下面是大佬发现的 bug,或者程序更新日志。
- 若有 5 个选手(n = 5),在某一评委处输入 555555 再回车,会使得后续所有正确输入都会识别成错误。定位在第 70 行左右。
for (; num < n; ++num) {
a[cur][num] = getchar() - '0';
if (b[a[cur][num]] || a[cur][num] <= 0 || a[cur][num] > n || a[cur][num] == -38)
{ //原先没有 a[cur][num] == -38 的判断条件
wrong = 1; break; //原先没有 break
}
else
++b[a[cur][num]];
}
if (!wrong) //原先没有这个 if 判断
a[cur][num] = getchar() - '0';
- 改用 mingw g++ 7.3.0 进行编译,解决调用 system 函数崩溃的问题;同时放弃对 32 位的支持。
- 增添文件操作,以便用原程序做一致性检验。为测试功能,定位在代码末尾。
- 由于对二维 vector 尚不清楚,笔者暂不能手动释放二维 vector 的内存;而执行默认析构函数的时候会出故障。已在更换编译器的前提下,弃用它;并对代码做了相应更改。
//定位在 50 行左右,下为先前定义“数组”、并赋予初值时的代码
vector<vector<int>> a(
m + 2,
vector<int>(n)); // m+1 行、n 列的二维向量,其中 a[0]
// 存放选手最低分、a[m+1] 存放选手最高分
vector<vector<int>> pw(m + 1, vector<int>(2, 1)); //存储评委“得分”
vector<int> pw_o(m + 1);
vector<int> b(n + 1); //作用是检查评委打分是否有重复
vector<int> score(n, 0); //各选手的分数总和
vector<int> score_s(n);
vector<int> score_r(n, 1); // s=sort, t=temp, p=prev, r=rank
int score_t, score_p, left, right, mid = 0;
// vs 里似乎不能通过 const int 实现“自定”大小的数组,故改用 vector
//定位在 125 行左右,原先用
score_s = score; //现需逐个复制
下面是笔者写程序的若干感想。
- 原本想用 system 函数实现文件的打开,但在 Visual Studio 2019 的编译下,其在笔者的 Windows 10 1909 的电脑里大概率崩溃。为支持此功能,笔者将改用 g++ 7.3.0 编译,并因此放弃对 32 位系统的支持。
- 要定义“自定义大小”的数组,Visual Studio 似乎不允许直接用传统数组的方法;于是用上了 vector(向量)。
int a;
cin >> a;
const int b = a;
int c[b]; //在 Visual Studio 默认不被允许
- 这段程序的技术不那么重要;值得读者借鉴的,是其较为人性化的界面——尽管写的是 Console Application。读者可以使用 Visual Studio 等工具尝试编译运行程序,体会其界面设计。
下面是一些 Q&A。
Q: 这还用写程序?Excel 它不香吗?
A: 确实如此;笔者也觉得用 Excel 更有逼格。无论是 Excel 还是 WPS 表格,这些程序都凝结了开发者的大量心血,汇聚各种数据统计功能于一身,不是我这个“小破程序”能比的。不过,就像上面所述,笔者和同学们正在学习 C++ 程序设计——抓住每一个练习写程序的机会,以锻炼自身的 C++ 开发能力。而且通过代码共享,同学们也可以提出改进意见,从中提升代码改进能力。
另外,尽管界面不太美观——毕竟它只是个黑漆漆的“控制台”;但笔者认为,单在判断评分的用途上,其比用 Excel 有一定优势。例如,用户的输入过程得到了简化。
Q: 无事献殷勤,写程序的真正动机到底在哪?
A: 根据笔者的一贯风格,这纯属无稽之谈。笔者写程序,纯粹出于帮助用户的真心。看到用户在之前的程序一遍遍输错之时,台下观众的焦急、台上主持的无奈,笔者便想到用自身的能力做点什么。
有疑虑的读者也不是说犯了什么大错;毕竟,我们在未来,还是要面对相对势利的社会。不过,用阴阳怪气来冷嘲热讽,甚至冒充本人,是无法让人接受的;建议趁大学四年,好好反思这种过激行为;也欢迎前往咨询室,同专业的心理辅导员交心。
Q: 你大佬,我这菜鸡就不管了。
A: 用来自嘲也挺 OK 的——笔者在其他科目(例如高数)也是如此。
根据笔者一贯风格,分享程序绝不是 show off。这段程序其实只用了《C++ 程序设计(第 3 版)》的前 7 章内容,加上 vector 向量的应用;有兴趣的读者也可以自己动手写程序。真正懂得 C++ 的同学,将有大概率使用“类”等面向对象的概念;这点是笔者将努力学习的。
为什么一个仅仅用上前 7 章内容的程序,就引来了注目?其实笔者同至少 30% 的读者处于同一水平,但这款程序是真正落到实际用途的,并且收获了用户的良好反馈,其中就会有种成就感在里边。同学们现在正与笔者一同完成 C++ 课程设计——里边的程序几乎都是有实际用途的,相信同学们在仔细完成课程设计后,会有相同的成就感。