Kotlin 中的通用转置(或其他任何东西!)

har*_*ont 3 generics polymorphism kotlin

在研究“代码出现”难题时,我发现自己定义了一个函数来转置整数矩阵:

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
}
Run Code Online (Sandbox Code Playgroud)

事实证明,在下面的谜题中,我还需要转置一个矩阵,但它不是Ints 的矩阵,所以我尝试概括。在 Haskell 中我会有某种类型的东西

transpose :: [[a]] -> [[a]]
Run Code Online (Sandbox Code Playgroud)

为了在 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
}
Run Code Online (Sandbox Code Playgroud)

这看起来没问题,但事实并非如此。事实上,当我尝试在原始整数矩阵上调用它时,我得到了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适用于任何二维数组的矩阵?

Jof*_*rey 5

正如您自己指出的,该行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]
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我真的不明白这个错误消息:我认为 Any 是其他任何东西的超类型?

这是因为Kotlin 中的数组的元素类型是不变的。如果您不了解泛型方差,那么它是关于描述泛型类型的层次结构与其类型参数的层次结构进行比较的方式。

例如,假设您有一个类型Foo<T>Int现在,是 的子类型这一事实Any并不一定意味着它Foo<Int>是 的子类型Foo<Any>。你可以查一下行话,但本质上你有 3 种可能性:

  • 如果是(类型“以相同方式变化” )的子类型,我们就说它的类型参数Foo协变的TFoo<Int>Foo<Any>FooT
  • 我们说,如果Foo是(与 相比,类型“以相反方式变化” )的类型,则类型参数是逆变的TFoo<Int>Foo<Any>FooT
  • 如果以上都不能说,我们就说它的类型参数Foo不变的T

Kotlin 中的数组是不变的。List然而,Kotlin 的 read-only元素的类型是协变的。这就是为什么可以将 a 分配List<Int>给 Kotlin 中的类型变量List<Any>