article banner (priority)

Challenge: Refactor collection processing

Today I have a challenge for you that I find especially fun. I always liked refactoring code, to transform a hideous mess into a beautiful flow. It comes from the Functional Kotlin book and online course. Here is the challenge:

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.

Without changing behavior, refactor getBestForScholarship and averageGradeFromSemester functions. Here is a playground you can use to implement and test your solution. Use "run" button to start unit tests. To see them, use "plus".

import org.junit.Test import java.util.Collections import kotlin.random.Random import kotlin.test.assertEquals 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> ) //sampleStart 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 } //sampleEnd 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") ) } }

Hint: To calculate an average of all the numbers in a list, you can use the average function.

Starting code and unit tests can be found in the kotlin-exercises project on GitHub in the file functional/collections/StudentGradesIntenship.kt. You can clone this project and solve this exercise locally.

I decided I wouldn't feel good showing you the solution. It can be found at the end of the book Functional Kotlin, and in the course Functional Kotlin, but here it would be just too easy to see it and copy it. I want you to try to solve it yourself. If you have any questions, feel free to ask them in the comments below. You can also send a Tweet to me @marcinmoskala. I will be happy to help you. If you managed to solve this exercise, feel free to share it with others. I will be happy to see your solutions. Good luck and have fun!