scala练习 —— 求取年级总分前十名

前期准备

数据展示,只展示部分数据

学生数据 students.txt

1500100001,施笑槐,22,女,文科六班

成绩数据 score.txt

1500100006,1000001,87
1500100006,1000002,98
1500100006,1000003,55
1500100006,1000007,44
1500100006,1000008,1
1500100006,1000009,29

科目数据 subject.txt

1000001,语文,150
1000002,数学,150
1000003,英语,150
1000004,政治,100
1000005,历史,100
1000006,物理,100
1000007,化学,100
1000008,地理,100
1000009,生物,100

如果每一次使用数据的时候都要对每一行按照逗号进行split切分,无疑是很麻烦的

这里可以针对三个数据文件创建三个样例类,将每一行内容都转换成一个对象,这样可以用 样例类对象 . 属性 的方式获取某一个值


package test

import org.junit.{Before, Test}

import scala.io.{BufferedSource, Source}

class testSelf {
  var students: List[Students] = _
  var scores: List[Score] = _
  var subjects: List[Subject] = _

  @Before
  //这个方法的作用是读取文件的每一行内容(getLines),每一行内容对应生成一个对象,每个对象放入对应的List集合中
  //利用map对每一行内容进行切分,将切分的结果按照属性放入students、scores或者是subjects,其中一个List集合中
  def read_file(): Unit = {
    //students是一个List集合,里面的每一个元素都是一个Students对象,元素举例:Students(1500100001,施笑槐,22,女,文科六班)
    val stu_bs: BufferedSource = Source.fromFile("data/students.txt")
    students = stu_bs
      .getLines()
      .toList //上一步返回的结果是Iterator[String]类型,这里要转为List
      .map(line => {
        val splits: Array[String] = line.split(",")
        Students(splits(0).toInt, splits(1), splits(2).toInt, splits(3), splits(4))
      })
    stu_bs.close()

    //scores集合
    val sco_bs: BufferedSource = Source.fromFile("data/score.txt")
    scores = sco_bs
      .getLines()
      .toList
      .map(line => {
        val splits: Array[String] = line.split(",")
        Score(splits(0).toInt, splits(1).toInt, splits(1).toInt)
      })
    sco_bs.close()

    //subject集合
    val sub_bs: BufferedSource = Source.fromFile("data/subject.txt")
    subjects = sub_bs
      .getLines()
      .toList
      .map(line => {
        val splits: Array[String] = line.split(",")
        Subject(splits(0).toInt, splits(1), splits(2).toInt)
      })
    sub_bs.close()
  }

  @Test
  //测试一下上面是否都生成对象成功。因为上面是@Before,所以会在每个@Test方法前执行
  def ListTest(): Unit = {
    students.take(10).foreach(println)
    scores.take(10).foreach(println)
    subjects.take(10).foreach(println)
  }
}

case class Students(id: Int, name: String, age: Int, gender: String, clazz: String)
case class Score(id: Int, subject_id: Int, score: Int)
case class Subject(subject_id: Int, subject_name: String, subject_score: Int)

首先构建三个对象,用来分别保存文件中的每一行,再把一个文件的全部对象保存在一个List集合里面,这样方便读取使用

结果举例,

Students(1500100001,施笑槐,22,女,文科六班)
Score(1500100001,1000001,1000001)
Subject(1000001,语文,150)

这里设置的@Before,所以每个@Test方法运行前都会重新生成一遍,students,scores,subjects

具体执行的代码

@Test
  // 1、统计年级排名前十学生各科的分数 [学号,学生姓名,学生班级,科目名,分数]
  def clazz_topn(): Unit = {
    // 1、首先找到前十名的学生,并获取他们的id
    //求每个学生的总分
    val stu_id_top: List[Int] = scores
      .groupBy(sco => sco.id)
      .toList
      .map {
        case (id: Int, scoList: List[Score]) =>
          val scoreList: List[Int] = scoList.map(sco => sco.score)
          //返回学生id和学生总分
          (id, scoreList.sum)
      } //到这一步求出了每个学生的总分 List[Int,Int]
      .sortBy(-_._2) //按照总分进行排序
      .take(10) //取出前十名学生
      .toMap
      .keys //获取前十名学生每人的id
      .toList

    // 2、首先确定以scores为基准,然后联系students和subjects,获取相应的属性值
    //构建students的Map集合,key是学生id,value是Score对象
    val stuMap: Map[Int, Students] = students
      .filter(stu => stu_id_top.contains(stu.id))
      .map(stu => (stu.id, stu))
      .toMap

    //构建subject的Map集合,key是subject_id,value是Subject对象
    val subMap: Map[Int, Subject] = subjects.map(sub => (sub.subject_id, sub)).toMap

    //以scores为基准进行联系
    val stu_topn: List[(Int, String, String, String, Int)] = scores
      .filter(sco => stu_id_top.contains(sco.id))  //这里也需要过滤,只要前十名的学生
      .map(sco => {
        val id: Int = sco.id //获取学生id
        val stu: Students = stuMap(id) //根据学生id获取学生对象
        val sub_id: Int = sco.subject_id //获取科目id
        val sub: Subject = subMap(sub_id) //根据科目id获取科目对象

        val name: String = stu.name
        val clazz: String = stu.clazz
        val sub_name: String = sub.subject_name
        val score: Int = sco.score
        //最后把这些属性放在一个元组里面返回,连接成一个字符串也行
        (id, name, clazz, sub_name, score)
      })

    stu_topn.foreach(println)
  }

这里需要多加注意的是,因为students和subjects都是一个id对应一行,一行就是一个对象。但是对于scores,里面一个id对应多行内容,每行都是一个对象

如果以students为基准表,那么一个学生id对应多个成绩id,此时如果想打印出来每个(科目的)成绩,就需要借助ListBuffer,一个ListBuffer对应一个学生,里面存放的每个元素都是一个学生和一个科目成绩
这样返回值是一个ListBuffer,里面是一个学生的全部科目,如果有六个科目就是六个元素
然后用flatMap而不是map,将ListBuffer拆开,这样就达成最终结果里面,一个元素对应一个学生的一个科目成绩

像上面这样无疑是很麻烦的,所以不如干脆就以scores为基准,然后联系students和subjects
这样对scores的map,每个元素都是一个科目,然后再拿到学生姓名和科目名称即可
姓名,科目等属性值的获取方式是按照(id,对象)的方式将students和subjects变成Map集合,然后让scores用id就可以获取对象,再通过对象获取属性值

上面执行的结果是,截取部分结果

在这里插入图片描述
另外给出,如果使用ListBuffer,应该怎么写

 @Test
  def sco_topn(): Unit = {
    //求取每个学生的总分
    val id_topn: List[Int] = scores
      .groupBy(sco => sco.id)
      .map {
        case (id: Int, sco_list: List[Score]) =>
          val score: List[Int] = sco_list.map(sco => sco.score)
          (id, score.sum)
      }
      .toList
      .sortBy(-_._2) 	//此时List里面的元素是(id,socre.num),所以通过 _._2可以取出score.sum,前面加上负号表示降序
      .take(10) 		//取出前十条数据。即找到总分前十名的学生
      .map(_._1) 		//取出前十名学生的id


    //构建scores的Map集合,从而根据学生id获取科目id
    //Map[Int, List[(Int, Score)]]第一个Int是groupBy的分组字段学生id。第二个Int是map函数的返回结果(sco.id, sco)的sco.id,同样是学生id。而最后一个 Score,是scores样例类的对象
    val scoMap: Map[Int, List[(Int, Score)]] = scores
      .map(sco => (sco.id, sco))
      .groupBy(_._1)

    //构建subject列表(包括上面的students,scores在类的开头就被定义了)的Map集合
    val subMap: Map[Int, Subject] = subject
      .map(sub => (sub.subject_id, sub))
      .toMap

    /*
    下面首先是根据上面获取的前十名学生id,筛选出students里面这十个学生的信息
    然后使用flatMap对每个元素进行处理,这里不用map,而是flatMap,因为返回值是一个ListBuffer
    ListBuffer里面的每一个元素才是需要的学生信息,因此需要将List[ListBuffer[()]]拍扁成List[()]
     */
    students
      .filter(stu => id_topn.contains(stu.id))
      .flatMap(stu => {
        val listBuffer: ListBuffer[(Int, String, String, String, Int)] = ListBuffer[(Int, String, String, String, Int)]()
        val id: Int = stu.id
        val name: String = stu.name
        val clazz: String = stu.clazz
        val scoreList: List[Score] = scoMap(id).map(kv => kv._2)
        //上面获取的是一个学生id,对应的多个Score样例类对象构成的List集合
        //对这个集合再遍历一次,等于一个学生的一个科目
        //foreach会遍历每一个元素,也就是一个学生id对应的全部Score对象,也就是全部科目
        scoreList.foreach(sco => {
          val subject_id: Int = sco.subject_id //取出科目id
          val score: Int = sco.score //取出每个科目的成绩
          val subject_name: String = subMap(subject_id).subject_name //取出每个科目的名称
          //将学生id,name,clazz,科目id,科目成绩包装成一个元组,作为一个元素放入ListBuffer里面
          listBuffer.append((id, name, clazz, subject_name, score))
        })
        listBuffer
      }).foreach(println)
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一纸春秋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值