Возвращать двойной индекс элемента коллекции при повторении

В документации Котлина я нашел следующий пример:

for ((index, value) in array.withIndex()) { println("the element at $index is $value") } 

Возможно ли (и как) сделать аналогичное с 2D-матрицей:

 for ((i, j, value) in matrix2D.withIndex()) { // but iterate iver double index: i - row, j - column if (otherMatrix2D[i, j] > value) doSomething() } 

Как поддержать эту функциональность в классе Kotlin?

    Хотя решения, предложенные miensol и hotkey , правильны, это был бы наименее эффективный способ итерации матрицы. Например, решение « горячей клавиши» делает M * N распределениями Cell<T> плюс M распределения List<Cell<T>> и IntRange плюс одно распределение List<List<Cell<T>>> и IntRange . Кроме того, списки изменяются при добавлении новых ячеек, что приводит к еще большему количеству распределений. Это слишком много ассигнований для простой итерации матрицы.

    Итерация с использованием встроенной функции

    Я бы рекомендовал вам реализовать очень похожую и очень эффективную в то же время функцию расширения, которая будет похожа на Array<T>.forEachIndexed . Это решение не выполняет каких-либо ассигнований вообще и столь же эффективно, как запись, вложенная for циклы.

     inline fun <T> Matrix<T>.forEachIndexed(callback: (Int, Int, T) -> Unit) { for (i in 0..cols - 1) { for (j in 0..rows - 1) { callback(i, j, this[i, j]) } } } 

    Вы можете вызвать эту функцию следующим образом:

     matrix.forEachIndexed { i, j, value -> if (otherMatrix[i, j] > value) doSomething() } 

    Итерация с использованием деструктивной декларации

    Если вы хотите использовать традиционный for -loop с деструктивным объявлением, по какой-то причине существует способ более эффективного, но хакерского решения. Он использует последовательность вместо распределения нескольких списков и создает только один экземпляр Cell , но сама Cell является изменяемой.

     data class Cell<T>(var i: Int, var j: Int, var value: T) fun <T> Matrix<T>.withIndex(): Sequence<Cell<T>> { val cell = Cell(0, 0, this[0, 0]) return generateSequence(cell) { cell -> cell.j += 1 if (cell.j >= rows) { cell.j = 0 cell.i += 1 if (cell.i >= cols) { return@generateSequence null } } cell.value = this[cell.i, cell.j] cell } } 

    И вы можете использовать эту функцию для итерации матрицы в for -loop:

     for ((i, j, item) in matrix.withIndex()) { if (otherMatrix[i, j] > value) doSomething() } 

    Это решение менее эффективно, чем первое, и не настолько надежное из-за изменчивой Cell , поэтому я бы порекомендовал вам использовать первый.

    Эти две языковые функции используются для реализации желаемого поведения:

    • For-loops можно использовать с любым классом, который имеет метод, который предоставляет итератор .

       for (item in myItems) { ... } 

      Этот код будет скомпилирован, если myItems имеет функцию iterator() возвращающую что-то с функциями hasNext(): Boolean и next() .

      Обычно это реализация Iterable<SomeType> (некоторая коллекция), но вы можете добавить метод iterator() в существующий класс в качестве расширения , и вы также сможете использовать этот класс для for-loops.

    • Для объявления деструкции тип элемента должен иметь функции componentN() .

       val (x, y, z) = item 

      Здесь компилятор ожидает, что item будет иметь функции component1() , component2() и component3() . Вы также можете использовать классы data , они генерируют эти функции.

      Деструктурирование в for-loop работает аналогичным образом: тип, возвращаемый next() должен иметь функции componentN() .


    Пример реализации (не претендующий на лучшую производительность, см. Ниже):

    • Класс с поддержкой деструкции:

       class Cell<T>(val i: Int, val j: Int, val item: T) { operator fun component1() = i operator fun component2() = j operator fun component3() = item } 

      Или используя класс data :

       data class Cell<T>(val i: Int, val j: Int, val item: T) 
    • Функция, которая возвращает List<Cell<T>> (записывается как расширение, но также может быть функцией-членом):

       fun <T> Matrix<T>.withIndex() = (0 .. height - 1).flatMap { i -> (0 .. width - 1). map { j -> Cell(i, j, this[i, j]) } } 
    • Использование:

       for ((i, j, item) in matrix2d.withIndex()) { ... } 

    Решение UPD, предлагаемое Майклом, действительно работает лучше (запустите этот тест , разница составляет от 2x до 3x), поэтому он более подходит для критического кода производительности.

    Следующий метод:

     data class Matrix2DValue<T>(val x: Int, val y: Int, val value: T) fun withIndex(): Iterable<Matrix2DValue<T>> { //build the list of values } 

    Позволит вам писать for :

      for ((x, y, value) in matrix2d.withIndex()) { println("value: $value, x: $x, y: $y") } 

    Имейте в виду, что порядок, в котором вы объявляете свойства data class определяет значения (x, y, value) – в отличие от имен переменных. Дополнительную информацию о деструкции вы можете найти в документации Kotlin .