在Kotlin的嵌套列表中查找并返回第一个匹配项?

Unc*_*Bob 5 kotlin

请考虑以下两类:

class ObjectA(val objectBs: List<ObjectB>,
              val otherFields: Any)

class ObjectB(val key: String,
              val otherFields: Any)
Run Code Online (Sandbox Code Playgroud)

任务是在ObjectA列表中查找并返回具有特定键的第一个ObjectB。

仅仅实现目标就足够简单了,但是很好而有效地做到这一点似乎很棘手。我找不到“ firstIn”或“ findIn”函数之类的东西,该函数使我在迭代ObjectA列表时可以返回ObjectA以外的其他类型。

我有几种方法,其中一种看起来不错,但效率很低:

listOfA.mapNotNull { 
    it.objectBs.firstOrNull { 
        item -> item.key == wantedKey
   } 
}.firstOrNull()
Run Code Online (Sandbox Code Playgroud)

该代码的明显低效之处在于,找到匹配项后,它不会停止通过listOfA进行迭代(并且只有一个匹配项,请注意)。使用过滤器或查找的方法也有类似的问题,需要遍历至少一个ObjectB列表进行冗余迭代。

kotlins标准库中是否有可以涵盖此类用例的内容?

Shr*_* Ye 10

通过将所有嵌套元素转换为扁平化Sequence,它们可以被延迟迭代,并且消除了不必要的迭代的开销。这个技巧是通过组合asSequence和来完成的flatMap

listOfA.asSequence().flatMap { it.objectBs.asSequence() }.find { it.key == wantedKey }
Run Code Online (Sandbox Code Playgroud)

我编写并运行了以下代码以确保其按预期工作:

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")
}
Run Code Online (Sandbox Code Playgroud)

输出:

listOfA.asSequence().flatMap { it.objectBs.asSequence() }.find { it.key == wantedKey }
Run Code Online (Sandbox Code Playgroud)

因此,我们看到12包含嵌套列表中找到的元素之后的元素 ( ) 不会被迭代,后面的嵌套列表 ( [20, 21, 22]) 也不会被迭代。


Ada*_*old 7

如果你想要一个优雅的解决方案,你可以这样做flatMap

val result: ObjectB? = listOfA.flatMap { it.objectBs }.firstOrNull { it.key == "myKey" }
Run Code Online (Sandbox Code Playgroud)

如果你想要效率,你可以这样做:

val result: ObjectB? = objectAs.firstOrNull {
    it.objectBs.map(ObjectB::key).contains("myKey")
}?.objectBs?.firstOrNull { it.key == "myKey" }
Run Code Online (Sandbox Code Playgroud)

您还可以将这些包装在一个 中Optional并将其放入一个函数中,以便此操作的用户可以拥有一个干净的 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 })
}
Run Code Online (Sandbox Code Playgroud)