前期准备
数据展示,只展示部分数据
学生数据 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)
}