CourseComparator 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- coursecomparator-0.1.0/CourseComparator/__init__.py +33 -0
- coursecomparator-0.1.0/CourseComparator/cc_classes.py +411 -0
- coursecomparator-0.1.0/CourseComparator/cc_functions.py +130 -0
- coursecomparator-0.1.0/CourseComparator.egg-info/PKG-INFO +69 -0
- coursecomparator-0.1.0/CourseComparator.egg-info/SOURCES.txt +10 -0
- coursecomparator-0.1.0/CourseComparator.egg-info/dependency_links.txt +1 -0
- coursecomparator-0.1.0/CourseComparator.egg-info/top_level.txt +1 -0
- coursecomparator-0.1.0/LICENCE +6 -0
- coursecomparator-0.1.0/PKG-INFO +69 -0
- coursecomparator-0.1.0/README.md +58 -0
- coursecomparator-0.1.0/pyproject.toml +18 -0
- coursecomparator-0.1.0/setup.cfg +4 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
"""
|
2
|
+
课程比较器模块
|
3
|
+
|
4
|
+
该模块提供了课程比较和课程集合操作的功能,用于比较不同专业、不同年份的课程设置,
|
5
|
+
帮助用户了解课程变化,包括可冲抵、待确认、需补修和需放弃的课程。
|
6
|
+
|
7
|
+
主要组件:
|
8
|
+
- Course: 课程类,表示一门课程的基本信息
|
9
|
+
- CoursePair: 课程对类,表示两个课程之间的关系
|
10
|
+
- CourseSet: 课程集合类,表示一组课程
|
11
|
+
- CourseSetDelta: 课程集合差异类,表示两个课程集合之间的差异
|
12
|
+
- init: 创建课程数据加载器的函数
|
13
|
+
"""
|
14
|
+
|
15
|
+
from .cc_classes import (
|
16
|
+
Course,
|
17
|
+
CoursePair,
|
18
|
+
CourseSet,
|
19
|
+
CourseSetDelta,
|
20
|
+
)
|
21
|
+
|
22
|
+
from .cc_functions import init, EMPTY_SEMESTER
|
23
|
+
|
24
|
+
__all__ = [
|
25
|
+
"Course",
|
26
|
+
"CoursePair",
|
27
|
+
"CourseSet",
|
28
|
+
"CourseSetDelta",
|
29
|
+
"init",
|
30
|
+
"EMPTY_SEMESTER",
|
31
|
+
]
|
32
|
+
|
33
|
+
__version__ = "0.1.0"
|
@@ -0,0 +1,411 @@
|
|
1
|
+
from typing import List, Optional
|
2
|
+
|
3
|
+
|
4
|
+
class Course:
|
5
|
+
"""
|
6
|
+
课程类
|
7
|
+
|
8
|
+
表示一门课程的基本信息,包括课程代码、课程名称、学分和是否必修
|
9
|
+
"""
|
10
|
+
|
11
|
+
def __init__(
|
12
|
+
self, course_code: str, course_name: str, credit: float, required: bool
|
13
|
+
):
|
14
|
+
"""
|
15
|
+
初始化课程对象
|
16
|
+
|
17
|
+
Args:
|
18
|
+
course_code: 课程代码
|
19
|
+
course_name: 课程名称
|
20
|
+
credit: 课程学分
|
21
|
+
required: 是否为必修课
|
22
|
+
"""
|
23
|
+
self.course_code = course_code
|
24
|
+
self.course_name = course_name
|
25
|
+
self.credit = credit
|
26
|
+
self.required = required
|
27
|
+
|
28
|
+
def __repr__(self) -> str:
|
29
|
+
"""
|
30
|
+
返回课程对象的字符串表示,用于调试
|
31
|
+
|
32
|
+
Returns:
|
33
|
+
str: 课程对象的字典表示
|
34
|
+
"""
|
35
|
+
return str(
|
36
|
+
{
|
37
|
+
"course_code": self.course_code,
|
38
|
+
"course_name": self.course_name,
|
39
|
+
"credit": self.credit,
|
40
|
+
"required": self.required,
|
41
|
+
}
|
42
|
+
)
|
43
|
+
|
44
|
+
def __str__(self) -> str:
|
45
|
+
"""
|
46
|
+
返回课程对象的字符串表示,用于显示
|
47
|
+
|
48
|
+
Returns:
|
49
|
+
str: 格式化的课程信息字符串
|
50
|
+
"""
|
51
|
+
return f"{self.course_code}\t{self.course_name}\t{self.credit} 学分\t{'必修' if self.required else '选修'}"
|
52
|
+
|
53
|
+
def __eq__(self, other: "Course") -> bool:
|
54
|
+
"""
|
55
|
+
判断两个课程是否相等
|
56
|
+
|
57
|
+
Args:
|
58
|
+
other: 另一个课程
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
bool: 如果两个课程的所有属性都相同,则返回True,否则返回False
|
62
|
+
"""
|
63
|
+
if not isinstance(other, Course):
|
64
|
+
return False
|
65
|
+
return (
|
66
|
+
self.course_code == other.course_code
|
67
|
+
and self.course_name == other.course_name
|
68
|
+
and self.credit == other.credit
|
69
|
+
and self.required == other.required
|
70
|
+
)
|
71
|
+
|
72
|
+
def __gt__(self, other: "Course") -> bool:
|
73
|
+
"""
|
74
|
+
判断当前课程是否可以完全涵盖另一个不同的课程(即虽不同但也可冲抵的情况)
|
75
|
+
|
76
|
+
1. 两个课程不完全相同
|
77
|
+
2. 课程代码相同
|
78
|
+
3. 课程名称相同
|
79
|
+
4. 当前课程(旧的)学分大于等于另一个课程(新的)
|
80
|
+
5. 如果当前课程(旧的)是选修,另一个课程(新的)不能是必修
|
81
|
+
|
82
|
+
Args:
|
83
|
+
other: 另一个课程
|
84
|
+
|
85
|
+
Returns:
|
86
|
+
bool: 如果当前课程可以完全涵盖另一个不同的课程,则返回True,否则返回False
|
87
|
+
"""
|
88
|
+
if not isinstance(other, Course):
|
89
|
+
return False
|
90
|
+
# 不能完全一致
|
91
|
+
if self == other:
|
92
|
+
return False
|
93
|
+
# 课程代码必须相同
|
94
|
+
if self.course_code != other.course_code:
|
95
|
+
return False
|
96
|
+
# 课程名必须相同
|
97
|
+
if self.course_name != other.course_name:
|
98
|
+
return False
|
99
|
+
# 当前课程学分必须大于等于另一个课程
|
100
|
+
if self.credit > other.credit:
|
101
|
+
return False
|
102
|
+
# 选修不能转必修
|
103
|
+
if not self.required and other.required:
|
104
|
+
return False
|
105
|
+
return True
|
106
|
+
|
107
|
+
|
108
|
+
class CoursePair:
|
109
|
+
"""
|
110
|
+
课程对类
|
111
|
+
|
112
|
+
表示新旧两门课程的对比,用于显示课程变化
|
113
|
+
"""
|
114
|
+
|
115
|
+
def __init__(self, old_course: Course, new_course: Course):
|
116
|
+
"""
|
117
|
+
初始化课程对对象
|
118
|
+
|
119
|
+
Args:
|
120
|
+
old_course: 旧课程
|
121
|
+
new_course: 新课程
|
122
|
+
"""
|
123
|
+
self.old_course = old_course
|
124
|
+
self.new_course = new_course
|
125
|
+
|
126
|
+
def __repr__(self) -> str:
|
127
|
+
"""
|
128
|
+
返回课程对对象的字符串表示,用于调试
|
129
|
+
|
130
|
+
Returns:
|
131
|
+
str: 课程对对象的字典表示
|
132
|
+
"""
|
133
|
+
return str(
|
134
|
+
{
|
135
|
+
"old": {
|
136
|
+
"course_code": self.old_course.course_code,
|
137
|
+
"course_name": self.old_course.course_name,
|
138
|
+
"credit": self.old_course.credit,
|
139
|
+
"required": self.old_course.required,
|
140
|
+
},
|
141
|
+
"new": {
|
142
|
+
"course_code": self.new_course.course_code,
|
143
|
+
"course_name": self.new_course.course_name,
|
144
|
+
"credit": self.new_course.credit,
|
145
|
+
"required": self.new_course.required,
|
146
|
+
},
|
147
|
+
}
|
148
|
+
)
|
149
|
+
|
150
|
+
def __str__(self) -> str:
|
151
|
+
"""
|
152
|
+
返回课程对对象的字符串表示,用于显示
|
153
|
+
|
154
|
+
显示新旧课程的对比,包括课程代码、课程名称、学分和是否必修
|
155
|
+
如果某项属性发生变化,会添加标记:
|
156
|
+
- [ ! ]: 表示课程代码或课程名称变化
|
157
|
+
- [ + ]: 表示学分增加或从选修变为必修
|
158
|
+
- [ - ]: 表示学分减少或从必修变为选修
|
159
|
+
|
160
|
+
Returns:
|
161
|
+
str: 格式化的课程对比字符串
|
162
|
+
"""
|
163
|
+
# 准备标记
|
164
|
+
code_mark = (
|
165
|
+
" [ ! ]"
|
166
|
+
if self.old_course.course_code != self.new_course.course_code
|
167
|
+
else ""
|
168
|
+
)
|
169
|
+
name_mark = (
|
170
|
+
" [ ! ]"
|
171
|
+
if self.old_course.course_name != self.new_course.course_name
|
172
|
+
else ""
|
173
|
+
)
|
174
|
+
|
175
|
+
# 学分标记
|
176
|
+
credit_mark = ""
|
177
|
+
if self.old_course.credit != self.new_course.credit:
|
178
|
+
credit_mark = (
|
179
|
+
" [ + ]"
|
180
|
+
if self.new_course.credit > self.old_course.credit
|
181
|
+
else " [ - ]"
|
182
|
+
)
|
183
|
+
|
184
|
+
# 选必修标记
|
185
|
+
required_mark = ""
|
186
|
+
if self.old_course.required != self.new_course.required:
|
187
|
+
required_mark = " [ + ]" if self.new_course.required else " [ - ]"
|
188
|
+
|
189
|
+
# 计算各项最大宽度
|
190
|
+
max_code_width = max(
|
191
|
+
len(self.old_course.course_code),
|
192
|
+
len(self.new_course.course_code) + len(code_mark),
|
193
|
+
)
|
194
|
+
max_name_width = max(
|
195
|
+
len(self.old_course.course_name),
|
196
|
+
len(self.new_course.course_name) + len(name_mark),
|
197
|
+
)
|
198
|
+
max_credit_width = max(
|
199
|
+
len(f"{self.old_course.credit} 学分"),
|
200
|
+
len(f"{self.new_course.credit} 学分") + len(credit_mark),
|
201
|
+
)
|
202
|
+
max_required_width = max(len("必修"), len("选修") + len(required_mark))
|
203
|
+
|
204
|
+
# 构建格式化字符串
|
205
|
+
format_str = f"{{prefix}} {{code:<{max_code_width}}}\t{{name:<{max_name_width}}}\t{{credit:<{max_credit_width}}}\t{{required:<{max_required_width}}}"
|
206
|
+
|
207
|
+
# 构建两行输出
|
208
|
+
old_line = format_str.format(
|
209
|
+
prefix="OLD:",
|
210
|
+
code=self.old_course.course_code,
|
211
|
+
name=self.old_course.course_name,
|
212
|
+
credit=f"{self.old_course.credit} 学分",
|
213
|
+
required="必修" if self.old_course.required else "选修",
|
214
|
+
)
|
215
|
+
|
216
|
+
new_line = format_str.format(
|
217
|
+
prefix="NEW:",
|
218
|
+
code=f"{self.new_course.course_code}{code_mark}",
|
219
|
+
name=f"{self.new_course.course_name}{name_mark}",
|
220
|
+
credit=f"{self.new_course.credit} 学分{credit_mark}",
|
221
|
+
required=f"{'必修' if self.new_course.required else '选修'}{required_mark}",
|
222
|
+
)
|
223
|
+
|
224
|
+
return f"{old_line}\n{new_line}"
|
225
|
+
|
226
|
+
|
227
|
+
class CourseSet:
|
228
|
+
"""
|
229
|
+
课程集合类
|
230
|
+
|
231
|
+
表示一组课程,提供课程集合的操作方法
|
232
|
+
"""
|
233
|
+
|
234
|
+
def __init__(self, courses: Optional[List[Course]] = None):
|
235
|
+
"""
|
236
|
+
初始化课程集合对象
|
237
|
+
|
238
|
+
Args:
|
239
|
+
courses: 课程列表,默认为None(空列表)
|
240
|
+
"""
|
241
|
+
self.courses = courses or []
|
242
|
+
self._validate_courses()
|
243
|
+
|
244
|
+
def _validate_courses(self) -> None:
|
245
|
+
"""
|
246
|
+
验证课程集合中是否有重复的课程代码或课程名
|
247
|
+
|
248
|
+
Raises:
|
249
|
+
ValueError: 如果存在重复的课程代码或课程名
|
250
|
+
"""
|
251
|
+
code_set = set()
|
252
|
+
name_set = set()
|
253
|
+
for course in self.courses:
|
254
|
+
if course.course_code in code_set:
|
255
|
+
raise ValueError(f"课程代码重复: {course.course_code}")
|
256
|
+
if course.course_name in name_set:
|
257
|
+
raise ValueError(f"课程名重复: {course.course_name}")
|
258
|
+
code_set.add(course.course_code)
|
259
|
+
name_set.add(course.course_name)
|
260
|
+
|
261
|
+
def append(self, course: Course) -> None:
|
262
|
+
"""
|
263
|
+
添加单个课程到集合中
|
264
|
+
|
265
|
+
Args:
|
266
|
+
course: 要添加的课程
|
267
|
+
|
268
|
+
Raises:
|
269
|
+
ValueError: 如果课程代码或课程名已存在
|
270
|
+
"""
|
271
|
+
if any(c.course_code == course.course_code for c in self.courses):
|
272
|
+
raise ValueError(f"课程代码重复: {course.course_code}")
|
273
|
+
if any(c.course_name == course.course_name for c in self.courses):
|
274
|
+
raise ValueError(f"课程名重复: {course.course_name}")
|
275
|
+
self.courses.append(course)
|
276
|
+
|
277
|
+
def __add__(self, other: "CourseSet") -> "CourseSet":
|
278
|
+
"""
|
279
|
+
合并两个课程集合, 用于聚合同一方案的不同学期
|
280
|
+
|
281
|
+
Args:
|
282
|
+
other: 另一个课程集合
|
283
|
+
|
284
|
+
Returns:
|
285
|
+
CourseSet: 合并后的新课程集合
|
286
|
+
|
287
|
+
Raises:
|
288
|
+
TypeError: 如果other不是CourseSet类型
|
289
|
+
"""
|
290
|
+
if not isinstance(other, CourseSet):
|
291
|
+
raise TypeError("只能与 CourseSet 类型相加")
|
292
|
+
new_courses = self.courses + other.courses
|
293
|
+
return CourseSet(new_courses)
|
294
|
+
|
295
|
+
def __sub__(self, other: "CourseSet") -> "CourseSetDelta":
|
296
|
+
"""
|
297
|
+
用旧的培养方案减去新的培养方案, 用于计算两者之间的差异
|
298
|
+
|
299
|
+
Args:
|
300
|
+
other: 另一个课程集合
|
301
|
+
|
302
|
+
Returns:
|
303
|
+
CourseSetDelta: 表示两个课程集合差异的对象
|
304
|
+
|
305
|
+
Raises:
|
306
|
+
TypeError: 如果other不是CourseSet类型
|
307
|
+
"""
|
308
|
+
if not isinstance(other, CourseSet):
|
309
|
+
raise TypeError("只能与 CourseSet 类型相减")
|
310
|
+
return CourseSetDelta(self, other)
|
311
|
+
|
312
|
+
def __str__(self) -> str:
|
313
|
+
"""
|
314
|
+
返回课程集合的字符串表示
|
315
|
+
|
316
|
+
Returns:
|
317
|
+
str: 所有课程的字符串表示,每行一个课程
|
318
|
+
"""
|
319
|
+
return "\n".join(str(course) for course in self.courses)
|
320
|
+
|
321
|
+
|
322
|
+
class CourseSetDelta:
|
323
|
+
"""
|
324
|
+
课程集合差异类
|
325
|
+
|
326
|
+
表示两个课程集合之间的差异,包括可冲抵、待确认、需补修和需放弃的课程
|
327
|
+
"""
|
328
|
+
|
329
|
+
def __init__(self, old_set: CourseSet, new_set: CourseSet):
|
330
|
+
"""
|
331
|
+
初始化课程集合差异对象
|
332
|
+
|
333
|
+
Args:
|
334
|
+
old_set: 旧课程集合
|
335
|
+
new_set: 新课程集合
|
336
|
+
"""
|
337
|
+
self.consistent_or_including = [] # 可冲抵
|
338
|
+
self.similar = [] # 待确认
|
339
|
+
self.new_only = [] # 需补修
|
340
|
+
self.old_only = [] # 需放弃
|
341
|
+
|
342
|
+
self._calculate_delta(old_set, new_set)
|
343
|
+
|
344
|
+
def _calculate_delta(self, old_set: CourseSet, new_set: CourseSet) -> None:
|
345
|
+
"""
|
346
|
+
计算两个课程集合之间的差异
|
347
|
+
|
348
|
+
Args:
|
349
|
+
old_set: 旧课程集合
|
350
|
+
new_set: 新课程集合
|
351
|
+
"""
|
352
|
+
# 处理可冲抵和待确认的课程
|
353
|
+
for old_course in old_set.courses:
|
354
|
+
found = False
|
355
|
+
for new_course in new_set.courses:
|
356
|
+
# 1. 完全相等或可冲抵的情况
|
357
|
+
if old_course == new_course or old_course > new_course:
|
358
|
+
self.consistent_or_including.append(
|
359
|
+
CoursePair(old_course, new_course)
|
360
|
+
)
|
361
|
+
found = True
|
362
|
+
break
|
363
|
+
# 2. 课程代码相同或课程名称相同
|
364
|
+
elif (
|
365
|
+
old_course.course_code == new_course.course_code
|
366
|
+
or old_course.course_name == new_course.course_name
|
367
|
+
):
|
368
|
+
self.similar.append(CoursePair(old_course, new_course))
|
369
|
+
found = True
|
370
|
+
break
|
371
|
+
# 3. 未找到匹配的旧课程
|
372
|
+
if not found:
|
373
|
+
self.old_only.append(old_course)
|
374
|
+
|
375
|
+
# 4. 处理需补修的课程
|
376
|
+
for new_course in new_set.courses:
|
377
|
+
if not any(
|
378
|
+
old_course == new_course
|
379
|
+
or old_course > new_course
|
380
|
+
or old_course.course_code == new_course.course_code
|
381
|
+
or old_course.course_name == new_course.course_name
|
382
|
+
for old_course in old_set.courses
|
383
|
+
):
|
384
|
+
self.new_only.append(new_course)
|
385
|
+
|
386
|
+
def __str__(self) -> str:
|
387
|
+
"""
|
388
|
+
返回课程集合差异的字符串表示
|
389
|
+
|
390
|
+
Returns:
|
391
|
+
str: 格式化的课程差异字符串,包括可冲抵、待确认、需补修和需放弃的课程
|
392
|
+
"""
|
393
|
+
result = []
|
394
|
+
|
395
|
+
if self.consistent_or_including:
|
396
|
+
result.append("\n【可冲抵】\n")
|
397
|
+
result.extend(str(pair) + "\n" for pair in self.consistent_or_including)
|
398
|
+
|
399
|
+
if self.similar:
|
400
|
+
result.append("\n【待确认】\n")
|
401
|
+
result.extend(str(pair) + "\n" for pair in self.similar)
|
402
|
+
|
403
|
+
if self.new_only:
|
404
|
+
result.append("\n【需补修】\n")
|
405
|
+
result.extend(str(course) + "\n" for course in self.new_only)
|
406
|
+
|
407
|
+
if self.old_only:
|
408
|
+
result.append("\n【需放弃】\n")
|
409
|
+
result.extend(str(course) + "\n" for course in self.old_only)
|
410
|
+
|
411
|
+
return "\n".join(result)
|
@@ -0,0 +1,130 @@
|
|
1
|
+
from typing import Callable
|
2
|
+
from .cc_classes import Course, CourseSet
|
3
|
+
import csv
|
4
|
+
import pickle
|
5
|
+
from pathlib import Path
|
6
|
+
|
7
|
+
|
8
|
+
# 全局唯一的0学期对象,表示刚入学的状态
|
9
|
+
EMPTY_SEMESTER = CourseSet()
|
10
|
+
|
11
|
+
|
12
|
+
def init(data_dir: str) -> Callable[[str, str, int], CourseSet]:
|
13
|
+
"""
|
14
|
+
创建课程数据加载器
|
15
|
+
|
16
|
+
Args:
|
17
|
+
data_dir: 数据目录的路径
|
18
|
+
|
19
|
+
Returns:
|
20
|
+
Callable[[str, str, int], CourseSet]: 加载器函数,接受专业名称、年份和学期作为参数,返回课程集合
|
21
|
+
"""
|
22
|
+
# 确保数据目录存在
|
23
|
+
data_path = Path(data_dir)
|
24
|
+
if not data_path.exists():
|
25
|
+
raise FileNotFoundError(f"数据目录不存在: {data_dir}")
|
26
|
+
|
27
|
+
# 创建缓存目录
|
28
|
+
cache_dir = data_path / "__cc_cache__"
|
29
|
+
cache_dir.mkdir(exist_ok=True)
|
30
|
+
|
31
|
+
def loader(major: str, year: str, semester: int) -> CourseSet:
|
32
|
+
"""
|
33
|
+
加载指定专业、年份和学期的课程数据
|
34
|
+
|
35
|
+
Args:
|
36
|
+
major: 专业名称
|
37
|
+
year: 年份
|
38
|
+
semester: 学期数(0-8,0表示刚入学的状态)
|
39
|
+
|
40
|
+
Returns:
|
41
|
+
CourseSet: 指定学期的课程集合
|
42
|
+
|
43
|
+
Raises:
|
44
|
+
FileNotFoundError: 如果专业目录不存在
|
45
|
+
ValueError: 如果学期数不在有效范围内
|
46
|
+
"""
|
47
|
+
# 处理 0 学期(刚入学的状态)
|
48
|
+
if semester == 0:
|
49
|
+
return EMPTY_SEMESTER
|
50
|
+
|
51
|
+
# 验证学期数
|
52
|
+
if semester < 0 or semester > 8:
|
53
|
+
raise ValueError(f"学期数必须在0-8之间,当前值: {semester}")
|
54
|
+
|
55
|
+
# 构建数据目录路径
|
56
|
+
major_dir = data_path / major / year
|
57
|
+
if not major_dir.exists():
|
58
|
+
raise FileNotFoundError(f"专业目录不存在: {major_dir}")
|
59
|
+
|
60
|
+
# 构建缓存文件路径
|
61
|
+
cache_file = cache_dir / f"{major}_{year}.pkl"
|
62
|
+
|
63
|
+
# 检查缓存是否存在且是最新的
|
64
|
+
if cache_file.exists():
|
65
|
+
# 获取缓存文件的修改时间
|
66
|
+
cache_mtime = cache_file.stat().st_mtime
|
67
|
+
|
68
|
+
# 检查数据文件是否有更新
|
69
|
+
data_files_updated = False
|
70
|
+
for i in range(1, 9):
|
71
|
+
csv_file = major_dir / f"{i}.csv"
|
72
|
+
if csv_file.exists() and csv_file.stat().st_mtime > cache_mtime:
|
73
|
+
data_files_updated = True
|
74
|
+
break
|
75
|
+
|
76
|
+
# 如果数据文件没有更新,直接使用缓存
|
77
|
+
if not data_files_updated:
|
78
|
+
with open(cache_file, "rb") as f:
|
79
|
+
course_sets = pickle.load(f)
|
80
|
+
|
81
|
+
# 如果学期数大于可用的学期数,返回最后一个学期的课程集合
|
82
|
+
if semester > len(course_sets):
|
83
|
+
return course_sets[-1]
|
84
|
+
|
85
|
+
# 计算前semester个学期的课程集合之和
|
86
|
+
result = CourseSet()
|
87
|
+
for i in range(semester):
|
88
|
+
result = result + course_sets[i]
|
89
|
+
|
90
|
+
return result
|
91
|
+
|
92
|
+
# 如果没有缓存或缓存已过期,重新加载数据
|
93
|
+
course_sets = []
|
94
|
+
for i in range(1, 9):
|
95
|
+
csv_file = major_dir / f"{i}.csv"
|
96
|
+
if not csv_file.exists():
|
97
|
+
# 如果某个学期的文件不存在,添加一个空的课程集合
|
98
|
+
course_sets.append(CourseSet())
|
99
|
+
continue
|
100
|
+
|
101
|
+
courses = []
|
102
|
+
with open(csv_file, "r", encoding="GBK") as f:
|
103
|
+
reader = csv.DictReader(f)
|
104
|
+
for row in reader:
|
105
|
+
course = Course(
|
106
|
+
course_code=row["course_code"],
|
107
|
+
course_name=row["course_name"],
|
108
|
+
credit=float(row["credit"]),
|
109
|
+
required=bool(int(row["required"])),
|
110
|
+
)
|
111
|
+
courses.append(course)
|
112
|
+
|
113
|
+
course_sets.append(CourseSet(courses))
|
114
|
+
|
115
|
+
# 保存缓存
|
116
|
+
with open(cache_file, "wb") as f:
|
117
|
+
pickle.dump(course_sets, f)
|
118
|
+
|
119
|
+
# 如果学期数大于可用的学期数,返回最后一个学期的课程集合
|
120
|
+
if semester > len(course_sets):
|
121
|
+
return course_sets[-1]
|
122
|
+
|
123
|
+
# 计算前semester个学期的课程集合之和
|
124
|
+
result = CourseSet()
|
125
|
+
for i in range(semester):
|
126
|
+
result = result + course_sets[i]
|
127
|
+
|
128
|
+
return result
|
129
|
+
|
130
|
+
return loader
|
@@ -0,0 +1,69 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: CourseComparator
|
3
|
+
Version: 0.1.0
|
4
|
+
Summary: 用于比较不同的专业、届别或年级之间的课程设置,供转专业、降级学生确认可冲抵及需补修的课程。
|
5
|
+
Author-email: Vincy SHI <vincy@vincy1230.net>
|
6
|
+
License-Expression: MIT
|
7
|
+
Project-URL: Homepage, https://github.com/vincy1230/Course-Comparator
|
8
|
+
Description-Content-Type: text/markdown
|
9
|
+
License-File: LICENCE
|
10
|
+
Dynamic: license-file
|
11
|
+
|
12
|
+
# Course-Comparator 课程比较器
|
13
|
+
|
14
|
+
用于比较不同的专业、届别或年级之间的课程设置,供转专业、降级学生确认可冲抵及需补修的课程。
|
15
|
+
|
16
|
+
## 使用方法
|
17
|
+
|
18
|
+
### 整理数据集
|
19
|
+
|
20
|
+
将各个课程的数据集按照如下格式整理:
|
21
|
+
|
22
|
+
`<数据集根目录>` / `<专业>` / `<届别>` / `<学期>.csv`
|
23
|
+
|
24
|
+
例如,人工智能专业 2021 级第 1 ~ 8 学期的课程数据位于,分别存于:
|
25
|
+
|
26
|
+
`<数据集根目录>` / `人工智能` / `2021` / `1.csv`
|
27
|
+
`<数据集根目录>` / `人工智能` / `2021` / `2.csv`
|
28
|
+
...
|
29
|
+
`<数据集根目录>` / `人工智能` / `2021` / `8.csv`
|
30
|
+
|
31
|
+
csv 文件的格式如下:
|
32
|
+
|
33
|
+
| csv 表头 | 含义 | 示例 | 备注 |
|
34
|
+
| ----------- | -------- | ---------------- | ---------------------- |
|
35
|
+
| course_code | 课程代码 | `MATA5B1001` | 将解析为字符串 |
|
36
|
+
| course_name | 课程名称 | `高等数学(上)` | 将解析为字符串 |
|
37
|
+
| credit | 课程学分 | `5` | 将解析为浮点数 |
|
38
|
+
| required | 是否必修 | `1` | `1` 为必修,`0` 为选修 |
|
39
|
+
|
40
|
+
```csv
|
41
|
+
course_code,course_name,credit,required
|
42
|
+
MATA5B1001,高等数学(上),5,1
|
43
|
+
...
|
44
|
+
```
|
45
|
+
|
46
|
+
### 安装本软件包
|
47
|
+
|
48
|
+
```bash
|
49
|
+
pip install CourseComparator
|
50
|
+
```
|
51
|
+
|
52
|
+
### 运行程序
|
53
|
+
|
54
|
+
```python
|
55
|
+
# 导入课程比较器模块
|
56
|
+
import CourseComparator as cc
|
57
|
+
|
58
|
+
# 传入数据集根目录,初始化数据加载器
|
59
|
+
loader = cc.init("<数据集根目录>")
|
60
|
+
|
61
|
+
# 获取旧的课程方案
|
62
|
+
old_courses = loader("<专业>", "<届别>", <学期>)
|
63
|
+
|
64
|
+
# 获取新的课程方案
|
65
|
+
new_courses = loader("<专业>", "<届别>", <学期>)
|
66
|
+
|
67
|
+
# 打印两个方案的差异
|
68
|
+
print(old_courses - new_courses)
|
69
|
+
```
|
@@ -0,0 +1,10 @@
|
|
1
|
+
LICENCE
|
2
|
+
README.md
|
3
|
+
pyproject.toml
|
4
|
+
CourseComparator/__init__.py
|
5
|
+
CourseComparator/cc_classes.py
|
6
|
+
CourseComparator/cc_functions.py
|
7
|
+
CourseComparator.egg-info/PKG-INFO
|
8
|
+
CourseComparator.egg-info/SOURCES.txt
|
9
|
+
CourseComparator.egg-info/dependency_links.txt
|
10
|
+
CourseComparator.egg-info/top_level.txt
|
@@ -0,0 +1 @@
|
|
1
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
CourseComparator
|
@@ -0,0 +1,69 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: CourseComparator
|
3
|
+
Version: 0.1.0
|
4
|
+
Summary: 用于比较不同的专业、届别或年级之间的课程设置,供转专业、降级学生确认可冲抵及需补修的课程。
|
5
|
+
Author-email: Vincy SHI <vincy@vincy1230.net>
|
6
|
+
License-Expression: MIT
|
7
|
+
Project-URL: Homepage, https://github.com/vincy1230/Course-Comparator
|
8
|
+
Description-Content-Type: text/markdown
|
9
|
+
License-File: LICENCE
|
10
|
+
Dynamic: license-file
|
11
|
+
|
12
|
+
# Course-Comparator 课程比较器
|
13
|
+
|
14
|
+
用于比较不同的专业、届别或年级之间的课程设置,供转专业、降级学生确认可冲抵及需补修的课程。
|
15
|
+
|
16
|
+
## 使用方法
|
17
|
+
|
18
|
+
### 整理数据集
|
19
|
+
|
20
|
+
将各个课程的数据集按照如下格式整理:
|
21
|
+
|
22
|
+
`<数据集根目录>` / `<专业>` / `<届别>` / `<学期>.csv`
|
23
|
+
|
24
|
+
例如,人工智能专业 2021 级第 1 ~ 8 学期的课程数据位于,分别存于:
|
25
|
+
|
26
|
+
`<数据集根目录>` / `人工智能` / `2021` / `1.csv`
|
27
|
+
`<数据集根目录>` / `人工智能` / `2021` / `2.csv`
|
28
|
+
...
|
29
|
+
`<数据集根目录>` / `人工智能` / `2021` / `8.csv`
|
30
|
+
|
31
|
+
csv 文件的格式如下:
|
32
|
+
|
33
|
+
| csv 表头 | 含义 | 示例 | 备注 |
|
34
|
+
| ----------- | -------- | ---------------- | ---------------------- |
|
35
|
+
| course_code | 课程代码 | `MATA5B1001` | 将解析为字符串 |
|
36
|
+
| course_name | 课程名称 | `高等数学(上)` | 将解析为字符串 |
|
37
|
+
| credit | 课程学分 | `5` | 将解析为浮点数 |
|
38
|
+
| required | 是否必修 | `1` | `1` 为必修,`0` 为选修 |
|
39
|
+
|
40
|
+
```csv
|
41
|
+
course_code,course_name,credit,required
|
42
|
+
MATA5B1001,高等数学(上),5,1
|
43
|
+
...
|
44
|
+
```
|
45
|
+
|
46
|
+
### 安装本软件包
|
47
|
+
|
48
|
+
```bash
|
49
|
+
pip install CourseComparator
|
50
|
+
```
|
51
|
+
|
52
|
+
### 运行程序
|
53
|
+
|
54
|
+
```python
|
55
|
+
# 导入课程比较器模块
|
56
|
+
import CourseComparator as cc
|
57
|
+
|
58
|
+
# 传入数据集根目录,初始化数据加载器
|
59
|
+
loader = cc.init("<数据集根目录>")
|
60
|
+
|
61
|
+
# 获取旧的课程方案
|
62
|
+
old_courses = loader("<专业>", "<届别>", <学期>)
|
63
|
+
|
64
|
+
# 获取新的课程方案
|
65
|
+
new_courses = loader("<专业>", "<届别>", <学期>)
|
66
|
+
|
67
|
+
# 打印两个方案的差异
|
68
|
+
print(old_courses - new_courses)
|
69
|
+
```
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Course-Comparator 课程比较器
|
2
|
+
|
3
|
+
用于比较不同的专业、届别或年级之间的课程设置,供转专业、降级学生确认可冲抵及需补修的课程。
|
4
|
+
|
5
|
+
## 使用方法
|
6
|
+
|
7
|
+
### 整理数据集
|
8
|
+
|
9
|
+
将各个课程的数据集按照如下格式整理:
|
10
|
+
|
11
|
+
`<数据集根目录>` / `<专业>` / `<届别>` / `<学期>.csv`
|
12
|
+
|
13
|
+
例如,人工智能专业 2021 级第 1 ~ 8 学期的课程数据位于,分别存于:
|
14
|
+
|
15
|
+
`<数据集根目录>` / `人工智能` / `2021` / `1.csv`
|
16
|
+
`<数据集根目录>` / `人工智能` / `2021` / `2.csv`
|
17
|
+
...
|
18
|
+
`<数据集根目录>` / `人工智能` / `2021` / `8.csv`
|
19
|
+
|
20
|
+
csv 文件的格式如下:
|
21
|
+
|
22
|
+
| csv 表头 | 含义 | 示例 | 备注 |
|
23
|
+
| ----------- | -------- | ---------------- | ---------------------- |
|
24
|
+
| course_code | 课程代码 | `MATA5B1001` | 将解析为字符串 |
|
25
|
+
| course_name | 课程名称 | `高等数学(上)` | 将解析为字符串 |
|
26
|
+
| credit | 课程学分 | `5` | 将解析为浮点数 |
|
27
|
+
| required | 是否必修 | `1` | `1` 为必修,`0` 为选修 |
|
28
|
+
|
29
|
+
```csv
|
30
|
+
course_code,course_name,credit,required
|
31
|
+
MATA5B1001,高等数学(上),5,1
|
32
|
+
...
|
33
|
+
```
|
34
|
+
|
35
|
+
### 安装本软件包
|
36
|
+
|
37
|
+
```bash
|
38
|
+
pip install CourseComparator
|
39
|
+
```
|
40
|
+
|
41
|
+
### 运行程序
|
42
|
+
|
43
|
+
```python
|
44
|
+
# 导入课程比较器模块
|
45
|
+
import CourseComparator as cc
|
46
|
+
|
47
|
+
# 传入数据集根目录,初始化数据加载器
|
48
|
+
loader = cc.init("<数据集根目录>")
|
49
|
+
|
50
|
+
# 获取旧的课程方案
|
51
|
+
old_courses = loader("<专业>", "<届别>", <学期>)
|
52
|
+
|
53
|
+
# 获取新的课程方案
|
54
|
+
new_courses = loader("<专业>", "<届别>", <学期>)
|
55
|
+
|
56
|
+
# 打印两个方案的差异
|
57
|
+
print(old_courses - new_courses)
|
58
|
+
```
|
@@ -0,0 +1,18 @@
|
|
1
|
+
[build-system]
|
2
|
+
requires = ["setuptools>=42", "wheel"]
|
3
|
+
build-backend = "setuptools.build_meta"
|
4
|
+
|
5
|
+
[project]
|
6
|
+
name = "CourseComparator"
|
7
|
+
version = "0.1.0"
|
8
|
+
description = "用于比较不同的专业、届别或年级之间的课程设置,供转专业、降级学生确认可冲抵及需补修的课程。"
|
9
|
+
authors = [{ name = "Vincy SHI", email = "vincy@vincy1230.net" }]
|
10
|
+
readme = "README.md"
|
11
|
+
license = "MIT"
|
12
|
+
dependencies = []
|
13
|
+
|
14
|
+
[tool.setuptools.packages.find]
|
15
|
+
include = ["CourseComparator*"]
|
16
|
+
|
17
|
+
[project.urls]
|
18
|
+
Homepage = "https://github.com/vincy1230/Course-Comparator"
|