Exercise: Refactor collection processing
In your code, you found a complex collection processing implemented using classic Java-like techniques. Your task is to refactor this code to use Kotlin collection processing functions. The function is responsible for finding the best students for internships. This is what is looks like:
fun List<StudentGrades>.getBestForScholarship(
semester: String
): List<StudentGrades> {
val students = this
var candidates = mutableListOf<StudentGrades>()
for (s in students) {
var ectsPointsGained = 0
for (g in s.grades) {
if (g.semester == semester && g.passing) {
ectsPointsGained += g.ects
}
}
if (ectsPointsGained > 30) {
candidates.add(s)
}
}
Collections.sort(candidates, { s1, s2 ->
val difference =
averageGradeFromSemester(s2, semester) -
averageGradeFromSemester(s1, semester)
if (difference > 0) 1 else -1
})
val best = mutableListOf<StudentGrades>()
for (i in 0 until 10) {
val next = candidates.getOrNull(i)
if (next != null) {
best.add(next)
}
}
return best
}
private fun averageGradeFromSemester(
student: StudentGrades,
semester: String
): Double {
var sum = 0.0
var count = 0
for (g in student.grades) {
if (g.semester == semester) {
sum += g.grade
count++
}
}
return sum / count
}
data class Grade(
val passing: Boolean,
var ects: Int,
var semester: String,
var grade: Double
)
data class StudentGrades(
val studentId: String,
val grades: List<Grade>
)
Without changing behavior, refactor those functions.
This problem can either be solved in the below playground or you can clone
kotlin-exercises project and solve it locally. In the project, you can find code template for this exercise in functional/collections/StudentGradesIntenship.kt. You can find there starting code and unit tests.
Hint: To calculate an average of all the numbers in a list, you can use the average function.
Once you are done with the exercise, you can check your solution
here.
Playground
import org.junit.Test
import java.util.Collections
import kotlin.random.Random
import kotlin.test.assertEquals
fun List<StudentGrades>.getBestForScholarship(
semester: String
): List<StudentGrades> {
val students = this
var candidates = mutableListOf<StudentGrades>()
for (s in students) {
var ectsPointsGained = 0
for (g in s.grades) {
if (g.semester == semester && g.passing) {
ectsPointsGained += g.ects
}
}
if (ectsPointsGained > 30) {
candidates.add(s)
}
}
Collections.sort(candidates, { s1, s2 ->
val difference =
averageGradeFromSemester(s2, semester) -
averageGradeFromSemester(s1, semester)
if (difference > 0) 1 else -1
})
val best = mutableListOf<StudentGrades>()
for (i in 0 until 10) {
val next = candidates.getOrNull(i)
if (next != null) {
best.add(next)
}
}
return best
}
private fun averageGradeFromSemester(
student: StudentGrades,
semester: String
): Double {
var sum = 0.0
var count = 0
for (g in student.grades) {
if (g.semester == semester) {
sum += g.grade
count++
}
}
return sum / count
}
data class Grade(
val passing: Boolean,
var ects: Int,
var semester: String,
var grade: Double
)
data class StudentGrades(
val studentId: String,
val grades: List<Grade>
)
class StudentGradesIntenshipTest {
@Test
fun `should return best students for scholarship`() {
val r = Random(123456789)
val grades = List(100) {
StudentGrades(
"S$it",
List(100) {
Grade(
passing = r.nextBoolean(),
ects = r.nextInt(5) + 1,
semester = "Semester ${r.nextInt(5)}",
grade = r.nextDouble() * 5 + 2.0
)
}
)
}
val res1 = grades.getBestForScholarship("Semester 0")
assertEquals(
listOf("S83", "S47", "S26", "S53", "S4", "S29", "S50", "S34", "S44", "S59"),
res1.map { it.studentId },
)
assertEquals(
listOf("S30", "S66", "S49", "S14", "S6", "S12", "S25", "S99", "S7", "S40"),
grades.getBestForScholarship("Semester 1").map { it.studentId }
)
}
@Test
fun `should return empty list if no students passed`() {
val grades = listOf(
StudentGrades(
"S1",
listOf(
Grade(false, 5, "Semester 0", 3.0),
Grade(false, 5, "Semester 1", 3.0),
Grade(false, 5, "Semester 2", 3.0),
Grade(false, 5, "Semester 3", 3.0),
Grade(false, 5, "Semester 4", 3.0),
)
),
StudentGrades(
"S2",
listOf(
Grade(false, 5, "Semester 0", 3.0),
Grade(false, 5, "Semester 1", 3.0),
Grade(false, 5, "Semester 2", 3.0),
Grade(false, 5, "Semester 3", 3.0),
Grade(false, 5, "Semester 4", 3.0),
)
),
StudentGrades(
"S3",
listOf(
Grade(false, 5, "Semester 0", 3.0),
Grade(false, 5, "Semester 1", 3.0),
Grade(false, 5, "Semester 2", 3.0),
Grade(false, 5, "Semester 3", 3.0),
Grade(false, 5, "Semester 4", 3.0),
)
),
)
assertEquals(
emptyList<String>(),
grades.getBestForScholarship("Semester 0").map { it.studentId }
)
}
@Test
fun `should sort by average grade for semester`() {
val grades = listOf(
StudentGrades(
"S1",
listOf(
Grade(true, 25, "Semester 1", 3.0),
Grade(true, 25, "Semester 1", 3.0),
)
),
StudentGrades(
"S2",
listOf(
Grade(true, 50, "Semester 1", 4.0),
Grade(true, 50, "Semester 2", 1.0),
Grade(true, 50, "Semester 3", 1.0),
Grade(true, 50, "Semester 4", 1.0),
)
),
StudentGrades("S3", listOf(Grade(true, 50, "Semester 1", 5.0),)),
)
assertEquals(
listOf("S3", "S2", "S1"),
grades.getBestForScholarship("Semester 1").map { it.studentId }
)
}
@Test
fun `should return 10 best students`() {
val grades = List(100) {
StudentGrades(
"S$it",
listOf(
Grade(
passing = true,
ects = 50,
semester = "Semester 0",
grade = 5.0 + (0.01 * it)
)
)
)
}
assertEquals(
List(10) { "S${99 - it}" },
grades.getBestForScholarship("Semester 0").map { it.studentId }
)
}
@Test
fun `should calculate average grade from semester`() {
assertEquals(
3.0,
averageGradeFromSemester(StudentGrades(
"S1",
listOf(
Grade(true, 25, "Semester 1", 3.0),
Grade(true, 25, "Semester 1", 3.0),
)
), "Semester 1")
)
assertEquals(
3.0,
averageGradeFromSemester(StudentGrades(
"S1",
listOf(
Grade(true, 10, "Semester 1", 2.0),
Grade(true, 20, "Semester 1", 4.0),
)
), "Semester 1")
)
assertEquals(
2.0,
averageGradeFromSemester(StudentGrades(
"S1",
listOf(
Grade(true, 10, "Semester 1", 2.0),
Grade(true, 20, "Semester 2", 4.0),
)
), "Semester 1")
)
}
}