在 Kotlin 的特定字段上连接两个相同类型的列表(如 SQL 中的内部连接)

Join two lists with same type on a specific field in Kotlin (like inner join in SQL)

我能找到的最接近的东西是 zip,它几乎可以满足我的要求,但它使用索引。我想指定一个字段,如果两个列表的值相同,则在该字段上加入。

在 SQL 中,可以使用“table1 INNER JOIN table2 WHERE table1.field = table2.field”。 Kotlin 中有类似的东西吗?

也许这个例子更清楚:

class Something(val id : Int, val value : Int)

val list1 = listOf(Something(0, 1), Something(1, 6), Something(2, 8))
val list2 = listOf(Something(1, 2), Something(5, 3), Something(9, 6))

val result = list1.innerJoin(list2, on=id).map { element1, element2 -> element1.value * element2.value}
//should return [12] (6*2)

list1 和 list2 都有一个 id=1 的元素,所以在这个例子中它们的值(6 和 2)相乘,结果应该是 12。

目前我使用这个代码片段,它有效,但我想知道是否有更简单、更有效的方法来做到这一点。

val result = list1.map { element1 ->
    val element2 = list2.find { element2 -> element2.id == element1.id } ?: return@map null
    element1.value * element2.value
}.filterNotNull()

谢谢。

您可以使用 mapNotNull 来消除二次过滤步骤,但这已经是您所能做到的最简洁的了。您还可以先将 list2 转换为 Map 以将其从 O(n^2) 更改为 O(n).

val list2ById = list2.associateBy(Something::id)
val result = list1.mapNotNull { element1 ->
    list2ById[element1.id]?.value?.times(element1.value)
}

Kotlin 没有为此提供 stdlib 方法,但您可以定义自己的方法:

fun <T> Collection<T>.innerJoin(other: Collection<T>, on: T.() -> Any): Collection<Pair<T, T>> {
    val otherByKey = other.associateBy(on)
    return this.mapNotNull {
        val otherMapped = otherByKey[on(it)]
        if (otherMapped == null) null else it to otherMapped
    }
}

用法:

fun main() {
    val list1 = listOf(Something(0, 1), Something(1, 6), Something(2, 8))
    val list2 = listOf(Something(1, 2), Something(5, 3), Something(9, 6))
    val result = list1.innerJoin(list2, on = { id }).map { (element1, element2) -> element1.value * element2.value }
    println(result) //12
}