虽然最近比较多用Matlab做事, 但还没怎么正式地写过几篇讲讲Matlab. 嗯, 我是不是还忘了什么. . . (Mathematica QAQ).

记起来之前写过一个Matlab与Excel交互处理表格的脚本, 于是又摸出了这篇博文. 事情是这样的, 作为一个苦逼的学习委员(基层干部)平时干得最多的事情当然是——打杂, 帮老师收个作业, 带个口信, 收收费什么的, 不过这些其中要说我比较讨厌的就是每次考试后的算绩点(好吧, 我承认我excel比较苦手), 不知道其他学校怎么样, 我们是学委担此重任. 话说计算机的同学们, 你们都能拉出表格了不顺便算个GPA?其实我写了才知道最恶心的其实是处理表格的格式, 算绩点一句话就够了. . .

本文的脚本应该没有什么其他人能用得上, 因为各种格式都是按照表格和要求设计的, 不过其中的一些处理表格的方法还是值得做个笔记的. 受闪轨启发, 我把这个计划称为——学委解放战线

老师的要求也不复杂, 可以总结为两点:

  • 1.算出每个同学绩点与排名, 并且填入到姓名后新建的两列内, 并计算班级平均绩点. (挂科按60算)
  • 2.筛选出低于60分不及格的成绩, 并用黄色填充.

表格样本的原格式如下(我随便找的, 不算滥用职权吧):

姓名我用序号代替了.

要用matlab来处理做到这些, 则需要解决:

  • 1.表格里的信息都是文本格式, 需要把数据与字符进行区分.
  • 2.有些课程是等级评分, 分优、良、及格等, 需要转换为对应百分制分值再计算.
  • 3.表格中包含了某些修读双学位或重修的额外课程, 如电气X班的表里还有修读金融学的同学, 而该班其他同学并不修这些金融学课程, 原则上计算绩点时, 这些课程是不算在内的.
  • 4.有些缓考的同学某课成绩标注的是’-‘, 需要区分出来, 并不计入绩点计算.
  • 5.matlab对表格的格式控制, 包括插入数据列、改变表格背景色等.
  • 6.matlab矩阵元素序号到excel表格序号的转换.
  • 7.先计算绩点, 然后计算排名.

主要思路是把处理表格格式和处理数据分开来做, 处理表格格式用actxserver(‘excel.application’)创建excel应用类来做, 操作方法与C#处理excel相同, 具体使用方法可以参考这里这里;表格的数据用xlsread读入处理.

另外做了一些强化, 包括:

  • 1.打印处理表格的基本信息和没有计入绩点计算的课程的修读人数情况, 便于查错.
  • 2.增设了几个可调变量, 包括判定为重修与双学位课程的修读人数阈值等.
  • 3.程序自动扫描当前文件夹下所有excel文档, 批量处理, 处理后文档输出到新建的Output文件夹.

下面是code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
% forfreedom.m
function forfreedom

%% 初始化 自定义参数设定
clc
clear
close all
%======================== 一般只需要在画线区域修改 ============================
stu_ppt = 0.2; % 当选课人数少于总人数的 stu_ppt * 100%, 认为该课为重修或双学位课程, 默认 20%
score_mdf = 60; % 挂科的按 score_mdf 来算绩点, 默认以 60分来算


%======================== 一般只需要在画线区域修改 ============================
disp('开始工作咯, 计时, 滴~');
tic

%% 读取excel文档
e = actxserver('excel.application'); % 建立excel数据类
path = pwd;
xlsfiles = dir(fullfile(path, '*.xls'));
disp(['这个目录下有' num2str(size(xlsfiles, 1)) '张表格']);

for j = 1 : size(xlsfiles)
disp(['正在处理第' num2str(j) '张表格>>>>>>>>>>>>>>>>>>>>>>>>>>>>>!']);
xls_file_name = xlsfiles(j).name;
WorkBook = e.Workbooks.Open([path '/' xls_file_name]);

sheets = e.ActiveWorkBook.Sheets;
sheet1 = sheets.Item(1); % 获得sheet1
% e.Visible = 1;

% 读入excel的数据
[~, xls] = xlsread([path '/' xls_file_name], 1);
[xls_n, xls_p] = size(xls);

%% 预处理 1.数据分割, 参数计算 2.去除成绩中的字符 3.去除重修、双学位科目数据
% 数据分割 参数计算
xls_title = xls{1, 1};
stu_num = xls_n - 5; % 学生人数
stu_nam = xls(6 : end, 4); %学生姓名
course_nam = xls(3, 5 : end - 3); % 课程名
course_cred = str2num(cell2mat(xls(4, 5 : end - 3))); %课程学分
score_matrix = xls(6 : end, 5 : end - 3); % 分数矩阵
[score_matrix_n, score_matrix_p] = size(score_matrix);
appendx = xls(6 : end, end - 2 : end); %最后三列

toc
fprintf('表格基本信息: \n表格标题: %s\n班级人数: %d\n总课程数: %d\n', ...
xls_title, stu_num, length(course_nam));

% 分数矩阵处理
% 非法字符处理
[score_matrix{strncmp({'优'}, score_matrix, 1)}] = deal('95.0'); % 处理 优 良 及格 不及格
[score_matrix{strncmp({'良'}, score_matrix, 1)}] = deal('85');
[score_matrix{strncmp({'及格'}, score_matrix, 1)}] = deal('65.0');
[score_matrix{strncmp({'不及格'}, score_matrix, 1)}] = deal('50');
[score_matrix{strncmp({'-'}, score_matrix, 1)}] = deal('-1'); % 处理 '-'
% 元胞数组 转 数值矩阵
score_matrix_num = cellfun(@str2num, score_matrix); % 计算绩点用
score_matrix_mark = score_matrix_num; %标注不及格成绩用

% 去除重修、双学位课程 规则: 有成绩的人数占班级总人数比例低于 stu_ppt * 100%
threshold = ceil(stu_num * stu_ppt); % 选课人数低于threshold认为要忽略课程
course_stu_num = sum(score_matrix_num ~= -1); % 选课人数
course_delete_mask = course_stu_num <= threshold;% 找到要删除的课程
course_delete_nam = course_nam(:, course_delete_mask); % 要删除课程的名字
course_delete_stu_num = course_stu_num(course_delete_mask);

toc
if (~isempty(course_delete_mask))
fprintf('忽略课程信息: \n有%d门课的成绩没有纳入计算绩点, 这些课可能是重修或双学位课程\n这些课程是:\n', ...
sum(course_delete_mask));
for i = 1 : sum(course_delete_mask)
fprintf('%-30s选课人数: %d\n', course_delete_nam{i}, course_delete_stu_num(i));
end
end


score_matrix_num(:, course_delete_mask) = []; % 删除课程分数数据
course_cred(:, course_delete_mask) = []; % 删除学分数据
course_nam(:, course_delete_mask) = []; % 删除课程名字

%% 计算绩点
score_mdf_mask = score_matrix_num < 60 & score_matrix_num >= 0; % 选出挂科的成绩
score_matrix_num(score_mdf_mask) = score_mdf; % 考试没过成绩修改
score_matrix_num(score_matrix_num < 0) = 50; % 没出成绩, 可能缺考, 先算50

% 计算 GPA
GPA = round((score_matrix_num - 50) * course_cred' ./ (sum(course_cred) * 10), 3);

%% 修改表结构 + 填入绩点 + 填入排名
sheet1.Range(['F1:F' num2str(xls_n)]).Insert; % 绩点
sheet1.Range(['F1:F' num2str(xls_n)]).Insert; % 排名
sheet1.Range('F5').Value = '绩点';
sheet1.Range('G5').Value = '排名';

sheet1.Range(['F6:F' num2str(6 + stu_num - 1)]).Value = GPA;
sheet1.Range(['F6:G' num2str(6 + stu_num)]).HorizontalAlignment = -4108; %居中
sheet1.Range(['F' num2str(6 + stu_num)]).Value = round(mean(GPA), 3);

[~, r] = sort(GPA, 'descend');
[~, r] = sort(r);
sheet1.Range(['G6:G' num2str(6 + stu_num - 1)]).Value = r;

%% 将未及格的课程标为黄色
score_matrix_mark_mask = score_matrix_mark < 60 & score_matrix_mark >=0;
[n, p] = find(score_matrix_mark_mask == 1);

for i = 1 : sum(score_matrix_mark_mask(:))
sheet1.Range(mark_score_excel_coor(n(i) + 5, p(i)+ 7)).Interior.ColorIndex = 6;
end

%% 保存excel
if ~exist('Output', 'dir')
mkdir('Output')
end
WorkBook.SaveAs([path '/Output/' xls_file_name]);

end
e.Quit;
e.delete;
toc
disp('呼~处理完毕*^-^*')
end

%% 坐标转换
function excel_coor = mark_score_excel_coor(n, p)

excel_coor = [ex(p) num2str(n)];

end

function excel_coor_p = ex(p)
if p > 26
s = ex(fix((p-1) / 26));
excel_coor_p = [s char(65 + mod(p-1, 26))];
else
excel_coor_p = char(65 + p - 1);
end
end

处理结果显示如下:

输出表格如下:

其中可能值得一说的是matlab矩阵索引坐标转换为excel表格索引的处理, 比如matlab中矩阵坐标(1, 2), 对应excel坐标是(1, B). 查了一圈好像没什么人碰到这个问题就自己写了. 其实主要要解决的是列坐标的转换问题. excel的列坐标序号是, 从A到Z, 然后是AA到AZ, 之后BA…, 我想你看出来了, 这本质上就是26进制的计数系统, 我用了一个递归算法实现了他.

刚写完, 然而…今年不用算绩点了. . .
.
.
.
.
.
.
.
.
.
終わり