Home > Mobile >  Log-statements and collection-functions (map, filter)
Log-statements and collection-functions (map, filter)

Time:09-08

I have code that combines and deduplicates two arrays. When duplicates are found, it drops the corresponding items of the second array.

fun combineAndDedupe(fromMainSupplier: List<Book>, fromSecondarySupplier: List<Book>): List<Book> {
    val isbnsFromMain = fromMainSupplier.map { it.isbn }
    return fromMainSupplier   fromSecondarySupplier.filter { it.isbn !in isbnsFromMain }
}

I have been asked write a log-statement if any books have been filtered out, listing all affected ISBN's. I am struggling to implement this in a way that feels "Kotliny". It's too verbose, too long, too hard to read. Is there a more elegant way? I often encounter this problem where the original code is reasonably elegant, but once you add a log-statement, you are writing Java 7 again.

One of my more verbose attempts:

private fun combineAndDedupe(fromMainSupplier: List<Book>, fromSecondarySupplier: List<Book>): List<Book> {
    val isbnsFromMain = fromMainSupplier.map { it.isbn }
    val isbnsFromSecondary = fromSecondarySupplier.map { it.isbn }
    val isbnsToRemove = isbnsFromSecondary.filter { it in isbnsFromMain }
    if (isbnsToRemove.isNotEmpty()) {
        val isbnsString = isbnsToRemove.joinToString(",")
        logger.info(
            "Removing books from secondary supplier that are already present in main supplier: ${isbnsString}"
        )
    }
    return fromMainSupplier   fromSecondarySupplier.filter { it.isbn !in isbnsToRemove }
}

CodePudding user response:

This is easier with the intersect function.

private fun combineAndDedupe(fromMainSupplier: List<Book>, fromSecondarySupplier: List<Book>): List<Book> {
    val intersection = fromMainSupplier.intersect(fromSecondarySupplier)
    if (intersection.isNotEmpty()) {
        logger.info(
            "Removing books from secondary supplier that are already present in main supplier: ${intersection.map(Book::isbn).joinToString()}"
        )
    }
    return fromMainSupplier   (fromSecondarySupplier - intersection)
}

The above assumes that Books are equal if their ISBNs are equal. If that's not true, you must extract the ISBN. Here's one way to do it (that assumes the input lists do not have duplicate ISBNs in them:

private fun combineAndDedupe(fromMainSupplier: List<Book>, fromSecondarySupplier: List<Book>): List<Book> {
    val intersection = fromMainSupplier.map(Book::isbn)
        .intersect(fromSecondarySupplier.map(Book::isbn).toSet())
    if (intersection.isNotEmpty()) {
        logger.info(
            "Removing books from secondary supplier that are already present in main supplier: ${intersection.joinToString()}"
        )
    }
    return (fromMainSupplier   fromSecondarySupplier ).distinctBy(Book::isbn)
}

Some describe "Kotlinly" as being as close to functional programming as possible, but that's not true. Functional programming is just an option in Kotlin. I would say "Kotlinly" just means using the tools it gives you to write readable code where it's easy to follow the logic.

CodePudding user response:

Thanks to @Tenfour04's solutions, I was able to rework my approach into:

fun combineAndDedupe(fromMainSupplier: List<Book>, fromSecondarySupplier: List<Book>): List<Book> {
    val isbnsFromMain = fromMainSupplier.map { it.isbn }
    val booksToRemove = fromSecondarySupplier.filter { it.isbn in isbnsFromMain }
    if (booksToRemove.isNotEmpty()) {
        val isbnsString = booksToRemove.joinToString(",") { it.isbn }
        logger.info(
            "Removing books from secondary supplier that are already present in main supplier: $isbnsString"
        )
    }
    return fromMainSupplier   (fromSecondarySupplier - booksToRemove.toSet())
}

The .toSet() in the last line is not strictly necessary, but it was suggested by my linter.

  • Related