Home > OS >  Kotlin is filtering list before getting max value best apporach?
Kotlin is filtering list before getting max value best apporach?

Time:06-03

If you have a list of objects object(category, value) and want to get the max value excluding some categories you can use something like this:

val max = objects.filter { it.category.name != xy }.maxByOrNull { it.value }

But this uses 2 iterators if I understand it correctly, so would there be a more performant version of this call using only one iterator?

CodePudding user response:

That's correct. This code will first iterate over the whole list to filter the result, and then again to find the max.

I'll detail some alternatives below, but, overall, I would recommend against any of them without a very good justification. The performance benefits are usually marginal - spending time making sure the code is clear and well tested will be a better investment. I'd recommend sticking to your existing code.

Example

Here's an executable version of your code.

fun main() {

  val items = listOf(
    MyData("shoes", 1),
    MyData("shoes", 22),
    MyData("shoes", 33),
    MyData("car", 555),
  )

  val max = items
    .filter {
      println("  filter $it")
      it.categoryName == "shoes"
    }.maxByOrNull {
      println("  maxByOrNull $it")
      it.value
    }

  println("\nresult: $max")
}

There are two iterations, and each are run twice.

  filter MyData(categoryName=shoes, value=1)
  filter MyData(categoryName=shoes, value=22)
  filter MyData(categoryName=shoes, value=33)
  filter MyData(categoryName=car, value=555)
  maxByOrNull MyData(categoryName=shoes, value=1)
  maxByOrNull MyData(categoryName=shoes, value=22)
  maxByOrNull MyData(categoryName=shoes, value=33)

result: MyData(categoryName=shoes, value=33)

Sequences

In some situations you can use sequences to reduce the number of operations.

  val max2 = items
    .asSequence()
    .filter {
      println("  filter $it")
      it.categoryName == "shoes"
    }.maxByOrNull {
      println("  maxByOrNull $it")
      it.value
    }

  println("\nresult: $max2")

As you can see, the order of operations is different

  filter MyData(categoryName=shoes, value=1)
  filter MyData(categoryName=shoes, value=22)
  maxByOrNull MyData(categoryName=shoes, value=1)
  maxByOrNull MyData(categoryName=shoes, value=22)
  filter MyData(categoryName=shoes, value=33)
  maxByOrNull MyData(categoryName=shoes, value=33)
  filter MyData(categoryName=car, value=555)

result: MyData(categoryName=shoes, value=33)

[S]equences let you avoid building results of intermediate steps, therefore improving the performance of the whole collection processing chain.

Note that in this small example, the benefits aren't worth it.

However, the lazy nature of sequences adds some overhead which may be significant when processing smaller collections or doing simpler computations.

Combining operations

In your small example you could combine the 'filterr' and 'maxBy' operations

  val max3 = items.maxByOrNull { data ->
    when (data.categoryName) {
      "shoes" -> data.value
      "car"   -> -1
      else    -> -1
    }
  }
  println("\nresult: $max3")
result: MyData(categoryName=shoes, value=33)

I hope it's clear that this solution isn't immediately clear, and has some nasty edge cases that would be a prime source of bugs. I won't detail the issues, but instead re-iterate that ease-of-use, adaptability, and simple code is usually much more valuable than optimised code!

  • Related