Can we optimize these foreach loops?

I am kind of new to Kotlin and have found that foreach was very helpful, and i use it a lot. For example, if I have two classes:

ClassA (
val itemsA: List<Class1>
) {
fun doSthA() {}
}

Class1 (
val id: String
)

ClassB (
val itemsB: List<Class2>
)

Class2 (
val user: String
)

Now i want to loop through itemsA then itemsB and call doSthA(), so i used:

classA.itemsA.foreach {
itemA -> {
classB.itemsB.foreach {
itemB -> {
if (itemB.user == itemA.id) {
classA.doSthA()
}
}
}
}
}

Is there a better way to change the above code? For example, can we use filter, any, map to maker the code look cleaner and more concise?

Thanks!

How about

(classA.itemsA intersect classB.itemsB).map { it.doSthA() }

or

(classA.itemsA intersect classB.itemsB).map(ClassA::doSthA)

? This is not quite the same because any duplicate elements in itemsA will only be processed once, because the result of intersect is a set.

Thanks for the reply. Sorry my original post didn’t describe the question correctly. I modified the question a little bit, but I am not sure if your answer can still solve the problem?

The best way would be to use a cartesian function, but as far as I know the Kotlin standard library doesn’t have it out of the box. If it did you could write this example this way:

classA.itemsA.cartesian(classB.itemsB)
    .filter { (itemA, itemB) -> itemB.user == itemA.id }
    .forEach { classA.doSthA() }
2 Likes

thank you! If cartesian is not available, there are no other ways?

classA.itemsA
    .flatMap { a -> classB.itemsB.map { a to it } }
    .filter { (a, b) -> a.user == b.id }
    .forEach { classA.doSmth() }

I think this would work, I haven’t tested it but the flatMap part should be one possible implementation of the cartesian function.

Also if you are just looking for different ways to write this you could use normal for loops instead of forEach (your example).
As a last note: The title says “optimize”. If you are looking at performance/memory your original code should be the best. All other options might perform as good but not better. If you use Sequences instead of the normal list version of flatMap, filter, etc you should be as performant but it wont be better. If you stick with lists it might be a bit worse depending on the number of items, especially if the temp list created by flatMap has to be resized multiple times – the resulting list of flatMap here might get really big if both of your original lists are big, but than sequences just skip that problem (itemsA.asSequence() and keep the rest as it is).

1 Like

Another way to think about optimising is to consider changing data types. For example, if you know you’ll do this lookup more often than others, you might want to store your itemsA in a Map<String, List<Class1>>, where the String key is the id from the Class1 instance. Then you can use that hash lookup to find the instance with an id matching a given user very quickly.

If you have multiple different lookups you could handle them with maps external to the class, possibly in some Index class and/or behind extension functions.

This is the same idea as indexes in a database: you’re using more space, but gaining speed.