Kotlin zipAll 替代方案

7

Kotlin 是否有类似于 Scala 中的 .zipAll 函数?

在 Scala 中,我可以使用 zipAll 函数对不同长度的两个数组进行求和。

Scala 代码示例:

val arrA = Array(1,2,3)
val arrB = Array(4, 5)
arrA.zipAll(arrB, 0, 0).map(x => x._1 + x._2)

或者在Kotlin中正确的做法是什么?
4个回答

7

Kotlin 1.0中没有内置的模拟函数。将其添加到stdlib可能是一个好主意。请在YouTrack上提交问题,随时欢迎。


非常感谢您的回答,我已经提交了问题 https://youtrack.jetbrains.com/issue/KT-13017。 - Oleg

2

以下是Kotlin的zipAll

fun <T1: Any, T2: Any> List<T1>.zipAll(other: List<T2>, emptyValue: T1, otherEmptyValue: T2): List<Pair<T1, T2>> {
    val i1 = this.iterator()
    val i2 = other.iterator()
    return generateSequence {
        if (i1.hasNext() || i2.hasNext()) {
            Pair(if (i1.hasNext()) i1.next() else emptyValue,
                    if (i2.hasNext()) i2.next() else otherEmptyValue)
        } else {
            null
        }
    }.toList()
}

还有一个单元测试:

@Test fun sumTwoUnevenLists() {
    val x = listOf(1,2,3,4,5)
    val y = listOf(10,20,30)

    assertEquals(listOf(11,22,33,4,5), x.zipAll(y, 0, 0).map { it.first + it.second })
}

同样的方法也可以应用于数组、其他集合类型、序列等。由于可以通过索引访问数组,因此仅针对数组的版本会更容易。数组版本可以是:

fun <T1: Any, T2: Any> Array<T1>.zipAll(other: Array<T2>, emptyValue: T1, otherEmptyValue: T2): List<Pair<T1, T2>> {
    val largest = this.size.coerceAtLeast(other.size)
    val result = arrayListOf<Pair<T1, T2>>()
    (0..this.size.coerceAtLeast(other.size)-1).forEach { i ->
        result.add(Pair(if (i < this.size) this[i] else emptyValue, if (i < other.size) other[i] else otherEmptyValue))
    }
    return result.filterNotNull()
}

它返回一个List,因为map函数无论如何都会将其转换为列表。

1
我为了好玩写了一个快速尾递归版本。由于列表附加,效率不是很高。
fun <T, U> List<T>.zipAll(that: List<U>, elem1: T, elem2: U): List<Pair<T, U>> {
    tailrec fun helper(first: List<T>, second: List<U>, acc: List<Pair<T, U>>): List<Pair<T, U>> {
        return when {
            first.isEmpty() && second.isEmpty() -> acc
            first.isEmpty() -> helper(first, second.drop(1), acc + listOf(elem1 to second.first()))
            second.isEmpty() -> helper(first.drop(1), second, acc + listOf(first.first() to elem2))
            else -> helper(first.drop(1), second.drop(1), acc + listOf(first.first() to second.first()))
        }
    }

    return helper(this, that, emptyList())
}

1

这在Kotlin stdlib中尚不存在,但这是我在youtrack ticket中提出的建议方法。


这是一个潜在的实现,模仿当前的zip函数https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/zip.html
/**
 * Returns a list of values built from the elements of `this` collection and the [other] collection with the same 
 * index using the provided [transform] function applied to each pair of elements. The returned list has length of 
 * the longest collection.
 */
fun <T, R, V> Iterable<T>.zipAll(
    other: Iterable<R>,
    thisDefault: T,
    otherDefault: R,
    transform: (a: T, b: R) -> V,
): List<V> {
    val first = iterator()
    val second = other.iterator()
    val list = ArrayList<V>(maxOf(collectionSizeOrDefault(10),
        other.collectionSizeOrDefault(10)))
    while (first.hasNext() || second.hasNext()) {
        val thisValue = if (first.hasNext()) first.next() else thisDefault
        val otherValue =
            if (second.hasNext()) second.next() else otherDefault
        list.add(transform(thisValue, otherValue))
    }
    return list
}

// Copying this from kotlin.collections where it is an Internal function
fun <T> Iterable<T>.collectionSizeOrDefault(default: Int): Int =
    if (this is Collection<*>) this.size else default

这是我使用它的方式

/**
 * Takes two multiline stings and combines them into a two column view.
 */
fun renderSideBySide(
    leftColumn: String,
    rightColumn: String,
    divider: String = " | ",
): String {
    val leftColumnWidth: Int = leftColumn.lines().map { it.length }.maxOrNull() ?: 0

    return leftColumn.lines()
        .zipAll(rightColumn.lines(), "", "") { left, right ->
            left.padEnd(leftColumnWidth) + divider + right
        }
        .reduce { acc, nextLine -> acc + "\n" + nextLine }
}

我正在使用的示例:

val left = """
    Left Column
    with some data
""".trimIndent()

val right = """
    Right Column
    also with some data
    but not the same length
    of data as the left colum. 
""".trimIndent()

println(left)

Left Column
with some data

println(right)

Right Column
also with some data
but not the same length
of data as the left colum. 

println(renderSideBySide(left,right))

Left Column    | Right Column
with some data | also with some data
               | but not the same length
               | of data as the left colum. 

println(renderSideBySide(right,left))

Right Column                | Left Column
also with some data         | with some data
but not the same length     | 
of data as the left colum.  | 

太酷了!非常感谢!) - Oleg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接