Find and return first match in nested lists in Kotlin? - kotlin

Consider the following two classes:
class ObjectA(val objectBs: List<ObjectB>,
val otherFields: Any)
class ObjectB(val key: String,
val otherFields: Any)
The task is to find and return the first ObjectB with a certain key in a List of ObjectA.
Just achieving the goal is simple enough, but doing it nicely and efficiently seems rather tricky. I can't find anything like a "firstIn" or "findIn" function that would allow me to return another type than ObjectA when iterating on a list of ObjectA.
I have a few approaches, one of which looks pretty nice, but is very inefficient:
listOfA.mapNotNull {
it.objectBs.firstOrNull {
item -> item.key == wantedKey
}
}.firstOrNull()
The obvious inefficiency of this code is that it will not stop iterating through listOfA when it has found a match (and there can only be one match, just to be clear).
Approaches using filter or find have similar problems, requiring redundant iterations through at least one list of ObjectB.
Is there something in kotlins standard library that would cover such a use case?

If you want an elegant solution you can just do a flatMap like this:
val result: ObjectB? = listOfA.flatMap { it.objectBs }.firstOrNull { it.key == "myKey" }
If you want the efficiency you can do something like this:
val result: ObjectB? = objectAs.firstOrNull {
it.objectBs.map(ObjectB::key).contains("myKey")
}?.objectBs?.firstOrNull { it.key == "myKey" }
You can also wrap these in an Optional and put it in a function so the users of this operation can have a clean API:
fun List<ObjectA>.findFirstObjectB(key: String): Optional<ObjectB> {
return Optional.ofNullable(firstOrNull {
it.objectBs.map(ObjectB::key).contains(key)
}?.objectBs?.firstOrNull { it.key == key })
}

By converting all the nested elements to a flattened Sequence, they can be iterated lazily, and the overhead of unnecessary iteration is eliminated. This trick is done by combining asSequence and flatMap:
listOfA.asSequence().flatMap { it.objectBs.asSequence() }.find { it.key == wantedKey }
I wrote and ran the following code to ensure that it works as expected:
class PrintSequenceDelegate<out T>(private val wrappedSequence: Sequence<T>) : Sequence<T> by wrappedSequence {
override fun iterator(): Iterator<T> {
val wrappedIterator = wrappedSequence.iterator()
return object : Iterator<T> by wrappedIterator {
override fun next(): T =
wrappedIterator.next().also { println("Retrieving: $it") }
}
}
}
fun <T> Sequence<T>.toPrintDelegate() = PrintSequenceDelegate(this)
fun main() {
val listOfLists = List(3) { i -> List(3) { j -> "$i$j" } }
println("List of lists: $listOfLists")
val found = listOfLists.asSequence().toPrintDelegate().flatMap { it.asSequence().toPrintDelegate() }.find { it == "11" }
println(if (found != null) "Found: $found" else "Not found")
}
Output:
List of lists: [[00, 01, 02], [10, 11, 12], [20, 21, 22]]
Retrieving: [00, 01, 02]
Retrieving: 00
Retrieving: 01
Retrieving: 02
Retrieving: [10, 11, 12]
Retrieving: 10
Retrieving: 11
Found: 11
Thus we see that the elements (12) after the element found in the containing nested list are not iterated, neither are the following nested lists ([20, 21, 22]).

Nothing fancy, but it does the job efficiently:
fun findBWithKey(listOfA: List<ObjectA>, wantedKey: String): ObjectB? {
listOfA.forEach {
it.objectBs.forEach { item ->
if(item.key == wantedKey){
return item
}
}
}
return null
}
I also like to use map and first, but doing the given task efficiently gets unecessary hard using those extension functions.

A simple flatMap does the trick:
listOfA.flatMap { it.objectBs }.first { it.key == wantedKey }
This will basically give you an intermediate List with all of them combined so that you can easily query the first matching one.

I would look in to coroutines or sequences if performance is critical.
You can optimize your code slightly by using firstOrNull on listOfA as well:
listOfA.filterNotNull().firstOrNull { item ->
item.objectBs.firstOrNull { it.key == wantedKey } != null
}
I would do some performance testing to see if this code is causing any issues before making it overly complex.

Related

In Kotlin, how can I test and use a value without computing it twice?

Every so often, I find myself wanting to compute a value for some sort of filter operation, but then wanting to use that value when it's already disappeared into the condition-checking thing.
For instance:
val found = list.firstOrNull { slowConversion(it).isWanted() }
if (found != null) {
something(found, slowConversion(found))
}
or
when {
other_conditions -> other_actions
list.any { it.contains(regex1) } -> something(list.firstOrNull { it.contains(regex1) } ?: "!!??")
}
For the slowConversion() I can work with a sequence mapped to pairs, although the terms first and second kinda confuse things a bit...
val pair = list.asSequence().map { it to slowConversion(it) }.firstOrNull { it.second.isWanted() }
if ( pair != null ) {
something(pair.first, pair.second)
}
or if I only want the conversion,
val converted = list.firstNotNullOfOrNull { slowConversion(it).takeIf { it.isWanted() } }
but the best I can come up with to avoid the when duplication involves moving the action part into the condition part!
fun case(s: List<String>, r: Regex) {
val match = s.firstOrNull { it.contains(r) }?.also { something(it) }
return match != null
}
when {
other_conditions -> other_actions
case(list, regex1) -> true
}
At this point, it seems I should just have a stack of function calls linked together with ||
other_things || case(list, regex1) || case(list, regex2) || catchAll(list)
Is there something better or more concise for either of these?
You can write your first example like this:
for(element in list) {
val result = slowConversion(element)
if(result.isWanted()) {
something(element, result)
break
}
}
This might not look very Kotlin-ish, but I think it's pretty straightforward & easy to understand.
For your second example, you can use the find function:
when {
other_conditions -> other_actions
else -> list.find { it.contains(regex1) }?.let(::something)
}
If you have multiple regexes, just iterate over them,
val regexes = listOf(regex1, regex2, ...)
for(regex in regexes) {
val element = list.find { it.contains(regex1) } ?: continue
something(element)
break
}

Functional way of checking if a list of list has duplicate elements

I have a list of list of elements, and would like to check if there are any duplicates. I would also like to break early - I don't care what the duplicates are, nor if there are many of them, I just want to know if there is at least one.
An imperative way which fits the bill would be:
fun main() {
println(hasDuplicates(listOf(
listOf("1", "2", "3"),
listOf("4", "5"),
listOf("1", "2")
)))
}
fun hasDuplicates(input: List<List<String>>): Boolean {
val seen = mutableSetOf<String>()
input.forEach { inner ->
inner.forEach { element ->
if (!seen.add(element)) {
return true
}
}
}
return false
}
Another way, without explicit iteration, would be:
fun hasDuplicates(input: List<List<String>>): Boolean {
val flat = input.flatten()
return flat.size != flat.toSet().size
}
but this iterates the whole list, and even creates a flattened intermediary in the first step.
I have an idea, but don't know how to implement it: suppose I could map each (flattened) list element to the number of times it has already be seen. I have this so far:
fun hasDuplicates(input: List<List<String>>): Boolean {
return input.asSequence().flatten()
// .onEach {
// println("getting $it")
// }
.groupingBy { it }
.eachCount()
.any { (_, count) -> count > 1 }
}
It does what it should but it first iterates the whole list (uncomment the onEach intermediary to see) to collect the groups. The idea would incrementally emit the element and its count, like (for input list ["1", "2", "1"]:
// (element, seenCount)
("1", 0)
("2", 0)
("1", 1)
at which point I could simply check for seenCount > 0 and return early.
Any help? Any other ideas are also welcome.
UPDATE: Got this, not really the initial idea, but seems to work:
fun hasDuplicates(input: List<List<String>>): Boolean {
input.asSequence().flatten()
.onEach {
println("getting $it")
}
.fold(mutableSetOf<String>()) { seen, element ->
if (!seen.add(element)) {
return true
}
seen
}
return false
}
The above code performs slightly worse than the very first version with loops in the worst cade (no duplicates), pretty much the same in the best case (second element is the duplicate) and in the 'medium' case (middle element of the flattened list is a duplicate).
The idea described in the question can be implemented the following way:
fun hasDuplicates(input: List<List<String>>): Boolean {
input.asSequence().flatten()
// .onEach {
// println("getting $it")
// }
.groupingBy { it }
.aggregate { _, _: Int?, _, first ->
if (first) {
1
} else {
return true
}
}
return false
}
The type of the accumulator (Int? above) doesn't matter as it is unused.
But, the following solution is even better for me, as it allows me to return the set in the case that all is unique, which I need later:
fun uniqueOrNull(input: List<List<String>>): Set<String>? {
return input.asSequence().flatten()
.fold(mutableSetOf()) { seen, element ->
if (!seen.add(element)) {
return null
}
seen
}
}
Using aggregate would also work, but performs negligibly worse and is more complicated to the reader:
fun uniqueOrNull(input: List<List<String>>): Set<String>? {
return input.asSequence().flatten()
.groupingBy { it }
.aggregate { _, _: Int?, _, first ->
if (first) {
1
} else {
return null
}
}.keys
}

Kotlin: remove identical adjacent members of an array

If I have the following array:
[1,1,1,2,2,1,1,1,1,2,2,3]
Is there any built in method in Kotlin which will filter out adjacent elements of the same value, resulting in:
[1,2,1,2,3]
It's important that the order is preserved.
P.S. My actual use case isn't integers, it's an object which implements equals.
I don't think there is a standard function to do this.
But it is easy to build one with mapOrNull:
fun <T : Any> Iterable<T>.removeAdjacent(): List<T> {
var last: T? = null
return mapNotNull {
if (it == last) {
null
} else {
last = it
it
}
}
}
There's a one-line solution, using zipWithNext():
list.zipWithNext().filter{ it.first != it.second }.map{ it.first } + list.last()
That creates a list of pairs of adjacent elements; we then filter out the identical pairs, and take the first of each remaining pair.  That will have omitted the last one, so we have to add that in separately.
That works with any element type, using the object's own notion of equality (via its equals() method); this includes nullable types (unlike another answer).  And it's stateless so ‘pure’ functional (which you may or may not consider a good thing!).
It handles one-element lists, but not empty lists; for completeness, you'd have to handle those separately.  And it would fit very neatly into an extension function:
fun <T> List<T>.compress() = when (isEmpty()) {
true -> listOf()
else -> zipWithNext().filter{ it.first != it.second }.map{ it.first } + last()
}
Functional solution using fold:
val result = listOf(1,1,1,2,2,1,1,1,1,2,2,3)
.fold(mutableListOf<Int>()) { currentList, currentItem ->
if (currentList.isEmpty()) { // Applies only to the very first item
mutableListOf(currentItem)
} else {
if (currentItem != currentList.last()) {
currentList.apply { add(currentItem) }
} else {
currentList
}
}
}

RxJava Filter on Error

This question is loosely related to this question, but there were no answers. The answer from Bob Dalgleish is close, but doesn't support the potential error coming from a Single (which I think that OP actually wanted as well).
I'm basically looking for a way to "filter on error" - but don't think this exists when the lookup is RX based. I am trying to take a list of values, run them through a lookup, and skip any result that returns a lookup failure (throwable). I'm having trouble figuring out how to accomplish this in a reactive fashion.
I've tried various forms of error handling operators combined with mapping. Filter only works for raw values - or at least I couldn't figure out how to use it to support what I'd like to do.
In my use case, I iterate a list of IDs, requesting data for each from a remote service. If the service returns 404, then the item doesn't exist anymore. I should remove non-existing items from the local database and continue processing IDs. The stream should return the list of looked up values.
Here is a loose example. How do I write getStream() so that canFilterOnError passes?
import io.reactivex.Single
import io.reactivex.schedulers.Schedulers
import org.junit.Test
class SkipExceptionTest {
private val data: Map<Int, String> = mapOf(
Pair(1, "one"),
Pair(2, "two"),
Pair(4, "four"),
Pair(5, "five")
)
#Test
fun canFilterOnError() {
getStream(listOf(1, 2, 3, 4, 5))
.subscribeOn(Schedulers.trampoline())
.observeOn(Schedulers.trampoline())
.test()
.assertComplete()
.assertNoErrors()
.assertValueCount(1)
.assertValue {
it == listOf(
"one", "two", "four", "five"
)
}
}
fun getStream(list: List<Int>): Single<List<String>> {
// for each item in the list
// get it's value via getValue()
// if a call to getValue() results in a NotFoundException, skip that value and continue
// mutate the results using mutate()
TODO("not implemented")
}
fun getValue(id: Int): Single<String> {
return Single.fromCallable {
val value: String? = data[id]
if (value != null) {
data[id]
} else {
throw NotFoundException("dat with id $id does not exist")
}
}
}
class NotFoundException(message: String) : Exception(message)
}
First .materialize(), then .filter() on non-error events, then .dematerialize():
getStream(/* ... */)
.materialize()
.filter(notification -> { return !notification.isOnError(); })
.dematerialize()
I ended up mapping getValue() to Optional<String>, then calling onErrorResumeNext() on that and either returning Single.error() or Single.just(Optional.empty()). From there, the main stream could filter out the empty Optional.
private fun getStream(list: List<Int>): Single<List<String>> {
return Observable.fromIterable(list)
.flatMapSingle {
getValue(it)
.map {
Optional.of(it)
}
.onErrorResumeNext {
when (it) {
is NotFoundException -> Single.just(Optional.empty())
else -> Single.error(it)
}
}
}
.filter { it.isPresent }
.map { it.get() }
.toList()
}

How to use Kotlin fold function to convert an array into a map?

I am trying to convert an Array via fold into an indexed Map. Somehow IntelliJ flags that when I return the accumulator that it expects Unit. When I remove the return it complains that I require the datatype I originally wanted to return.
The code is as follows (Item is just a data class)
constructor(vararg items: Item){
val itemMap = items.fold(mutableMapOf<Int, MutableList<Item>>(), { acc, item ->
if (acc.containsKey(item.state)) {
acc[item.state]?.add(item)
} else {
acc.put(item.state, mutableListOf(item))
}
return acc
})
}
Its a bit late here so I probably miss something very obvious. Any help would be very appreciated.
Thanks
Use the qualified return#fold operator instead of return. In Kotlin, return without a qualifier means 'return from the innermost fun (ignoring lambdas)'.
val itemMap = items.fold(mutableMapOf<Int, MutableList<Item>>(), { acc, item ->
if (acc.containsKey(item.state)) {
acc[item.state]?.add(item)
} else {
acc.put(item.state, mutableListOf(item))
}
return#fold acc
})
See Whats does “return#” mean?, Return at Labels in the language reference.
Or just use the result expression, omitting return:
val itemMap = items.fold(mutableMapOf<Int, MutableList<Item>>(), { acc, item ->
if (acc.containsKey(item.state)) {
acc[item.state]?.add(item)
} else {
acc.put(item.state, mutableListOf(item))
}
acc
})
Basically, this kind of fold is implemented in the standard library: see .groupBy { ... }.