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中正确的做法是什么?
Kotlin 1.0中没有内置的模拟函数。将其添加到stdlib可能是一个好主意。请在YouTrack上提交问题,随时欢迎。
以下是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
函数无论如何都会将其转换为列表。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())
}
这在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. |