需求分析

并没有什么需求分析
依然是成绩订阅那个程序,自从我开始重构,我发现下面这个方法写的太恶心了,或者说,太java了。

课程名 成绩 考试时间
A 90 20150721
B 92 20150723
C 90 20150729
D 97 20150801
E 95 20160121
F 88 20160121
G 90 20160125
H 85 20160721
I 85 20160721
J 80 20160921
K 90 20160921

这是所有的考试成绩,以一个形如下面的Hash结构为元素,组成的一维数组

1
2
3
4
5
[
{ name: A, grade: 90, date: 20150721 },
{ name: B, grade: 92, date: 20150723 },
{ name: C, grade: 90, date: 20150729 }
]

由于我订阅成绩通常只需要知道当前学期,而且计算GPA也是分学期计算,所以我需要把这些数据按学期分开来。
通常情况下,同一学期的考试都是在同一天,但考虑到有些同学挂科补考的情况,我必须要考虑到各种客户的 需求,有人问重修的怎么算?

算你个球球 算新学期的考试。

所以我的任务是把这个一维数组,按照学期,变成一个二维数组,第一层是不同的学期,第二层是某学期的所有成绩列表。

我把5.1~11.1之间的考试都当做是第二学期,因为有开学补考嘛。所以算法思路是,通过判断是否为第二学期,如果是将flag置为true,如果不是则置为false,于是循环的时候,前几个都是true,然后到E科目的时候,就变成了false,所以这时候判定到了新学期,把这个下标记录下来,放到res数组里,如此遍历一遍,就可以把转折点的下标记录到一个数组里。

然后再根据res中每一个下标位置,用slice方法切割成绩数组,压入目标数组。

于是写出来了下面这个我后来都看不懂的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def nest_with_date
res = []
z = 0
second_semester = 500...1100
flag = false
@guide_score_list.each do |s|
z += 1
date = s[:date].slice(-4..-1).to_i
res << (z - 1) unless flag == second_semester.include?(date)
flag = second_semester.include?(date)
end
res << z
res.length.times do |i|
@score_list << @guide_score_list.slice((i.zero? ? 0 : res[i - 1])...res[i])
end
end

Array#inject使用方法

于是我想到了最近看到的inject方法,就学习了一下姿势。
用法举例如下

1
2
3
4
5
list = [1,2,3,4]
result = list.inject(7) do |res, obj|
res + obj
end
result # => 17

当inject加了参数,这个参数就是第一次迭代时res的初始值,此时res==7,obj==1,也就是第一个元素的值。

执行res+obj,返回值为8,这个8赋值给res,obj指向下一个元素,也就是2

执行res+obj,返回值为10,这个10赋值给res,obj指向下一个

以此类推

最后迭代完毕时res值就是整个方法的返回值,赋值给result变量就行了。
如果inject没有传参数,那么res的初始值就是list的第一个元素值——1,obj指向第二个元素值——2,然后同上

魔改重构

1
2
3
4
5
6
7
8
9
10
def nest_with_date
# Magic rather than readable
@score_list = @guide_score_list.inject([[@guide_score_list.first]]) do |res, s|
next res if s == @guide_score_list.first
l = (5_01...11_01).include?(res.last.last[:date].slice(-4..-1).to_i)
n = (5_01...11_01).include?(s[:date].slice(-4..-1).to_i)
l ^ n ? res << Array[s] : res.last << s
res
end
end

原本14行代码被我强行魔改成6行,注释里还要写上拒绝可读性…
这个算法的思路就更清晰了,实际上算法没什么变化,只是强行吃口语法糖。

我给res赋值了初始值,就是

1
[[{ name: A, grade: 90, date: 20150721 }]]

ln变量分别表示上一个成绩和当前这个成绩的日期是否在第二学期内

当这两个值相等时则表示在同一个学期,就把这个数据压入最后一个学期的第二层

当这两个值不等时则表示在不同的学期,要新开辟一个学期,所以把这个成绩包装成数组,压入第一层的尾部

l ^ n则表示异或关系,就不多说了。

这样目的就达到了。

人生苦短,我选Ruby~