Skip to content

Commit 7f87c0c

Browse files
authored
refactor(course): course adjust (#56)
Move course adjust logics out of `Student.GetSemesterCourses` and expose `ApplyAdjustRules` to apply course adjust rules to course schedule rules. After this change, `Student.GetSemesterCourses` will not apply adjust rules by default, users should manually call `ApplyAdjustRules(scheduleRules, adjustRules)` to get final course schedule rules.
1 parent fd2566b commit 7f87c0c

3 files changed

Lines changed: 257 additions & 90 deletions

File tree

course.go

Lines changed: 102 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -235,96 +235,18 @@ func (s *Student) GetSemesterCourses(term, viewState, eventValidation string) ([
235235
single := !strings.Contains(classBasicInfo[1], "双")
236236
double := !strings.Contains(classBasicInfo[1], "单")
237237

238-
if len(adjustRules) == 0 {
239-
scheduleRules = append(scheduleRules, CourseScheduleRule{
240-
Location: location,
241-
StartClass: startClass,
242-
EndClass: endClass,
243-
StartWeek: startWeek,
244-
EndWeek: endWeek,
245-
Weekday: weekDay,
246-
Single: single,
247-
Double: double,
248-
Adjust: false,
249-
FromFullWeek: false,
250-
})
251-
} else {
252-
startWeek := utils.SafeAtoi(weekInfo[0])
253-
endWeek := utils.SafeAtoi(weekInfo[1])
254-
startClass := utils.SafeAtoi(classInfo[0])
255-
endClass := utils.SafeAtoi(classInfo[1])
256-
removedWeeks := []int{}
257-
258-
for _, adjustRule := range adjustRules {
259-
// 匹配是否是对应的调课信息
260-
if adjustRule.OldWeek < startWeek ||
261-
adjustRule.OldWeek > endWeek ||
262-
adjustRule.OldStartClass != startClass ||
263-
adjustRule.OldEndClass != endClass ||
264-
adjustRule.OldWeekday != weekDay {
265-
continue
266-
}
267-
268-
// 记录被去掉的周次
269-
removedWeeks = append(removedWeeks, adjustRule.OldWeek)
270-
271-
// 添加新的课程信息
272-
scheduleRules = append(scheduleRules, CourseScheduleRule{
273-
Location: adjustRule.NewLocation,
274-
StartClass: adjustRule.NewStartClass,
275-
EndClass: adjustRule.NewEndClass,
276-
StartWeek: adjustRule.NewWeek,
277-
EndWeek: adjustRule.NewWeek,
278-
Weekday: adjustRule.NewWeekday,
279-
Single: true,
280-
Double: true,
281-
Adjust: true, // 调课
282-
FromFullWeek: false,
283-
})
284-
}
285-
286-
sort.Ints(removedWeeks)
287-
// 去掉被调课的周次
288-
curStartWeek := startWeek
289-
290-
for _, removedWeek := range removedWeeks {
291-
if removedWeek == curStartWeek {
292-
curStartWeek++
293-
294-
continue
295-
}
296-
297-
scheduleRules = append(scheduleRules, CourseScheduleRule{
298-
Location: location,
299-
StartClass: startClass,
300-
EndClass: endClass,
301-
StartWeek: curStartWeek,
302-
EndWeek: removedWeek - 1,
303-
Weekday: weekDay,
304-
Single: single,
305-
Double: double,
306-
Adjust: false,
307-
FromFullWeek: false,
308-
})
309-
310-
curStartWeek = removedWeek + 1
311-
}
312-
313-
if curStartWeek <= endWeek {
314-
scheduleRules = append(scheduleRules, CourseScheduleRule{
315-
Location: location,
316-
StartClass: startClass,
317-
EndClass: endClass,
318-
StartWeek: curStartWeek,
319-
EndWeek: endWeek,
320-
Weekday: weekDay,
321-
Single: single,
322-
Double: double,
323-
Adjust: false,
324-
FromFullWeek: false,
325-
})
326-
}
327-
}
238+
scheduleRules = append(scheduleRules, CourseScheduleRule{
239+
Location: location,
240+
StartClass: startClass,
241+
EndClass: endClass,
242+
StartWeek: startWeek,
243+
EndWeek: endWeek,
244+
Weekday: weekDay,
245+
Single: single,
246+
Double: double,
247+
Adjust: false,
248+
FromFullWeek: false,
249+
})
328250
}
329251
}
330252

@@ -340,6 +262,7 @@ func (s *Student) GetSemesterCourses(term, viewState, eventValidation string) ([
340262
ExamType: utils.GetChineseCharacter(htmlquery.OutputHTML(info[6], false)),
341263
Teacher: htmlquery.OutputHTML(info[7], false),
342264
ScheduleRules: scheduleRules,
265+
AdjustRules: adjustRules,
343266
FullWeekScheduleRules: fullWeekScheduleRules,
344267
RawScheduleRules: strings.Join(courseInfo8, "\n"),
345268
RawExamTime: strings.TrimSpace(htmlquery.InnerText(info[9])),
@@ -367,3 +290,92 @@ func (s *Student) GetLocateDate() (*LocateDate, error) {
367290

368291
return &LocateDate{Week: matches[1], Year: matches[2], Term: matches[3]}, nil
369292
}
293+
294+
// ApplyAdjustRules 将调课规则应用到原始课程安排上,返回调整后的 ScheduleRules。
295+
// 该函数会将匹配到的调课周次从原有规则中移除,并添加调课后的新规则。
296+
func ApplyAdjustRules(scheduleRules []CourseScheduleRule, adjustRules []CourseAdjustRule) []CourseScheduleRule {
297+
if len(adjustRules) == 0 {
298+
return scheduleRules
299+
}
300+
301+
result := make([]CourseScheduleRule, 0, len(scheduleRules))
302+
303+
for _, rule := range scheduleRules {
304+
removedWeeks := []int{}
305+
306+
for _, adj := range adjustRules {
307+
// 匹配是否是对应的调课信息
308+
if adj.OldWeek < rule.StartWeek ||
309+
adj.OldWeek > rule.EndWeek ||
310+
adj.OldStartClass != rule.StartClass ||
311+
adj.OldEndClass != rule.EndClass ||
312+
adj.OldWeekday != rule.Weekday {
313+
continue
314+
}
315+
316+
// 记录被调课的周次,后续需要从原有规则中移除
317+
removedWeeks = append(removedWeeks, adj.OldWeek)
318+
319+
// 添加新的课程信息
320+
result = append(result, CourseScheduleRule{
321+
Location: adj.NewLocation,
322+
StartClass: adj.NewStartClass,
323+
EndClass: adj.NewEndClass,
324+
StartWeek: adj.NewWeek,
325+
EndWeek: adj.NewWeek,
326+
Weekday: adj.NewWeekday,
327+
Single: true,
328+
Double: true,
329+
Adjust: true,
330+
FromFullWeek: false,
331+
})
332+
}
333+
334+
if len(removedWeeks) == 0 {
335+
result = append(result, rule)
336+
continue
337+
}
338+
339+
sort.Ints(removedWeeks)
340+
// 去掉被调课的周次,并将剩余的周次按照连续的区间重新组合成新的规则
341+
curStartWeek := rule.StartWeek
342+
for _, removedWeek := range removedWeeks {
343+
if removedWeek == curStartWeek {
344+
curStartWeek++
345+
continue
346+
}
347+
348+
result = append(result, CourseScheduleRule{
349+
Location: rule.Location,
350+
StartClass: rule.StartClass,
351+
EndClass: rule.EndClass,
352+
StartWeek: curStartWeek,
353+
EndWeek: removedWeek - 1,
354+
Weekday: rule.Weekday,
355+
Single: rule.Single,
356+
Double: rule.Double,
357+
Adjust: false,
358+
FromFullWeek: rule.FromFullWeek,
359+
})
360+
361+
curStartWeek = removedWeek + 1
362+
}
363+
364+
if curStartWeek <= rule.EndWeek {
365+
result = append(result, CourseScheduleRule{
366+
Location: rule.Location,
367+
StartClass: rule.StartClass,
368+
EndClass: rule.EndClass,
369+
StartWeek: curStartWeek,
370+
EndWeek: rule.EndWeek,
371+
Weekday: rule.Weekday,
372+
Single: rule.Single,
373+
Double: rule.Double,
374+
Adjust: false,
375+
FromFullWeek: rule.FromFullWeek,
376+
})
377+
}
378+
}
379+
380+
return result
381+
}

jwch_test.go

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package jwch
1919
import (
2020
"fmt"
2121
"os"
22+
"reflect"
2223
"testing"
2324

2425
"github.com/west2-online/jwch/constants"
@@ -311,3 +312,156 @@ func TestGetLectures(t *testing.T) {
311312
t.Error(err)
312313
}
313314
}
315+
316+
func TestApplyAdjustRules(t *testing.T) {
317+
cases := []struct {
318+
name string
319+
rules []CourseScheduleRule
320+
adjusts []CourseAdjustRule
321+
expected []CourseScheduleRule
322+
}{
323+
{
324+
name: "NoAdjust",
325+
rules: []CourseScheduleRule{
326+
{Location: "旗山西1-206", StartClass: 3, EndClass: 4, StartWeek: 1, EndWeek: 16, Weekday: 1, Single: true, Double: true},
327+
},
328+
adjusts: nil,
329+
expected: []CourseScheduleRule{
330+
{Location: "旗山西1-206", StartClass: 3, EndClass: 4, StartWeek: 1, EndWeek: 16, Weekday: 1, Single: true, Double: true},
331+
},
332+
},
333+
{
334+
name: "EmptyAdjustRules",
335+
rules: []CourseScheduleRule{
336+
{Location: "旗山西1-206", StartClass: 3, EndClass: 4, StartWeek: 1, EndWeek: 16, Weekday: 1, Single: true, Double: true},
337+
},
338+
adjusts: []CourseAdjustRule{},
339+
expected: []CourseScheduleRule{
340+
{Location: "旗山西1-206", StartClass: 3, EndClass: 4, StartWeek: 1, EndWeek: 16, Weekday: 1, Single: true, Double: true},
341+
},
342+
},
343+
{
344+
name: "SingleAdjust",
345+
rules: []CourseScheduleRule{
346+
{Location: "铜盘A110", StartClass: 5, EndClass: 6, StartWeek: 5, EndWeek: 18, Weekday: 3, Single: true, Double: true},
347+
},
348+
adjusts: []CourseAdjustRule{
349+
{OldWeek: 6, OldWeekday: 3, OldStartClass: 5, OldEndClass: 6, NewWeek: 9, NewWeekday: 1, NewStartClass: 7, NewEndClass: 8, NewLocation: "旗山西1-206"},
350+
},
351+
expected: []CourseScheduleRule{
352+
{Location: "旗山西1-206", StartClass: 7, EndClass: 8, StartWeek: 9, EndWeek: 9, Weekday: 1, Single: true, Double: true, Adjust: true},
353+
{Location: "铜盘A110", StartClass: 5, EndClass: 6, StartWeek: 5, EndWeek: 5, Weekday: 3, Single: true, Double: true},
354+
{Location: "铜盘A110", StartClass: 5, EndClass: 6, StartWeek: 7, EndWeek: 18, Weekday: 3, Single: true, Double: true},
355+
},
356+
},
357+
{
358+
name: "AdjustFirstWeek",
359+
rules: []CourseScheduleRule{
360+
{Location: "铜盘A110", StartClass: 3, EndClass: 4, StartWeek: 1, EndWeek: 8, Weekday: 2, Single: true, Double: true},
361+
},
362+
adjusts: []CourseAdjustRule{
363+
{OldWeek: 1, OldWeekday: 2, OldStartClass: 3, OldEndClass: 4, NewWeek: 10, NewWeekday: 5, NewStartClass: 3, NewEndClass: 4, NewLocation: "旗山东3-101"},
364+
},
365+
expected: []CourseScheduleRule{
366+
{Location: "旗山东3-101", StartClass: 3, EndClass: 4, StartWeek: 10, EndWeek: 10, Weekday: 5, Single: true, Double: true, Adjust: true},
367+
{Location: "铜盘A110", StartClass: 3, EndClass: 4, StartWeek: 2, EndWeek: 8, Weekday: 2, Single: true, Double: true},
368+
},
369+
},
370+
{
371+
name: "AdjustLastWeek",
372+
rules: []CourseScheduleRule{
373+
{Location: "铜盘A110", StartClass: 1, EndClass: 2, StartWeek: 5, EndWeek: 10, Weekday: 4, Single: true, Double: true},
374+
},
375+
adjusts: []CourseAdjustRule{
376+
{OldWeek: 10, OldWeekday: 4, OldStartClass: 1, OldEndClass: 2, NewWeek: 12, NewWeekday: 3, NewStartClass: 1, NewEndClass: 2, NewLocation: "旗山西1-206"},
377+
},
378+
expected: []CourseScheduleRule{
379+
{Location: "旗山西1-206", StartClass: 1, EndClass: 2, StartWeek: 12, EndWeek: 12, Weekday: 3, Single: true, Double: true, Adjust: true},
380+
{Location: "铜盘A110", StartClass: 1, EndClass: 2, StartWeek: 5, EndWeek: 9, Weekday: 4, Single: true, Double: true},
381+
},
382+
},
383+
{
384+
name: "MultipleAdjusts",
385+
rules: []CourseScheduleRule{
386+
{Location: "铜盘A110", StartClass: 5, EndClass: 6, StartWeek: 5, EndWeek: 18, Weekday: 3, Single: true, Double: true},
387+
},
388+
adjusts: []CourseAdjustRule{
389+
{OldWeek: 6, OldWeekday: 3, OldStartClass: 5, OldEndClass: 6, NewWeek: 9, NewWeekday: 1, NewStartClass: 7, NewEndClass: 8, NewLocation: "旗山西1-206"},
390+
{OldWeek: 10, OldWeekday: 3, OldStartClass: 5, OldEndClass: 6, NewWeek: 12, NewWeekday: 2, NewStartClass: 5, NewEndClass: 6, NewLocation: "旗山东3-101"},
391+
},
392+
expected: []CourseScheduleRule{
393+
{Location: "旗山西1-206", StartClass: 7, EndClass: 8, StartWeek: 9, EndWeek: 9, Weekday: 1, Single: true, Double: true, Adjust: true},
394+
{Location: "旗山东3-101", StartClass: 5, EndClass: 6, StartWeek: 12, EndWeek: 12, Weekday: 2, Single: true, Double: true, Adjust: true},
395+
{Location: "铜盘A110", StartClass: 5, EndClass: 6, StartWeek: 5, EndWeek: 5, Weekday: 3, Single: true, Double: true},
396+
{Location: "铜盘A110", StartClass: 5, EndClass: 6, StartWeek: 7, EndWeek: 9, Weekday: 3, Single: true, Double: true},
397+
{Location: "铜盘A110", StartClass: 5, EndClass: 6, StartWeek: 11, EndWeek: 18, Weekday: 3, Single: true, Double: true},
398+
},
399+
},
400+
{
401+
name: "NoMatchingAdjust",
402+
rules: []CourseScheduleRule{
403+
{Location: "铜盘A110", StartClass: 3, EndClass: 4, StartWeek: 1, EndWeek: 16, Weekday: 1, Single: true, Double: true},
404+
},
405+
adjusts: []CourseAdjustRule{
406+
{OldWeek: 6, OldWeekday: 3, OldStartClass: 3, OldEndClass: 4, NewWeek: 9, NewWeekday: 1, NewStartClass: 7, NewEndClass: 8, NewLocation: "旗山西1-206"},
407+
},
408+
expected: []CourseScheduleRule{
409+
{Location: "铜盘A110", StartClass: 3, EndClass: 4, StartWeek: 1, EndWeek: 16, Weekday: 1, Single: true, Double: true},
410+
},
411+
},
412+
{
413+
name: "MultipleScheduleRules",
414+
rules: []CourseScheduleRule{
415+
{Location: "铜盘A110", StartClass: 3, EndClass: 4, StartWeek: 1, EndWeek: 16, Weekday: 1, Single: true, Double: true},
416+
{Location: "铜盘A508", StartClass: 7, EndClass: 8, StartWeek: 1, EndWeek: 16, Weekday: 5, Single: true, Double: true},
417+
},
418+
adjusts: []CourseAdjustRule{
419+
{OldWeek: 4, OldWeekday: 5, OldStartClass: 7, OldEndClass: 8, NewWeek: 5, NewWeekday: 2, NewStartClass: 7, NewEndClass: 8, NewLocation: "旗山东3-101"},
420+
},
421+
expected: []CourseScheduleRule{
422+
{Location: "铜盘A110", StartClass: 3, EndClass: 4, StartWeek: 1, EndWeek: 16, Weekday: 1, Single: true, Double: true},
423+
{Location: "旗山东3-101", StartClass: 7, EndClass: 8, StartWeek: 5, EndWeek: 5, Weekday: 2, Single: true, Double: true, Adjust: true},
424+
{Location: "铜盘A508", StartClass: 7, EndClass: 8, StartWeek: 1, EndWeek: 3, Weekday: 5, Single: true, Double: true},
425+
{Location: "铜盘A508", StartClass: 7, EndClass: 8, StartWeek: 5, EndWeek: 16, Weekday: 5, Single: true, Double: true},
426+
},
427+
},
428+
{
429+
name: "ConsecutiveWeeksRemoved",
430+
rules: []CourseScheduleRule{
431+
{Location: "铜盘A110", StartClass: 1, EndClass: 2, StartWeek: 1, EndWeek: 10, Weekday: 3, Single: true, Double: true},
432+
},
433+
adjusts: []CourseAdjustRule{
434+
{OldWeek: 5, OldWeekday: 3, OldStartClass: 1, OldEndClass: 2, NewWeek: 11, NewWeekday: 4, NewStartClass: 1, NewEndClass: 2, NewLocation: "旗山西1-206"},
435+
{OldWeek: 6, OldWeekday: 3, OldStartClass: 1, OldEndClass: 2, NewWeek: 12, NewWeekday: 4, NewStartClass: 1, NewEndClass: 2, NewLocation: "旗山西1-206"},
436+
},
437+
expected: []CourseScheduleRule{
438+
{Location: "旗山西1-206", StartClass: 1, EndClass: 2, StartWeek: 11, EndWeek: 11, Weekday: 4, Single: true, Double: true, Adjust: true},
439+
{Location: "旗山西1-206", StartClass: 1, EndClass: 2, StartWeek: 12, EndWeek: 12, Weekday: 4, Single: true, Double: true, Adjust: true},
440+
{Location: "铜盘A110", StartClass: 1, EndClass: 2, StartWeek: 1, EndWeek: 4, Weekday: 3, Single: true, Double: true},
441+
{Location: "铜盘A110", StartClass: 1, EndClass: 2, StartWeek: 7, EndWeek: 10, Weekday: 3, Single: true, Double: true},
442+
},
443+
},
444+
{
445+
name: "PreservesFromFullWeek",
446+
rules: []CourseScheduleRule{
447+
{Location: "", StartClass: 1, EndClass: 8, StartWeek: 3, EndWeek: 4, Weekday: 1, Single: true, Double: true, FromFullWeek: true},
448+
},
449+
adjusts: []CourseAdjustRule{
450+
{OldWeek: 3, OldWeekday: 1, OldStartClass: 1, OldEndClass: 8, NewWeek: 5, NewWeekday: 1, NewStartClass: 1, NewEndClass: 8, NewLocation: ""},
451+
},
452+
expected: []CourseScheduleRule{
453+
{Location: "", StartClass: 1, EndClass: 8, StartWeek: 5, EndWeek: 5, Weekday: 1, Single: true, Double: true, Adjust: true},
454+
{Location: "", StartClass: 1, EndClass: 8, StartWeek: 4, EndWeek: 4, Weekday: 1, Single: true, Double: true, FromFullWeek: true},
455+
},
456+
},
457+
}
458+
459+
for _, tc := range cases {
460+
t.Run(tc.name, func(t *testing.T) {
461+
result := ApplyAdjustRules(tc.rules, tc.adjusts)
462+
if !reflect.DeepEqual(result, tc.expected) {
463+
t.Errorf("result mismatch\ngot: %+v\nexpected: %+v", result, tc.expected)
464+
}
465+
})
466+
}
467+
}

model.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ type Course struct {
7272
Teacher string `json:"teacher"` // 任课教师
7373
ScheduleRules []CourseScheduleRule `json:"scheduleRules"` // 上课时间地点规则
7474
FullWeekScheduleRules []CourseFullWeekScheduleRule `json:"fullWeekScheduleRules"` // 整周课程上课时间地点规则
75+
AdjustRules []CourseAdjustRule `json:"adjustRules"` // 调课规则
7576
RawScheduleRules string `json:"rawScheduleRules"` // 上课时间地点(原始文本)
7677
RawExamTime string `json:"rawExamTime"` // 考试时间地点(原始文本)
7778
RawAdjust string `json:"rawAdjust"` // 调课信息(原始文本)

0 commit comments

Comments
 (0)