private class Photo(
val name: String,
val city: String,
val time: String
) {
companion object {
fun createFromFormat(format: String): Photo {
val fields = format.split(", ")
return Photo(
name = fields[0],
city = fields[1],
time = fields[2]
)
}
}
override fun toString() = "$name, $city, $time"
fun extension() = name.substringAfterLast(".")
}
private fun getNewNames(photoInfoList: List<String>): List<String> {
val oldToNewNamesMap = photoInfoList
.map { Photo.createFromFormat(it) }
.groupBy { it.city }
.mapValues { (_, cityPhotos) -> cityPhotos.sortedByTime() }
.map { (city, cityPhotos) -> cityPhotos.mapToNewNames(city) }.flatten().toMap()
return photoInfoList.map {
oldToNewNamesMap[it] ?: error("New name not found!")
}
}
private fun List<Photo>.sortedByTime(): List<Photo> {
return sortedBy {
SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(it.time).time
}
}
private fun List<Photo>.mapToNewNames(city: String): List<Pair<String, String>> {
return mapIndexed { index, photo ->
val newName = "$city${index + 1}.${photo.extension()}"
photo.toString() to newName
}
}
fun main() {
println(
getNewNames(
listOf(
"FS.jpg, Udaipur, 2019-09-05 14:08:15",
"IG.png, Delhi, 2021-06-20 15:13:22",
"SKB.png, Udaipur, 2019-09-05 14:07:13",
"TH.jpg, Mumbai, 2021-07-23 08:03:02",
"GOI.jpg, Mumbai, 2021-07-22 23:59:59"
)
)
)
}
Note :
- The use of Kotlin STL functions like
map
, groupBy
, mapValues
, user defined function like Photo.createFromFormat
and extension functions like List<Photo>.sortedByTime
, List<Photo>.mapToNewNames
make the program declarative where focus is on What to do rather than How to do.
- The above program leverages composition of functions.
photoInfoList: List<String>
→ map → List<Photo>
→ groupBy → Map<String, List<Photo>>
→ mapValues → Map<String, List<Photo>>
→ map → List<List<Pair<String, String>>>
→ flatten → List<Pair<String, String>>
→ toMap → Map<String, String>
- Kotlin STL functions like
map
, groupBy
, mapValues
are Higher order functions that take lambda function as input parameter. Hence, it is an example of Functional programming.
In Functional Programming / Declarative approach, a large program is often broken down into several smaller functions. Hence, making the program easier to debug & maintain. Also, tweaks & addition of new funtionalities is also easier as compared to Imperative approach.
Following is a comparison between the two paradigms :
|
Imperative |
Declarative / Functional |
Focus |
How to do |
What to do |
Building blocks |
Loops |
STL functions like map, filter, associate, groupBy, reduce etc. |
Code Readability |
Poor |
Better |
Maintenance |
Tough |
Easy |
Easeness to debug |
Poor |
Better |