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

时间:2018-10-10 12:23:35

标签: kotlin

请考虑以下两个类:

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

class ObjectB(val key: String,
              val otherFields: Any)

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

仅要达到目标就很简单,但要有效地做到这一点似乎很棘手。我找不到像“ firstIn”或“ findIn”函数之类的东西,当遍历ObjectA列表时,该函数将允许我返回ObjectA以外的其他类型。

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

listOfA.mapNotNull { 
    it.objectBs.firstOrNull { 
        item -> item.key == wantedKey
   } 
}.firstOrNull()

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

kotlins标准库中是否有可以涵盖这种用例的东西?

5 个答案:

答案 0 :(得分:2)

通过将所有嵌套元素转换为平坦的Sequence,可以延迟迭代它们,并消除了不必要的迭代开销。通过结合asSequenceflatMap完成这个技巧:

listOfA.asSequence().flatMap { it.objectBs.asSequence() }.find { it.key == wantedKey }

我编写并运行了以下代码,以确保其可以正常工作:

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")
}

输出:

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

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

答案 1 :(得分:1)

如果您想要一个优雅的解决方案,则可以像这样进行flatMap

val result: ObjectB? = listOfA.flatMap { it.objectBs }.firstOrNull { it.key == "myKey" }

如果您想提高效率,可以执行以下操作:

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

您也可以将它们包装在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 })
}

答案 2 :(得分:1)

没什么好看的,但是它可以有效地完成工作:

fun findBWithKey(listOfA: List<ObjectA>, wantedKey: String): ObjectB? {
  listOfA.forEach { 
        it.objectBs.forEach { item ->        
             if(item.key == wantedKey){
                 return item
             }
        } 
    }

    return null
}

我还喜欢使用mapfirst,但是使用这些扩展功能很难有效地完成给定任务。

答案 3 :(得分:0)

一个简单的flatMap就能解决问题:

listOfA.flatMap { it.objectBs }.first { it.key == wantedKey }

基本上,这将为您提供一个中间列表,将它们全部组合在一起,以便您可以轻松查询第一个匹配的列表。

答案 4 :(得分:-1)

如果性能很关键,我会查看coroutinessequences

您也可以通过在listOfA上使用firstOrNull来稍微优化代码:

listOfA.filterNotNull().firstOrNull { item ->
    item.objectBs.firstOrNull { it.key == wantedKey } != null
}

我将进行一些性能测试,以查看该代码是否引起任何问题,然后再使其变得过于复杂。