通用转置(或任何其他真的!)在Kotlin

cx6n0qe3  于 2023-06-24  发布在  Kotlin
关注(0)|答案(2)|浏览(129)

在处理Advent of Code难题时,我发现自己定义了一个函数来转置整数矩阵:

fun transpose(xs: Array<Array<Int>>): Array<Array<Int>> {
    val cols = xs[0].size // 3
    val rows = xs.size // 2
    var ys = Array(cols) { Array(rows) { 0 } }
    for (i in 0..rows - 1) {
        for (j in 0..cols - 1)
            ys[j][i] = xs[i][j]
    }
    return ys
}

在下面的谜题中,我也需要转置一个矩阵,但它不是Int s的矩阵,所以我试着推广。在Haskell中我会有一些

transpose :: [[a]] -> [[a]]

为了在Kotlin中复制它,我尝试了以下方法:

fun transpose(xs: Array<Array<Any>>): Array<Array<Any>> {
    val cols = xs[0].size
    val rows = xs.size
    var ys = Array(cols) { Array(rows) { Any() } } // maybe this is the problem?
    for (i in 0..rows - 1) {
        for (j in 0..cols - 1)
            ys[j][i] = xs[i][j]
    }
    return ys
}

这看起来很好,但事实并非如此。事实上,当我尝试在原始整数矩阵上调用它时,我得到Type mismatch: inferred type is Array<Array<Int>> but Array<Array<Any>> was expected。问题是,我真的不明白这个错误消息:我以为Any是其他任何东西的超类型?
在谷歌上搜索了一下,我想我应该使用某种类型约束语法(对不起,不确定它在Kotlin中的调用方式),因此将类型更改为fun <T: Any> transpose(xs: Array<Array<T>>): Array<Array<T>>,但在返回行中我得到了Type mismatch: inferred type is Array<Array<Any>> but Array<Array<T>> was expected
所以我的问题是,我如何写一个transpose矩阵,它适用于任何二维数组?

bkhjykvo

bkhjykvo1#

正如你自己指出的,Array(cols) { Array(rows) { Any() } }行创建了一个Array<Array<Any>>,所以如果你在泛型函数中使用它,当Array<Array<T>>被期望时,你将无法返回它。
相反,你应该使用这个lambda直接为正确的索引提供正确的值(而不是初始化为任意值并替换所有值):

inline fun <reified T> transpose(xs: Array<Array<T>>): Array<Array<T>> {
    val cols = xs[0].size
    val rows = xs.size
    return Array(cols) { j ->
        Array(rows) { i -> 
            xs[i][j]
        }
    }
}

我真的不明白这个错误消息:我以为Any是其他任何东西的超类型?
这是因为Kotlin中的数组在其元素类型上是不变的。如果你不知道泛型变异,那么它是关于描述泛型类型的层次结构与它们的类型参数的层次结构的比较。
例如,假设您有一个类型Foo<T>。现在,IntAny的子类型这一事实并不一定意味着Foo<Int>Foo<Any>的子类型。您可以查找术语,但基本上您有3种可能性:

  • 如果Foo<Int>Foo<Any>的 * 子类型 *(Foo的类型与T的“变化相同”),我们说Foo在其类型参数T中是 * 协变 *。
  • 如果Foo<Int>Foo<Any>的 * 超类型 *,我们说Foo在其类型参数T中是 * 逆变的 *(与T相比,Foo类型“以相反的方式变化”)
  • 如果以上都不能说,我们说Foo在其类型参数T中是 * 不变的 *

Kotlin中的数组是不变的。然而,Kotlin的只读List在其元素的类型上是协变的。这就是为什么在Kotlin中可以将List<Int>分配给List<Any>类型的变量。

mtb9vblg

mtb9vblg2#

另一种方法是向List或Array添加一个通用扩展方法,以便可以在示例上调用.transpose()方向。

/**
 * Given a list of lists (ie a matrix), transpose it
 */
fun <T>List<List<T>>.transpose(): List<List<T>> {
    return (this[0].indices).map { i -> (this.indices).map { j -> this[j][i] } }
}

/**
 * Given an array of arrays (ie a matrix), transpose it
 */
inline fun <reified T>Array<Array<T>>.transpose(): Array<Array<T>> {
    return Array(this[0].size) { i -> Array(this.size) { j -> this[j][i] } }
}

你可以这样调用

val rows = listOf(listOf(1, 2, 3), listOf(4, 5, 6), listOf(7, 8, 9))
val columns = rows.transpose()

希望这有帮助!

相关问题