diff --git a/HFUTCourseSimulation/Kernel/Arranger.cs b/HFUTCourseSimulation/Kernel/Arranger.cs
index c27361c..d0e24e8 100644
--- a/HFUTCourseSimulation/Kernel/Arranger.cs
+++ b/HFUTCourseSimulation/Kernel/Arranger.cs
@@ -1,140 +1,129 @@
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
-using HFUTCourseSimulation.Kernel.Data.Built;
using HFUTCourseSimulation.Util;
namespace HFUTCourseSimulation.Kernel {
- ///
- /// 一个标识符,用于指代学期中指定周的指定星期的指定节次
- ///
- internal class ArrangerSpot : IEquatable {
- public ArrangerSpot(int week, int day, int index) {
- this.week = week;
- this.day = day;
- this.index = index;
- }
+ /////
+ ///// 一个标识符,用于指代学期中指定周的指定星期的指定节次
+ /////
+ //internal class ArrangerSpot : IEquatable {
+ // public ArrangerSpot(int week, int day, int index) {
+ // this.week = week;
+ // this.day = day;
+ // this.index = index;
+ // }
- ///
- /// 教学周
- ///
- public int week;
- ///
- /// 星期几
- ///
- public int day;
- ///
- /// 节次
- ///
- public int index;
+ // ///
+ // /// 教学周
+ // ///
+ // public int week;
+ // ///
+ // /// 星期几
+ // ///
+ // public int day;
+ // ///
+ // /// 节次
+ // ///
+ // public int index;
- public override bool Equals(object obj) {
- return Equals(obj as ArrangerSpot);
- }
+ // public override bool Equals(object obj) {
+ // return Equals(obj as ArrangerSpot);
+ // }
- public bool Equals(ArrangerSpot other) {
- return !(other is null) &&
- week == other.week &&
- day == other.day &&
- index == other.index;
- }
+ // public bool Equals(ArrangerSpot other) {
+ // return !(other is null) &&
+ // week == other.week &&
+ // day == other.day &&
+ // index == other.index;
+ // }
- public override int GetHashCode() {
- int hashCode = -206993699;
- hashCode = hashCode * -1521134295 + week.GetHashCode();
- hashCode = hashCode * -1521134295 + day.GetHashCode();
- hashCode = hashCode * -1521134295 + index.GetHashCode();
- return hashCode;
- }
+ // public override int GetHashCode() {
+ // int hashCode = -206993699;
+ // hashCode = hashCode * -1521134295 + week.GetHashCode();
+ // hashCode = hashCode * -1521134295 + day.GetHashCode();
+ // hashCode = hashCode * -1521134295 + index.GetHashCode();
+ // return hashCode;
+ // }
- public static bool operator ==(ArrangerSpot left, ArrangerSpot right) {
- return EqualityComparer.Default.Equals(left, right);
- }
+ // public static bool operator ==(ArrangerSpot left, ArrangerSpot right) {
+ // return EqualityComparer.Default.Equals(left, right);
+ // }
- public static bool operator !=(ArrangerSpot left, ArrangerSpot right) {
- return !(left == right);
- }
- }
+ // public static bool operator !=(ArrangerSpot left, ArrangerSpot right) {
+ // return !(left == right);
+ // }
+ //}
///
/// 课程安排器。
/// 负责将课程安排到每周之中,并提示用户错误等各种信息。
///
- public class Arranger {
- public Arranger(Kernel.Data.Storage.Semester semester) {
- this.semester = semester;
- arrangeMap = new Dictionary();
- reporter = new Reporter();
- }
-
- private Kernel.Data.Storage.Semester semester;
- private Dictionary arrangeMap;
- private Reporter reporter;
-
- ///
- /// 获取汇报器用于查看安排日志
- ///
- ///
- public Reporter GetReporter() {
- return reporter;
- }
+ public static class Arranger {
///
/// 开始安排课程
///
/// 安排好的,用于呈现的课程。如果安排失败,则为null,查看汇报器来了解具体差错
- public Kernel.Data.Presentation.Semester Arrange() {
- reporter.Clear();
- arrangeMap.Clear();
- return ConcludeCourses();
+ public static Data.Presentation.Semester Arrange(Data.Storage.Semester semester, Reporter reporter) {
+ var builtSemester = CheckSemester(semester, reporter);
+ if (builtSemester is null) return null;
+
+ var arrangeMap = ArrangeCourses(builtSemester, reporter);
+ var renderResult = ConcludeCourses(builtSemester, arrangeMap, reporter);
+
+ return renderResult;
}
+ #region 验证用户课表输入
+
///
/// 检查用户输入的学期数据是否正确
///
- /// 如果检查无误,返回检查后的结果,否则返回null
- private Semester CheckSemester() {
+ /// 如果检查无误则返回检查好的结构,否则返回null
+ private static Data.Built.Semester CheckSemester(Data.Storage.Semester semester, Reporter reporter) {
// 检查Semester
reporter.Info($"正在检查学期");
if (semester.StartDate.DayOfWeek != DayOfWeek.Monday) {
reporter.Error($"起始日期{semester.StartDate:yyyy-MM-dd}不是星期一");
return null;
}
- var rv = CheckInteger(semester.WeekCount, 1, 100, "周数", "0 < N");
+ var rv = CheckInteger(reporter, semester.WeekCount, 1, 100, "周数", "0 < N");
if (rv is null) return null;
int weekCount = rv.Value;
- rv = CheckInteger(semester.IndexCount, 1, 100, "节次数", "0 < N");
+ rv = CheckInteger(reporter, semester.IndexCount, 1, 100, "节次数", "0 < N");
if (rv is null) return null;
int indexCount = rv.Value;
- rv = CheckInteger(semester.BreakfastAt, 0, indexCount, "早餐时间点", "0 <= N <= 节次数");
+ rv = CheckInteger(reporter, semester.BreakfastAt, 0, indexCount, "早餐时间点", "0 <= N <= 节次数");
if (rv is null) return null;
int breakfastAt = rv.Value;
- rv = CheckInteger(semester.LunchAt, breakfastAt, indexCount, "午餐时间点", "早餐时间点 <= N <= 节次数");
+ rv = CheckInteger(reporter, semester.LunchAt, breakfastAt, indexCount, "午餐时间点", "早餐时间点 <= N <= 节次数");
if (rv is null) return null;
int lunchAt = rv.Value;
- rv = CheckInteger(semester.DinnerAt, lunchAt, indexCount, "晚餐时间点", "午餐时间点 <= N <= 节次数");
+ rv = CheckInteger(reporter, semester.DinnerAt, lunchAt, indexCount, "晚餐时间点", "午餐时间点 <= N <= 节次数");
if (rv is null) return null;
int dinnerAt = rv.Value;
- var courses = new List();
+ var courses = new List();
foreach (var course in semester.Courses) {
// 检查课程(实际上只有安排需要检查)
reporter.Info($"正在检查课程:{course.Name}");
- var schedules = new List();
+ var schedules = new List();
foreach (var schedule in course.Schedules) {
// 检查安排
- var week = CheckIntegerCollection(schedule.Week, 1, weekCount, "周", "1 <= N <= 周数");
- var day = CheckIntegerCollection(schedule.Day, 1, 7, "星期", "1 <= N <= 7");
- var index = CheckIntegerCollection(schedule.Index, 1, indexCount, "节次", "1 <= N <= 节次数");
+ var week = CheckIntegerCollection(reporter, schedule.Week, 1, weekCount, "周", "1 <= N <= 周数");
+ var day = CheckIntegerCollection(reporter, schedule.Day, 1, 7, "星期", "1 <= N <= 7");
+ var index = CheckIntegerCollection(reporter, schedule.Index, 1, indexCount, "节次", "1 <= N <= 节次数");
// 如果格式均正确,就添加这个安排
if (!(week is null || day is null || index is null)) {
- schedules.Add(new Schedule() {
+ schedules.Add(new Data.Built.Schedule() {
week = week,
day = day,
index = index
@@ -143,7 +132,7 @@ namespace HFUTCourseSimulation.Kernel {
}
// OK,插入课程
- courses.Add(new Course() {
+ courses.Add(new Data.Built.Course() {
name = course.Name,
description = course.Description,
color = ColorTrans.ToStandardColorPair(course.Color),
@@ -152,7 +141,7 @@ namespace HFUTCourseSimulation.Kernel {
}
// OK无误,返回结果
- return new Semester() {
+ return new Data.Built.Semester() {
startDate = semester.StartDate,
weekCount = weekCount,
indexCount = indexCount,
@@ -168,8 +157,8 @@ namespace HFUTCourseSimulation.Kernel {
/// field_name是字段的名称,field_require是字段的要求,均用于用户提示。
/// 返回null表示检查失败,否则返回检查好的值。
///
- private int? CheckInteger(string s, int min, int max, string field_name, string field_require) {
- if (int.TryParse(semester.WeekCount, out int rv)) {
+ private static int? CheckInteger(Reporter reporter, string s, int min, int max, string field_name, string field_require) {
+ if (int.TryParse(s, out int rv)) {
if (rv < min || rv > max) {
reporter.Error($"{field_name}的值“{s}”超出范围。要求:{field_require}");
return null;
@@ -192,7 +181,7 @@ namespace HFUTCourseSimulation.Kernel {
/// 检查整数集合的辅助函数,即检查Schedule中的三个字段Week,Day和Index的格式。
/// 各类参数含义与上面函数相同,只不过是作用到每一项上。
///
- private IEnumerable CheckIntegerCollection(string s, int min, int max, string field_name, string field_require) {
+ private static IEnumerable CheckIntegerCollection(Reporter reporter, string s, int min, int max, string field_name, string field_require) {
// 两种整数集合所特有的字符常量,用于分割和分析
const char COMMA = ',';
const char DASH = '-';
@@ -215,7 +204,7 @@ namespace HFUTCourseSimulation.Kernel {
split_char = COMMA;
break;
case IntegerCollectionKind.Range:
- split_char = DASH;
+ split_char = DASH;
break;
default:
return null;
@@ -240,63 +229,148 @@ namespace HFUTCourseSimulation.Kernel {
// 按整数集合类型进行收尾检查并返回
switch (kind) {
case IntegerCollectionKind.Individual:
- return new IndividualInt(ints);
+ return new Data.Built.IndividualInt(ints);
case IntegerCollectionKind.Range:
if (ints.Count != 2) {
reporter.Error($"{field_name}的值“{s}”不是有效的整数范围表达式");
return null;
} else {
- return new RangedInt(ints[0], ints[1]);
+ return new Data.Built.RangedInt(ints[0], ints[1]);
}
default:
return null;
}
}
+ #endregion
+
+ #region 安排课程
+
///
/// 安排课程
///
- /// 安排无误返回true,否则返回false
- private bool ArrangeCourses() {
- // Check it first
- var rv = CheckSemester();
- if (rv is null) return false;
+ /// 返回安排好的课程表
+ private static int[,,] ArrangeCourses(Data.Built.Semester semester, Reporter reporter) {
+ // 创建课程表并预填充所有项为-1。
+ var arrangeMap = new int[semester.weekCount, 7, semester.indexCount];
+ for (var week = 0; week < semester.weekCount; ++week) {
+ for (var day = 0; day < 7; ++day) {
+ for (var index = 0; index < semester.indexCount; ++index) {
+ arrangeMap[week, day, index] = -1;
+ }
+ }
+ }
// 遍历所有课程安排开始排课
- bool okey = true;
- for (int course_index = 0; course_index < rv.courses.Count; ++course_index) {
- var course = rv.courses[course_index];
+ // 排课冲突并不是严重错误,因此不需要强制退出
+ for (int course_index = 0; course_index < semester.courses.Count; ++course_index) {
+ var course = semester.courses[course_index];
foreach (var schedule in course.schedules) {
foreach (var week in schedule.week) {
foreach (var day in schedule.day) {
foreach (var index in schedule.index) {
- var spot = new ArrangerSpot(week, day, index);
- if (arrangeMap.TryGetValue(spot, out int occupied_course_index)) {
- var occupied_course = rv.courses[occupied_course_index];
- reporter.Error($"课程冲突:无法将{course.name}安排到周{week},星期{day},第{index}节。因为此处已被{occupied_course.name}占据");
- okey = false;
+ // 获取课程占用情况并分析
+ var occupied_course_index = arrangeMap[week - 1, day - 1, index - 1];
+ if (occupied_course_index < 0) {
+ // 没有占用的课程,可以放心安排
+ arrangeMap[week - 1, day - 1, index - 1] = course_index;
} else {
- arrangeMap.Add(spot, course_index);
+ // 发现课程占用,汇报占用的课程并记录到flag
+ var occupied_course = semester.courses[occupied_course_index];
+ reporter.Error($"课程冲突:无法将{course.name}安排到周{week},星期{day},第{index}节。因为此处已被{occupied_course.name}占据");
}
}
}
}
}
}
- return okey;
+
+ // 返回结果
+ return arrangeMap;
}
+ #endregion
+
+ #region 总结课程为方便渲染的结构
+
///
/// 将安排好的课程总结为方便渲染的结果
///
- ///
- private Kernel.Data.Presentation.Semester ConcludeCourses() {
- // Arrange it first
- var rv = ArrangeCourses();
- if (!rv) return null;
+ /// 总结好的可用于渲染的结果
+ private static Data.Presentation.Semester ConcludeCourses(Data.Built.Semester semester, int[,,] arrangeMap, Reporter reporter) {
+ var dateCursor = semester.startDate;
- return null;
+ var weeks = new List();
+ for (var week = 1; week <= semester.weekCount; ++week) {
+ var days = new List();
+ for (var day = 1; day <= 7; ++day) {
+ // 获取日期的字符串形式,并自增天数
+ var dateString = dateCursor.ToString("MM/dd");
+ dateCursor = dateCursor.AddDays(1);
+
+ // 获取这一日的课程
+ var lessons = new List();
+ Data.Presentation.Lesson current_lesson = null;
+ int previous_course_index = -1;
+ for (var index = 1; index <= semester.indexCount; ++index) {
+ // 比较上一个课程和当前课程的区别
+ var this_course_index = arrangeMap[week - 1, day - 1, index - 1];
+ if (this_course_index != previous_course_index) {
+ // 课程发生了变化,我们需要根据这种变化做出操作。
+ // 如果有上一任课程,我们就把它push到列表中。
+ if (previous_course_index >= 0) {
+ lessons.Add(current_lesson);
+ current_lesson = null;
+ }
+ // 然后设置当前课程
+ var this_course = semester.courses[this_course_index];
+ current_lesson = new Data.Presentation.Lesson() {
+ name = this_course.name,
+ description = this_course.description,
+ color = this_course.color,
+ startIndex = index,
+ indexSpan = 1
+ };
+ } else {
+ // 课程未曾发生改变,如果当前有课程,我们只需要单纯地自增其节次跨度
+ if (previous_course_index >= 0) {
+ current_lesson.indexSpan++;
+ }
+ }
+ // 更新上一个课程的index
+ previous_course_index = this_course_index;
+ }
+ // 收尾处理最后一个课程
+ if (previous_course_index >= 0) {
+ lessons.Add(current_lesson);
+ current_lesson = null;
+ }
+
+ // 将这一日添加到这周中
+ days.Add(new Data.Presentation.Day() {
+ date = dateString,
+ lessons = lessons.ToImmutableList()
+ });
+ }
+
+ // 将这一星期添加教学周中
+ weeks.Add(new Data.Presentation.Week() {
+ days = days.ToImmutableList()
+ });
+ }
+
+ // 以教学周构建并返回学期
+ return new Data.Presentation.Semester() {
+ weekCount = semester.weekCount,
+ indexCount = semester.indexCount,
+ breakfastAt = semester.breakfastAt,
+ lunchAt = semester.lunchAt,
+ dinnerAt = semester.dinnerAt,
+ weeks = weeks.ToImmutableList()
+ };
}
+ #endregion
+
}
}
diff --git a/HFUTCourseSimulation/Kernel/Data/Built.cs b/HFUTCourseSimulation/Kernel/Data/Built.cs
index e363849..5be9113 100644
--- a/HFUTCourseSimulation/Kernel/Data/Built.cs
+++ b/HFUTCourseSimulation/Kernel/Data/Built.cs
@@ -6,6 +6,10 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
+///
+/// 安排课程时使用的课表数据。
+/// 仅在课表安排器中作为中间结构使用,其本质上为经过验证的用户输入的课表数据。
+///
namespace HFUTCourseSimulation.Kernel.Data.Built {
public class Semester {
diff --git a/HFUTCourseSimulation/Kernel/Data/Presentation.cs b/HFUTCourseSimulation/Kernel/Data/Presentation.cs
index 69d1d9c..bdf74be 100644
--- a/HFUTCourseSimulation/Kernel/Data/Presentation.cs
+++ b/HFUTCourseSimulation/Kernel/Data/Presentation.cs
@@ -5,6 +5,10 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
+///
+/// 用于最终呈现的排课完毕的课表数据。
+/// 方便模拟器和图片输出接口使用的课表数据,是用户输入课表的最终“编译”后的形式。
+///
namespace HFUTCourseSimulation.Kernel.Data.Presentation {
///
@@ -30,42 +34,37 @@ namespace HFUTCourseSimulation.Kernel.Data.Presentation {
}
public class Semester {
- ///
- /// 学期开始日期。
- /// 该类保证该日期一定是星期一,且没有时间数据。
- ///
- public readonly DateTime startDate;
///
/// 教学周个数。
/// 该类保证该数值与weeks中存储的数据个数相同。
///
- public readonly int weekCount;
+ public int weekCount;
///
/// 每天课程的节次数。
/// 该类保证该数值总是大于等于1。
///
- public readonly int indexCount;
+ public int indexCount;
///
/// 早餐插入在第几节次后。
/// 该类保证该数值总是位于0至indexCount之间(含首尾)。
///
- public readonly int breakfastAt;
+ public int breakfastAt;
///
/// 午餐插入在第几节次后。
/// 该类保证该数值总是位于0至indexCount之间(含首尾),
/// 且总是大于等于breakfastAt。
///
- public readonly int lunchAt;
+ public int lunchAt;
///
/// 晚餐插入在第几节次后。
/// 该类保证该数值总是位于0至indexCount之间(含首尾),
/// 且总是大于等于lunchAt。
///
- public readonly int dinnerAt;
+ public int dinnerAt;
///
/// 每周课程数据。
///
- public readonly ImmutableList weeks;
+ public ImmutableList weeks;
public IndexKind GetIndexKind(int index) {
if (index <= 0) throw new ArgumentException("index out of range");
@@ -82,41 +81,44 @@ namespace HFUTCourseSimulation.Kernel.Data.Presentation {
/// 每周七天的数据。
/// 该类保证该字段总包含7项。
///
- public readonly ImmutableList days;
+ public ImmutableList days;
}
public class Day {
+ ///
+ /// 这一天的日期的字符串形式
+ ///
+ public string date;
///
/// 这一天的所有课程。
///
- public readonly ImmutableList lessons;
+ public ImmutableList lessons;
}
public class Lesson {
///
/// 课程的名称。
///
- public readonly string name;
+ public string name;
///
/// 课程的说明,例如教室位置,教师姓名等。
/// 该值可以包含换行。
///
- public readonly string description;
+ public string description;
///
/// 课程的颜色
///
- public readonly Util.ColorPair color;
+ public Util.ColorPair color;
///
/// 课程的起始节次。
/// 该类保证该值位于1到indexCount之间(含首尾)。
///
- public readonly int startIndex;
+ public int startIndex;
///
- /// 课程的结束节次。
- /// 该类保证该值位于1到indexCount之间(含首尾),
- /// 且大于等于startIndex。
+ /// 课程的节次跨度。
+ /// 该类保证该值总大于0,且加上startIndex后不会大于节次数。
///
- public readonly int endIndex;
+ public int indexSpan;
}
diff --git a/HFUTCourseSimulation/Kernel/Data/Storage.cs b/HFUTCourseSimulation/Kernel/Data/Storage.cs
index 1e67692..8811f69 100644
--- a/HFUTCourseSimulation/Kernel/Data/Storage.cs
+++ b/HFUTCourseSimulation/Kernel/Data/Storage.cs
@@ -8,6 +8,10 @@ using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
+///
+/// 用于保存到文件的课表数据。
+/// 同时也是程序中作为转换核心的数据结构,各种课表数据之间的转换以该结构为核心。
+///
namespace HFUTCourseSimulation.Kernel.Data.Storage {
///
diff --git a/HFUTCourseSimulation/Kernel/Data/Ui.cs b/HFUTCourseSimulation/Kernel/Data/Ui.cs
index 87b0352..ef2b02d 100644
--- a/HFUTCourseSimulation/Kernel/Data/Ui.cs
+++ b/HFUTCourseSimulation/Kernel/Data/Ui.cs
@@ -7,6 +7,10 @@ using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
+///
+/// 用于用户界面呈现的课表数据。
+/// 该命名空间的类针对WPF的UI实现了特殊的接口,以正确地和用户交互。
+///
namespace HFUTCourseSimulation.Kernel.Data.Ui {
public class Semester : INotifyPropertyChanged {