Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Transform a list, filtering out the items that cause an exception

How to convert this array of String:

"2018-05-08T23:22:49Z" "n/a" "2018-05-07T16:37:00Z"

to an array of Date using Higher-Order Functions such as map, flatMap or reduce?

I do know that it's possible to do that using forEach, but I'm interested to involve Kotlin Higher-Order Functions:

val stringArray
        = mutableListOf("2018-05-08T23:22:49Z", "n/a", "2018-05-07T16:37:00Z")

val dateArray = mutableListOf<Date>()

stringArray.forEach {
    try {
        val date = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
                .parse(it)
        dateArray.add(date)
    } catch (e: ParseException) {
        //* Just prevents app from crash */
    }
}
like image 448
Eugene Brusov Avatar asked Jan 24 '26 13:01

Eugene Brusov


2 Answers

Using mapNotNull

val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
val dates = listOf("2018-05-08T23:22:49Z", "n/a", "2018-05-07T16:37:00Z")
    .mapNotNull {
        try {
            format.parse(it)
        } catch (e: ParseException) {
            null
        }
    }
println(dates)

This avoids creating a list for each item in the list, it maps the bad dates to null, and mapNotNull removes the nulls from the list.

Using an extension function

You could also extract the tryOrRemove to an extension function, making the code look like this:

val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)

fun <T, U: Any> Iterable<T>.tryOrRemove(block:(T)->U): List<U> {
    return mapNotNull {
        try {
            block(it)
        } catch (ex: Throwable) {
            null
        }
    }
}

val dates = listOf("2018-05-08T23:22:49Z", "n/a", "2018-05-07T16:37:00Z")
    .tryOrRemove(format::parse)

println(dates)

Using filter

I have written it based on the only bad dates being n/a, which simplifies it.

val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)

val dates = listOf("2018-05-08T23:22:49Z", "n/a", "2018-05-07T16:37:00Z")
    .filter { it != "n/a" }
    .map(format::parse)

println(dates)
like image 142
jrtapsell Avatar answered Jan 27 '26 02:01

jrtapsell


You're looking for a transformation that can output zero or one element per input element. This is flatMap. The result of a flatmapping function must be an Iterable, so:

val dateArray = stringArray.flatMap {
    try {
        listOf(SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).parse(it))
    } catch (e: ParseException) {
        emptyList<Date>()
    }
}

Adding the following based on @pwolaq's input:

It's highly recommended to extract the SimpleDateFormat instance because it has heavyweight initialization. Further, a solution with mapNotNull is cleaner than flatMap, I wasn't aware of it. This becomes especially convenient if you add a function that I feel is missing from the Kotlin standard library:

inline fun <T> runOrNull(block: () -> T) = try {
    block()
} catch (t: Throwable) {
    null
}

With this in your toolbox you can say:

val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
val dateArray: List<Date> = stringArray.mapNotNull { 
    runOrNull { formatter.parse(it) } 
}
like image 24
Marko Topolnik Avatar answered Jan 27 '26 02:01

Marko Topolnik



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!