Kotlin 中的通用转置(或其他任何东西!)
Generic transpose (or anything else really!) in Kotlin
在解决 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
的矩阵,所以我试着概括一下。在 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>>
,但随后在 return 行我得到 Type mismatch: inferred type is Array<Array<Any>> but Array<Array<T>> was expected
所以我的问题是,如何编写适用于任何二维数组的 transpose
矩阵?
正如您自己指出的那样,Array(cols) { Array(rows) { Any() } }
行创建了一个 Array<Array<Any>>
,因此如果您在通用函数中使用它,您将无法 return预计 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]
}
}
}
I don't really understand this error message: I thought Any was a supertype of anything else?
这是因为arrays in Kotlin are invariant in their element type。如果您不了解泛型变体,它是关于描述泛型类型的层次结构与其类型参数的层次结构的比较。
例如,假设您有一个类型 Foo<T>
。现在,Int
是 Any
的子类型这一事实并不一定意味着 Foo<Int>
是 Foo<Any>
的子类型。您可以查找行话,但基本上您在这里有 3 种可能性:
- 如果
Foo<Int>
是 子类型 [= Foo<Any>
的 58=](Foo
类型与 T
的“变化方式相同”)
- 如果
Foo<Int>
是 超类型 [= Foo<Any>
的 58=](与 T
相比,Foo
类型“以相反的方式变化”)
- 我们说
Foo
在它的类型参数T
中是不变的如果上面的none可以说
Kotlin 中的数组是不变的。然而,Kotlin 的只读 List
在其元素类型上是 协变的 。这就是为什么可以在 Kotlin 中将 List<Int>
分配给 List<Any>
类型的变量。
在解决 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
的矩阵,所以我试着概括一下。在 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>>
,但随后在 return 行我得到 Type mismatch: inferred type is Array<Array<Any>> but Array<Array<T>> was expected
所以我的问题是,如何编写适用于任何二维数组的 transpose
矩阵?
正如您自己指出的那样,Array(cols) { Array(rows) { Any() } }
行创建了一个 Array<Array<Any>>
,因此如果您在通用函数中使用它,您将无法 return预计 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]
}
}
}
I don't really understand this error message: I thought Any was a supertype of anything else?
这是因为arrays in Kotlin are invariant in their element type。如果您不了解泛型变体,它是关于描述泛型类型的层次结构与其类型参数的层次结构的比较。
例如,假设您有一个类型 Foo<T>
。现在,Int
是 Any
的子类型这一事实并不一定意味着 Foo<Int>
是 Foo<Any>
的子类型。您可以查找行话,但基本上您在这里有 3 种可能性:
- 如果
Foo<Int>
是 子类型 [=Foo<Any>
的 58=](Foo
类型与T
的“变化方式相同”) - 如果
Foo<Int>
是 超类型 [=Foo<Any>
的 58=](与T
相比,Foo
类型“以相反的方式变化”) - 我们说
Foo
在它的类型参数T
中是不变的如果上面的none可以说
Kotlin 中的数组是不变的。然而,Kotlin 的只读 List
在其元素类型上是 协变的 。这就是为什么可以在 Kotlin 中将 List<Int>
分配给 List<Any>
类型的变量。