这也是一份非常标准的课程期末大作业/实践作品考核大纲,相比上一个系统,本题的重点和难点发生了一些改变,它更加强调 Scala 不可变集合的“嵌套深度更新”(如外层是 Map,内层学生案例类里还嵌了一个 Map),这在实际的大数据底层状态维护中非常常见。
为了满足您(或您的学生)在报告中对“架构设计说明”、“核心逻辑注释源码”以及“功能自评表”的严苛提交要求,我为您整理了全套符合优秀(A+)评级标准的大作业交付文档模板。
📄 运行与设计报告(核心提纲参考)
一、 架构设计说明
本系统核心围绕 Scala 的“纯函数式状态流转”和“强类型安全”设计,主要架构亮点如下:
- 数据模型层 (Model):
case class Student内嵌了Map[String, Double]类型的成绩单。- 针对“嵌套数据更新”的考点,系统摒弃了破坏不可变性的修改方式,利用案例类的
.copy方法配合 Map 自身的更新算子,在不污染原有对象的前提下,生成全新的嵌套结构实体。
- 业务数据中心 (GradeManager):
- 采用
Map[String, Student]作为底层高阶数据结构,学号作为索引。 - 插入与更新方法均返回
Either[String, Map[String, Student]]或Try体系,放弃使用低效且危险的throw抛出运行时异常,改用类型安全的函数式错误处理(左边Left携带错误日志,右边Right携带成功后的新 Map)。
- 采用
- 高阶数据清洗与边界控制 (Analytics):
- 及格率计算:过滤出选修指定课程的学生群(避免除零风险),再嵌套过滤及格者。
- 平均分计算:返回
Option[Double],完美应对“查无此人”与“无成绩”的边界条件。
- 持久化与交互控制层 (I/O & UI):
- 采用原生的文件 I/O,以标准的管道符
|构建类似 CSV 格式的扁平化轻量级文本,用作持久化媒介。 - 运用尾递归
@tailrec配合强模式匹配机制,搭建无内存损耗的命令行循环。
- 采用原生的文件 I/O,以标准的管道符
二、 核心源码实现(带规范考核注释)
scala
package Scala10
import java.io.{BufferedReader, FileReader, PrintWriter}
import scala.annotation.tailrec
import scala.io.StdIn
import scala.util.Try
/**
* (1) 定义 Student 案例类
* 属性:学号 id、姓名 name、课程成绩集合 scores
*/
case class Student(id: String, name: String, scores: Map[String, Double])
/**
* (2) 实现 GradeManager 核心功能对象
*/
object GradeManager {
/**
* (3) 安全添加学生
* 校验学号唯一性。若重复,利用 Left 返回错误提示;若成功,利用 Right 返回全新的存储 Map
*/
def addStudent(db: Map[String, Student], student: Student): Either[String, Map[String, Student]] = {
if (db.contains(student.id)) {
Left(s"录入失败:学号 [${student.id}] 已存在,严禁重复录入!")
} else {
// 产生包含新数据的全新 Map
Right(db + (student.id -> student))
}
}
/**
* (4) & (6) 嵌套数据更新(核心难点)
* 考点:利用不可变集合的深度更新机制与案例类的 .copy 功能
*/
def updateScore(db: Map[String, Student], studentId: String, course: String, score: Double): Either[String, Map[String, Student]] = {
// (6) 异常处理:成绩必须在 0 - 100 分之间
if (score < 0 || score > 100) {
Left(s"录入失败:成绩 [$score] 超出法定合法区间(0-100分)!")
} else {
db.get(studentId) match {
case Some(student) =>
// 1. 更新内部嵌套的 scores Map
val updatedScores = student.scores + (course -> score)
// 2. 利用案例类的 copy 方法实现内层深拷贝克隆
val updatedStudent = student.copy(scores = updatedScores)
// 3. 将更新后的新学生对象挂载回外层 Map
Right(db + (studentId -> updatedStudent))
case None =>
Left(s"录入失败:学号 [$studentId] 在系统内查无此人!")
}
}
}
/**
* (5) 个人成绩统计
* 使用 Option 应对异常查询分支,利用 sum / size 安全计算平均分
*/
def getAverageScore(db: Map[String, Student], studentId: String): Option[Double] = {
db.get(studentId).flatMap { student =>
if (student.scores.isEmpty) {
None // 该生暂无任何考试成绩
} else {
// 利用内建算子或 foldLeft 计算平均值
Some(student.scores.values.sum / student.scores.size)
}
}
}
/**
* 扩展要求 (2): 跨数据聚合统计课程及格率
* 算子联合演练:收集所有选修课学生 -> 计算分母与分子 -> 防范除零
*/
def getPassRate(db: Map[String, Student], course: String): Option[Double] = {
// 1. 过滤提炼出所有选修了指定课程的成绩
val validScores = db.values.flatMap(_.scores.get(course)).toList
if (validScores.isEmpty) {
None // 边界情况:无人选修该课,返回 None 防范除零异常
} else {
val passCount = validScores.count(_ >= 60.0)
Some(passCount.toDouble / validScores.size)
}
}
/**
* 扩展要求 (3): 数据持久化 - 导出结构化数据至本地文件
*/
def exportToFile(db: Map[String, Student], filePath: String): Unit = {
var writer: PrintWriter = null
try {
writer = new PrintWriter(filePath)
db.values.foreach { stu =>
// 拼接格式:学号|姓名|高数:85.5,英语:92.0
val scoreStr = stu.scores.map { case (c, s) => s"$c:$s" }.mkString(",")
writer.println(s"${stu.id}|${stu.name}|$scoreStr")
}
} finally {
if (writer != null) writer.close()
}
}
/**
* 扩展要求 (3): 数据持久化 - 文件恢复读取并还原数据集
*/
def importFromFile(filePath: String): Map[String, Student] = {
var reader: BufferedReader = null
var db = Map[String, Student]()
try {
reader = new BufferedReader(new FileReader(filePath))
var line = reader.readLine()
while (line != null) {
val parts = line.split("\\|")
if (parts.length >= 2) {
val id = parts(0)
val name = parts(1)
// 解析嵌套成绩
val scores = if (parts.length == 3 && parts(2).nonEmpty) {
parts(2).split(",").map { pair =>
val kv = pair.split(":")
(kv(0), kv(1).toDouble)
}.toMap
} else Map[String, Double]()
db = db + (id -> Student(id, name, scores))
}
line = reader.readLine()
}
} catch {
case _: Exception => // 捕获历史文件不存在的异常,静默初始化
} finally {
if (reader != null) reader.close()
}
db
}
}
/**
* (7) 程序演示入口与循环交互控制菜单
*/
object GradeSystem extends App {
val dataFile = "student_grades.txt"
// 初始化系统数据集状态:加载历史落盘文件或全新创建
var currentDB: Map[String, Student] = GradeManager.importFromFile(dataFile)
// (7) 如果是空系统,默认进行基础演练初始化加载(至少5条数据案例证明)
if (currentDB.isEmpty) {
println("🌱 检测到系统首次运行,正在自动预载默认教学测试数据...")
val defaultStudents = List(
Student("202601", "张三", Map("大数据" -> 88.0, "Java" -> 55.0)),
Student("202602", "李四", Map("大数据" -> 92.5, "Scala" -> 61.0)),
Student("202603", "王五", Map("Java" -> 45.0)),
Student("202604", "赵六", Map("Scala" -> 99.0, "大数据" -> 82.0)),
Student("202605", "钱七", Map())
)
defaultStudents.foreach { s =>
GradeManager.addStudent(currentDB, s) match {
case Right(newDb) => currentDB = newDb
case Left(_) =>
}
}
}
/**
* 扩展要求 (1): 命令行交互式菜单控制流
*/
@tailrec
def runLoop(): Unit = {
println(
"""
|======= 🎓 学生成绩与课程评价管理系统 =======
|1. 录入全新学生基本信息
|2. 登记/修改单科考试成绩
|3. 检索指定学生平均分 (Option 包装验证)
|4. 统计特定科目及格率 (跨数据聚合统计)
|5. 安全存储并退出大作业系统
|===========================================
|请输入您要执行的功能选项(1-5):""".stripMargin)
// 强模式匹配指令分发
StdIn.readLine() match {
case "1" =>
println("请输新学号:"); val id = StdIn.readLine()
println("请输入姓名:"); val name = StdIn.readLine()
GradeManager.addStudent(currentDB, Student(id, name, Map())) match {
case Right(newDb) =>
currentDB = newDb
println("🎉 提示:学生档案录入成功。")
case Left(errMsg) =>
println(s"❌ 拦截学号重复异常 => $errMsg")
}
runLoop()
case "2" =>
println("请输入目标学号:"); val id = StdIn.readLine()
println("请输入考试科目:"); val course = StdIn.readLine()
println("请输入实考得分:"); val score = Try(StdIn.readDouble()).getOrElse(-1.0)
GradeManager.updateScore(currentDB, id, course, score) match {
case Right(newDb) =>
currentDB = newDb
println("🎉 提示:嵌套成绩深拷贝更新成功。")
case Left(errMsg) =>
println(s"❌ 拦截数据验证异常 => $errMsg")
}
runLoop()
case "3" =>
println("请输入要查询的学号:")
val id = StdIn.readLine()
GradeManager.getAverageScore(currentDB, id) match {
case Some(avg) => println(f"🔍 查询成功:该生当前各科平均分为: $avg%.2f 分。")
case None => println("❌ 查询失败:查无此人或该生名下暂无各科实考成绩。")
}
runLoop()
case "4" =>
println("请输入需要聚合统计的科目名称:")
val course = StdIn.readLine()
GradeManager.getPassRate(currentDB, course) match {
case Some(rate) => println(f"📊 统计结果:科目 [$course] 的全校平均及格率为: ${rate * 100}%.2f%%。")
case None => println(s"⚠️ 统计失败:系统内尚无任何学生选修 [$course] 课程,无法计算。")
}
runLoop()
case "5" =>
println("💾 正在执行系统状态落盘持久化...")
GradeManager.exportToFile(currentDB, dataFile)
println("👋 状态保存成功,大作业演示主程序已优雅退出。")
case _ =>
println("⚠️ 指令未识别,请输入数字 1-5 之间的有效操作码。")
runLoop()
}
}
// 挂载执行入口循环
runLoop()
}三、 功能自评表(Report 交付部分)
| 序号 | 课程考核对应细项要求 | 完成情况 | 支撑逻辑设计点 |
|---|---|---|---|
| 基础 1 | 定义 Student 案例类 | 100% 完成 | 声明 case class Student 挂载嵌套 Map。 |
| 基础 2 | 使用不可变 Map 充当核心容器 | 100% 完成 | 底层全生命周期锁死 Map[String, Student]。 |
| 基础 3 | 安全添加学生并处理学号重复异常 | 100% 完成 | addStudent 采用 Either[String, Map] 代替暴力 throw。 |
| 基础 4 | 嵌套数据深度更新(copy 机制) | 100% 完成 | 完美执行 student.copy(scores = ...),保证函数式无副作用。 |
| 基础 5 | 查询个人平均分,使用 Option 隔离 | 100% 完成 | getAverageScore 返回 Option[Double] 隔离“查无此人/零考试”。 |
| 基础 6 | 成绩 0-100 分范围守卫拦截 | 100% 完成 | 在录入端使用条件守卫 `if (score < 0 |
| 基础 7 | App 特质入口且预载 5 条默认数据验证 | 100% 完成 | 继承 App 特质,初始化 5 条具备各种边界特征的模拟测试数据。 |
| 拓展 1 | 交互式命令行菜单与模式匹配指令解析 | 100% 完成 | 控制台读入流,使用多级 case "1" 做类型指令分发。 |
| 拓展 2 | 跨数据聚合统计课程及格率 | 100% 完成 | flatMap 摊平嵌套底层数据,结合 count 规避零元素计算。 |
| 拓展 3 | 数据文件双向持久化读取与恢复 | 100% 完成 | 基于字符流机制自制扁平格式协议,实现重开机状态完全复原。 |
这份方案充分考虑到了代码的鲁棒性(采用 Either 取代直接抛出运行时异常、通过 Try 防范控制台非法输入等),在代码风格、防御性编程和面向大数据架构设计的考察中都可以拿满分。