diff --git a/.idea/misc.xml b/.idea/misc.xml
index bfbb0ff..e0844bc 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/res/sample.grades b/res/sample.grades
index a70e9e4..0f67dc8 100644
--- a/res/sample.grades
+++ b/res/sample.grades
@@ -24,7 +24,7 @@ programming languages: 89
differential equations: 59
differential equations: 97
software architecture: 56
-parallel and distributed computing: 89
+parallel and distributed computing:
parallel and distributed computing: 66
differential equations: 58
software architecture: 98
diff --git a/src/three_feature.kt b/src/three_feature.kt
index c56850f..aa41724 100644
--- a/src/three_feature.kt
+++ b/src/three_feature.kt
@@ -1,51 +1,64 @@
import java.io.File
+class ReportCard {
+ // Maps and Lists are, by default, immutable.
+ // Specify our data structure to be mutable at both levels
+ private val courses = mutableMapOf>()
+
+ // use getOrPut to get a list of course grades, and add the new grade to that list
+ fun addAssignment(course: String, grade: Int) = courses.getOrPut(course) { mutableListOf() }.add(grade)
+
+ fun grades(func: (String, String, Double) -> Unit) {
+ // Iteration over maps is easy when combined with iterable unpacking
+ for ((course, grades) in courses) {
+ val average = grades.average() // List has helper methods such as `sum()` and `average()`
+
+ // use a `when` block as an alternative to case. Supports rich pattern matching,
+ // but we just use it here to check which grade range we're in
+ val letter = when {
+ average > 90 -> "A"
+ average > 80 -> "B"
+ average > 70 -> "C"
+ average > 60 -> "D"
+ else -> "F"
+ }
+
+ // call back to the block given
+ func(course, letter, average)
+ }
+ }
+}
+
+// Create an extension method on `Double` that allows us to specify precision
+fun Double.format(digits: Int) = java.lang.String.format("%.${digits}f", this)
+
fun main(args: Array) {
// File I/O is easily done with `File()` constructor
val file = File("res/sample.grades")
- // Maps and Lists are, by default, immutable.
- // Specify our data structure to be mutable at both levels
- val courses = mutableMapOf>()
+ val report = ReportCard()
- // File supports line iteration with forEachLine, which accepts a code block.
+ // `File` supports line iteration with `forEachLine`, which accepts a code block.
// `it` is the iteration variable in the code block
file.forEachLine {
// It is possible to unpack iterables into multiple variables
// This is very brittle, though, if improperly formatted
- val (rawCourse, rawGrade) = it.split(":")
+ val (rawCourse, rawGrade) = it.split(":").let {
+ Pair(it[0], it.getOrElse(1) { _ -> "" })
+ }
// use `trim()` to strip whitespace from either side of the strings
// string conversion is easy with helper methods like `toInt()` and `toIntOrNull()`
val course = rawCourse.trim()
- val grade = rawGrade.trim().toInt()
-
- // collection testing is easy with `in` and `!in` operators
- if (course !in courses)
- courses[course] = mutableListOf() // implicit type arguments are passed: `mutableListOf()`
-
- // use `!!` to assert the hash lookup will not be null. We just added it to the map.
- courses[course]!!.add(grade)
+ val grade = rawGrade.trim().toIntOrNull()
+ grade?.let { report.addAssignment(course, it) }
}
-
- // Iteration over maps is easy when combined with iterable unpacking
- for ((course, grades) in courses) {
- val average = grades.average() // List has helper methods such as `sum()` and `average()`
-
- // use a `when` block as an alternative to case. Supports rich pattern matching,
- // but we just use it here to check which grade range we're in
- val letter = when {
- average > 90 -> "A"
- average > 80 -> "B"
- average > 70 -> "C"
- average > 70 -> "D"
- else -> "F"
- }
-
- // input and output from console are as easy as `println()` and `readline()`
- // uses string interpolation for easier formatting
- println("$course:\n\t$letter ($average)\n")
+ // input and output from console are as easy as `println()` and `readline()`
+ // uses string interpolation for easier formatting
+ // grade precision is specified with the `format` extension method defined above
+ report.grades { course: String, letter: String, average: Double ->
+ println("$course:\n\t$letter (${average.format(2)})\n")
}
}
\ No newline at end of file