Kirill Rakhman
Kirill Rakhman

Reputation: 43841

Create generic 2D array in Kotlin

Suppose I have a generic class and I need a 2D array of generic type T. If I try the following

class Matrix<T>(width: Int, height: Int) {
    val data: Array<Array<T>> = Array(width, arrayOfNulls<T>(height))
}

the compiler will throw an error saying "Cannot use 'T' as reified type parameter. Use a class instead.".

Upvotes: 14

Views: 8556

Answers (2)

Ross Anderson
Ross Anderson

Reputation: 470

Just because the syntax has moved on a bit, here's my take on it:

class Array2D<T> (val xSize: Int, val ySize: Int, val array: Array<Array<T>>) {

    companion object {

        inline operator fun <reified T> invoke() = Array2D(0, 0, Array(0, { emptyArray<T>() }))

        inline operator fun <reified T> invoke(xWidth: Int, yWidth: Int) =
            Array2D(xWidth, yWidth, Array(xWidth, { arrayOfNulls<T>(yWidth) }))

        inline operator fun <reified T> invoke(xWidth: Int, yWidth: Int, operator: (Int, Int) -> (T)): Array2D<T> {
            val array = Array(xWidth, {
                val x = it
                Array(yWidth, {operator(x, it)})})
            return Array2D(xWidth, yWidth, array)
        }
    }

    operator fun get(x: Int, y: Int): T {
        return array[x][y]
    }

    operator fun set(x: Int, y: Int, t: T) {
        array[x][y] = t
    }

    inline fun forEach(operation: (T) -> Unit) {
        array.forEach { it.forEach { operation.invoke(it) } }
    }

    inline fun forEachIndexed(operation: (x: Int, y: Int, T) -> Unit) {
        array.forEachIndexed { x, p -> p.forEachIndexed { y, t -> operation.invoke(x, y, t) } }
    }
}

This also allows you to create 2d arrays in a similar manner to 1d arrays, e.g. something like

val array2D = Array2D<String>(5, 5) { x, y -> "$x $y" }

and access/set contents with the indexing operator:

val xy = array2D[1, 2]

Upvotes: 11

Kirill Rakhman
Kirill Rakhman

Reputation: 43841

The problem is calling arrayOfNulls<T>(height) with the non-reified type parameter T. But we also can't make T reified, the compiler will throw the following error: "Only type parameters of inline functions can be reified"

So that's what we're going to do. Instead of the constructor we use an inlined factory method:

class Matrix<T> private(width: Int, height: Int, arrayFactory: (Int) -> Array<T>) {

    class object {
        inline fun <reified T>invoke(width: Int, height: Int)
                = Matrix(width, height, { size -> arrayOfNulls<T>(size) })
    }

    val data: Array<Array<T>> = Array(width, { size -> arrayFactory(size) })
}

Notice, the constructor is now private, so calling Matrix() will correctly call the new invoke() method (related question). Because the method is inlined, we can use reified generics which makes it possible to call arrayOfNulls<T>.

Upvotes: 9

Related Questions