请考虑以下两个类:
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标准库中是否有可以涵盖这种用例的东西?
答案 0 :(得分:2)
通过将所有嵌套元素转换为平坦的Sequence
,可以延迟迭代它们,并消除了不必要的迭代开销。通过结合asSequence
和flatMap
完成这个技巧:
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
}
我还喜欢使用map
和first
,但是使用这些扩展功能很难有效地完成给定任务。
答案 3 :(得分:0)
一个简单的flatMap
就能解决问题:
listOfA.flatMap { it.objectBs }.first { it.key == wantedKey }
基本上,这将为您提供一个中间列表,将它们全部组合在一起,以便您可以轻松查询第一个匹配的列表。
答案 4 :(得分:-1)
如果性能很关键,我会查看coroutines或sequences。
您也可以通过在listOfA上使用firstOrNull来稍微优化代码:
listOfA.filterNotNull().firstOrNull { item ->
item.objectBs.firstOrNull { it.key == wantedKey } != null
}
我将进行一些性能测试,以查看该代码是否引起任何问题,然后再使其变得过于复杂。