编程练习
18 道题,覆盖十章核心能力。每题给解题思路、参考代码和运行结果,复制到本机跑一遍就能对照。
怎么用① 先自己写;② 卡住看思路,还不懂再看代码;③ 跑通后改改输入再试,才算真会。
第1章 Scala 入门与环境
01
用 Scala 输出一行「课程:Scala 程序设计,学时:64」,课程名和学时分别放进 val,再用字符串插值拼出来。 基础
解题思路用 object Ex extends App 外壳;val 保存两个值,s"..." 把变量插入字符串,保存为 Ex.scala 后 scalac 编译、scala 运行。
查看参考代码与运行结果
object Ex extends App {
val course = "Scala 程序设计"
val hours = 64
println(s"课程:$course,学时:$hours")
}👀 运行结果
课程:Scala 程序设计,学时:64
第2章 变量、类型与表达式
02
把 37.0 摄氏度换算成华氏度(公式:华氏度 = 摄氏度 * 9 / 5 + 32),输出保留 1 位小数的结果。 基础
解题思路用 App 外壳;把摄氏度声明成 Double,先乘 9 再除以 5,避免整数除法截断;f 插值用 %.1f 控制小数位。
查看参考代码与运行结果
object Ex extends App {
val celsius = 37.0
val fahrenheit = celsius * 9 / 5 + 32
println(f"$celsius%.1f 摄氏度 = $fahrenheit%.1f 华氏度")
}👀 运行结果
37.0 摄氏度 = 98.6 华氏度
03
固定整数 n = 583,用整数除法和取余拆出百位、十位、个位,输出三个数字和它们的和。 基础
解题思路用 App 外壳;Int 的 / 是整除,% 是取余;n / 100 取百位,n / 10 % 10 取十位,n % 10 取个位。
查看参考代码与运行结果
object Ex extends App {
val n = 583
val hundreds = n / 100
val tens = n / 10 % 10
val ones = n % 10
println(s"$n 的百位是 $hundreds,十位是 $tens,个位是 $ones,各位和 = ${hundreds + tens + ones}")
}👀 运行结果
583 的百位是 5,十位是 8,个位是 3,各位和 = 16
第3章 控制结构
04
按分数分级:90 分及以上为优秀,80 到 89 为良好,60 到 79 为及格,低于 60 为不及格;对 score = 85 输出「85 分:良好」。 基础
解题思路用 App 外壳;Scala 的 if/else 是表达式,可以直接把整条判断赋给 val level,不需要先声明再逐个赋值。
查看参考代码与运行结果
object Ex extends App {
val score = 85
val level =
if (score >= 90) "优秀"
else if (score >= 80) "良好"
else if (score >= 60) "及格"
else "不及格"
println(s"$score 分:$level")
}👀 运行结果
85 分:良好
05
分别用带守卫的 for 和 while 循环,求 1 到 100 中所有奇数的和,输出两种写法的结果。 进阶
解题思路用 App 外壳;for 的 if 守卫筛掉偶数,while 要自己维护计数器并递增;累加变量需要 var。
查看参考代码与运行结果
object Ex extends App {
var sumFor = 0
for (n <- 1 to 100 if n % 2 == 1) {
sumFor += n
}
var i = 1
var sumWhile = 0
while (i <= 100) {
if (i % 2 == 1) sumWhile += i
i += 1
}
println(s"for 循环奇数和 = $sumFor")
println(s"while 循环奇数和 = $sumWhile")
}👀 运行结果
for 循环奇数和 = 2500 while 循环奇数和 = 2500
第4章 函数与高阶函数
06
用递归写 factorial(n),求 6 的阶乘,输出「6! = 720」。 基础
解题思路用 App 外壳;递归函数先写终止条件 n 小于等于 1,再写 n * factorial(n - 1),6! 在 Int 范围内。
查看参考代码与运行结果
object Ex extends App {
def factorial(n: Int): Int =
if (n <= 1) 1 else n * factorial(n - 1)
println(s"6! = ${factorial(6)}")
}👀 运行结果
6! = 720
07
写高阶函数 twice(f, x),把函数 f 连续应用两次;用「加 3」和「乘 2」两个函数各验证一次。 进阶
解题思路用 App 外壳;f 的类型是 Int => Int,把函数当值传进来;先给 val 标好函数类型,再用 twice 连续调用。
查看参考代码与运行结果
object Ex extends App {
def twice(f: Int => Int, x: Int): Int = f(f(x))
val add3: Int => Int = _ + 3
val double: Int => Int = _ * 2
println(s"twice(add3, 5) = ${twice(add3, 5)}")
println(s"twice(double, 3) = ${twice(double, 3)}")
}👀 运行结果
twice(add3, 5) = 11 twice(double, 3) = 12
08
求斐波那契数列第 10 项和第 30 项(fib(0)=0、fib(1)=1),要求用尾递归,并输出两行结果。 高阶
解题思路用 App 外壳;内层 loop 把已经算出的两个数 a、b 继续往下传,@tailrec 让编译器确认可优化,BigInt 防止大数溢出。
查看参考代码与运行结果
object Ex extends App {
import scala.annotation.tailrec
def fib(n: Int): BigInt = {
@tailrec
def loop(i: Int, a: BigInt, b: BigInt): BigInt =
if (i == n) a
else loop(i + 1, b, a + b)
loop(0, 0, 1)
}
println(s"fib(10) = ${fib(10)}")
println(s"fib(30) = ${fib(30)}")
}👀 运行结果
fib(10) = 55 fib(30) = 832040
第5章 集合
09
统计 List("scala", "spark", "scala", "akka", "scala", "spark") 中每个词出现的次数,按次数从高到低、次数相同按单词升序输出。 进阶
解题思路用 App 外壳;groupBy(identity) 分组,再 map 成「单词 -> 个数」,最后用 (-次数, 单词) 作为复合排序键。
查看参考代码与运行结果
object Ex extends App {
val words = List("scala", "spark", "scala", "akka", "scala", "spark")
val counts = words.groupBy(identity).map { case (word, items) => word -> items.size }
counts.toSeq
.sortBy { case (word, count) => (-count, word) }
.foreach { case (word, count) => println(s"$word: $count") }
}👀 运行结果
scala: 3 spark: 2 akka: 1
10
给定 Map("张三" -> 88, "李四" -> 95, "王五" -> 76, "赵六" -> 91),筛出 85 分及以上的学生,按分数从高到低输出姓名和分数。 进阶
解题思路用 App 外壳;Map 先 toSeq 变成序列,再 filter 和 sortBy;元组用 case 解构,排序时取负分实现降序。
查看参考代码与运行结果
object Ex extends App {
val scores = Map("张三" -> 88, "李四" -> 95, "王五" -> 76, "赵六" -> 91)
scores.toSeq
.filter { case (_, score) => score >= 85 }
.sortBy { case (_, score) => -score }
.foreach { case (name, score) => println(s"$name $score") }
}👀 运行结果
李四 95 赵六 91 张三 88
第6章 类与对象
11
定义 Student 类(name、score)和伴生对象 Student 的 apply 工厂,判断是否及格;对小明 82 分、小刚 55 分输出结果。 基础
解题思路用 App 外壳;class 主构造器直接声明字段,伴生 object 提供 apply,之后可写 Student(...) 而不写 new。
查看参考代码与运行结果
class Student(val name: String, val score: Int) {
def passed: Boolean = score >= 60
}
object Student {
def apply(name: String, score: Int): Student = new Student(name, score)
}
object Ex extends App {
val a = Student("小明", 82)
val b = Student("小刚", 55)
println(s"${a.name}:${a.score} 分,${if (a.passed) "及格" else "不及格"}")
println(s"${b.name}:${b.score} 分,${if (b.passed) "及格" else "不及格"}")
}👀 运行结果
小明:82 分,及格 小刚:55 分,不及格
12
用 trait Named 和 trait Scored 组合出学生能力:前者要求 name,后者要求 score 并提供 passed,创建 3 名学生按分数降序输出描述。 进阶
解题思路用 App 外壳;两个 trait 各管一项能力,类用 extends ... with ... 混入;sortBy(s => -s.score) 做降序。
查看参考代码与运行结果
trait Named {
def name: String
}
trait Scored {
def score: Int
def passed: Boolean = score >= 60
}
class Student(val name: String, val score: Int) extends Named with Scored {
def describe: String = s"$name:$score 分,${if (passed) "及格" else "不及格"}"
}
object Ex extends App {
val students = List(new Student("小明", 82), new Student("小刚", 55), new Student("小红", 91))
students.sortBy(s => -s.score).foreach(s => println(s.describe))
}👀 运行结果
小红:91 分,及格 小明:82 分,及格 小刚:55 分,不及格
第7章 面向对象进阶
13
定义抽象类 Shape,让 Rectangle(3, 4) 和 Circle(2) 实现 area,打印每个图形的面积(保留 2 位小数)和总面积。 基础
解题思路用 App 外壳;抽象类只声明 area,子类分别实现;List[Shape] 用多态统一遍历,重写 toString 控制输出格式。
查看参考代码与运行结果
abstract class Shape(val name: String) {
def area: Double
override def toString: String = f"$name 面积 = $area%.2f"
}
class Rectangle(w: Double, h: Double) extends Shape("矩形") {
def area: Double = w * h
}
class Circle(r: Double) extends Shape("圆") {
def area: Double = math.Pi * r * r
}
object Ex extends App {
val shapes: List[Shape] = List(new Rectangle(3.0, 4.0), new Circle(2.0))
shapes.foreach(s => println(s.toString))
println(f"总面积 = ${shapes.map(_.area).sum}%.2f")
}👀 运行结果
矩形 面积 = 12.00 圆 面积 = 12.57 总面积 = 24.57
14
用 sealed trait 定义订单状态:待付款、已付款、已发货、已取消(原因),写 match 打印四种状态的中文描述。 进阶
解题思路用 App 外壳;sealed 把子类型限制在同一文件,match 覆盖全部 case 时编译器能检查穷尽性;case class 还能解构出取消原因。
查看参考代码与运行结果
sealed trait OrderStatus
case object Pending extends OrderStatus
case object Paid extends OrderStatus
case object Shipped extends OrderStatus
case class Cancelled(reason: String) extends OrderStatus
object Ex extends App {
def describe(s: OrderStatus): String = s match {
case Pending => "待付款"
case Paid => "已付款,待发货"
case Shipped => "已发货"
case Cancelled(reason) => s"已取消:$reason"
}
val statuses = List(Pending, Paid, Shipped, Cancelled("库存不足"))
statuses.foreach(s => println(describe(s)))
}👀 运行结果
待付款 已付款,待发货 已发货 已取消:库存不足
第8章 异常与 Option
15
实现 safeDiv(a, b) 返回 Option[Int];用模式匹配处理 10 除以 2,用 getOrElse 处理 7 除以 0,输出两行结果。 进阶
解题思路用 App 外壳;Option 把“可能没有值”写进类型;Some/None 可以 match,也可以用 getOrElse 提供兜底值。
查看参考代码与运行结果
object Ex extends App {
def safeDiv(a: Int, b: Int): Option[Int] =
if (b == 0) None else Some(a / b)
println(safeDiv(10, 2) match {
case Some(v) => s"10 / 2 = $v"
case None => "除数不能为 0"
})
println(s"7 / 0 = ${safeDiv(7, 0).getOrElse("无法计算")}")
}👀 运行结果
10 / 2 = 5 7 / 0 = 无法计算
16
用 Either 实现注册校验:姓名不能为空、年龄必须满 18 岁;对 ("小明", 20)、(" ", 20)、("小红", 16) 分别输出成功或失败原因。 高阶
解题思路用 App 外壳;Either 的 for 推导在第一个 Left 处短路,只在全为 Right 时 yield 结果;最后用 match 转成中文提示。
查看参考代码与运行结果
object Ex extends App {
def checkName(name: String): Either[String, String] =
if (name.trim.nonEmpty) Right(name.trim) else Left("姓名不能为空")
def checkAge(age: Int): Either[String, Int] =
if (age >= 18) Right(age) else Left("年龄必须满 18 岁")
def register(name: String, age: Int): Either[String, (String, Int)] =
for {
n <- checkName(name)
a <- checkAge(age)
} yield (n, a)
List(("小明", 20), (" ", 20), ("小红", 16)).foreach { case (name, age) =>
register(name, age) match {
case Right((n, a)) => println(s"注册成功:$n,$a 岁")
case Left(msg) => println(s"注册失败:$msg")
}
}
}👀 运行结果
注册成功:小明,20 岁 注册失败:姓名不能为空 注册失败:年龄必须满 18 岁
第9章 泛型与隐式
17
为 case class Student(name, score) 提供隐式 Ordering,写泛型函数 top,求小明 82、小刚 55、小红 91 中分数最高的学生。 进阶
解题思路用 App 外壳;top 用类型参数 T 和隐式 Ordering[T] 支持任意可比较类型;Ordering.by(_.score) 让 Student 按分数比较。Scala 3 把 implicit 改成 given/using,思路相同。
查看参考代码与运行结果
case class Student(name: String, score: Int)
object Ex extends App {
implicit val studentOrdering: Ordering[Student] = Ordering.by(_.score)
def top[T](xs: List[T])(implicit ord: Ordering[T]): T = xs.max(ord)
val students = List(Student("小明", 82), Student("小刚", 55), Student("小红", 91))
val best = top(students)
println(s"最高分:${best.name} ${best.score} 分")
}👀 运行结果
最高分:小红 91 分
第10章 实战:文件、JSON 与并发
18
准备 scores.csv(表头 name,cls,score;数据:小明,一班,92 / 小红,一班,85 / 小刚,二班,78 / 小美,二班,96),用 scala.io.Source 读取,按班级统计人数、平均分、最高分,按平均分从高到低输出,最后输出全年级平均分(保留 2 位小数)。 高阶
解题思路用 App 外壳;先用 java.nio 写出 scores.csv,这段代码复制出去就能直接跑;Source.fromFile 用 UTF-8 读入并及时 close,drop(1) 去表头,split 后转 Int,groupBy 分组统计,sortBy(-平均分) 排序。
查看参考代码与运行结果
import java.nio.file.{Files, Paths}
import scala.io.Source
object Ex extends App {
// ① 先把数据写成文件,这段代码复制出去就能直接跑
val path = "scores.csv"
Files.writeString(
Paths.get(path),
"name,cls,score\n小明,一班,92\n小红,一班,85\n小刚,二班,78\n小美,二班,96\n"
)
// ② 读文件:指定 UTF-8,读完记得关
val src = Source.fromFile(path, "UTF-8")
val lines = src.getLines().toList
src.close()
val rows = lines.drop(1).filter(_.trim.nonEmpty).map { line =>
val parts = line.split(",").map(_.trim)
(parts(0), parts(1), parts(2).toInt)
}
val byClass = rows.groupBy(_._2)
val stats = byClass.toSeq.map { case (cls, items) =>
val scores = items.map(_._3)
(cls, items.size, scores.sum.toDouble / scores.size, scores.max)
}.sortBy { case (_, _, avg, _) => -avg }
stats.foreach { case (cls, n, avg, max) =>
println(f"$cls:$n 人,平均 $avg%.2f,最高 $max")
}
println(f"全年级平均:${rows.map(_._3).sum.toDouble / rows.size}%.2f")
}👀 运行结果
一班:2 人,平均 88.50,最高 92 二班:2 人,平均 87.00,最高 96 全年级平均:87.75