编程练习

18 道题,覆盖十章核心能力。每题给解题思路、参考代码和运行结果,复制到本机跑一遍就能对照。

18 道题基础/进阶/高阶可直接运行
怎么用① 先自己写;② 卡住看思路,还不懂再看代码;③ 跑通后改改输入再试,才算真会。

第1章 Scala 入门与环境

01

用 Scala 输出一行「课程:Scala 程序设计,学时:64」,课程名和学时分别放进 val,再用字符串插值拼出来。 基础

解题思路用 object Ex extends App 外壳;val 保存两个值,s"..." 把变量插入字符串,保存为 Ex.scala 后 scalac 编译、scala 运行。
查看参考代码与运行结果
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 控制小数位。
查看参考代码与运行结果
SCALA
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 取个位。
查看参考代码与运行结果
SCALA
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,不需要先声明再逐个赋值。
查看参考代码与运行结果
SCALA
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。
查看参考代码与运行结果
SCALA
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 范围内。
查看参考代码与运行结果
SCALA
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 连续调用。
查看参考代码与运行结果
SCALA
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 防止大数溢出。
查看参考代码与运行结果
SCALA
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 成「单词 -> 个数」,最后用 (-次数, 单词) 作为复合排序键。
查看参考代码与运行结果
SCALA
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 解构,排序时取负分实现降序。
查看参考代码与运行结果
SCALA
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。
查看参考代码与运行结果
SCALA
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) 做降序。
查看参考代码与运行结果
SCALA
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 控制输出格式。
查看参考代码与运行结果
SCALA
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 还能解构出取消原因。
查看参考代码与运行结果
SCALA
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 提供兜底值。
查看参考代码与运行结果
SCALA
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 转成中文提示。
查看参考代码与运行结果
SCALA
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,思路相同。
查看参考代码与运行结果
SCALA
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(-平均分) 排序。
查看参考代码与运行结果
SCALA
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