In Kotlin, is there a way to use chunking to split a List<List<Int>> of size 1104 x 828 into 184 x 69 sublists of 6 x 12?

120 Views Asked by At

I have a List<List<Int>> in Kotlin of size 1104 x 828.

I want to split it into subgrids of size 6 x 12, of which there would be:

(1104 / 6) x (828 / 12) = 184 x 69

So, in essence, I need a List<List<List<List<Int>>>> of size 184 x 69 x 6 x 12.

Is there any easy way to to do this using chunking? I can easily get it to 184 x 6 x 69 x 12 by calling:

list.map { it.chunked(12) }.chunked(6)

but, of course, that's not the right dimensions.

If I have to, I'll use looping to make the 6 x 12 subgrids, but I was hoping that there was a way to get the list to the proper shape by just using chunking and perhaps mapping or some other list operations.

The goal is to perform a reduction on the 6 x 12 subgrids to end up with a 184 x 69 grid, so maybe there's an easier way to do this that I'm not seeing.

2

There are 2 best solutions below

0
Sebastian On BEST ANSWER

I finally figured out the answer thanks to lukas.j's response. Here is a function that takes a grid List<List<T>> with a subgridHeight and subgridWidth and partitions the grid into rows and columns of the specified height and width. I believe it can probably be made to work even if the subgridHeight and subgridWidth don't evenly divide the grid by passing partialWindows=true to the call to windowed.

fun <T> subgrids(grid: List<List<T>>,
                 subgridHeight: Int,
                 subgridWidth: Int): List<List<List<List<T>>>> =
        grid
            .map { it.windowed(subgridHeight, subgridHeight) }
            .flatMap { it.withIndex() }
            .groupBy { it.index }
            .map { row -> row.value.map { it.value }.chunked(subgridWidth) }

It's working fine for me.

1
lukas.j On

If a list of 9 rows and 4 columns looked like this:

0, 0    0, 1    0, 2    0, 3
1, 0    1, 1    1, 2    1, 3
2, 0    2, 1    2, 2    2, 3
3, 0    3, 1    3, 2    3, 3
4, 0    4, 1    4, 2    4, 3
5, 0    5, 1    5, 2    5, 3
6, 0    6, 1    6, 2    6, 3
7, 0    7, 1    7, 2    7, 3
8, 0    8, 1    8, 2    8, 3

And if the sublists would have to be of size 3 rows and 2 columns, then the splitting into subgrids would look like this:

0, 0    0, 1   |   0, 2    0, 3
1, 0    1, 1   |   1, 2    1, 3
2, 0    2, 1   |   2, 2    2, 3
–––––––––––––––––––––––––––––––
3, 0    3, 1   |   3, 2    3, 3
4, 0    4, 1   |   4, 2    4, 3
5, 0    5, 1   |   5, 2    5, 3
–––––––––––––––––––––––––––––––
6, 0    6, 1   |   6, 2    6, 3
7, 0    7, 1   |   7, 2    7, 3
8, 0    8, 1   |   8, 2    8, 3

With the subgrids indexed like this:

Subgrids:
0, 0   |   0, 1
1, 0   |   1, 1
2, 0   |   1, 1

The following function transforms a grid into subgrids:

fun transform(list: List<List<Pair<Int, Int>>>, rows: Int, columns: Int): List<List<List<List<Pair<Int, Int>>>>> {
  return list
    .flatten()
    .chunked(rows)
    .windowed(rows * columns)
    .map {
      it
        .withIndex()
        .groupBy { (index, _) ->
          index % rows
        }
        .map { (_, value) ->
          value.map { indexedValue -> indexedValue.value }
        }
    }
}

Of course the Pair<Int, Int> is only for demo purposes. It should be switched with Int both in the parameter list and in the return type declaration.

Usage:

val list = List(9) { row -> List(4) { col -> Pair(row, col) } }

val result = transform(list = list, rows = 2, columns = 3)

println(result)

println(result[0][0])
// Output: [[(0, 0), (0, 1)], [(1, 0), (1, 1)], [(2, 0), (2, 1)]]

println(result[1][1])
// Output: [[(1, 0), (1, 1)], [(2, 0), (2, 1)], [(3, 0), (3, 1)]]

println(result[2][0])
// Output: [[(1, 0), (1, 1)], [(2, 0), (2, 1)], [(3, 0), (3, 1)]]