C# Question

How to group by portions of a grid by quadratic amounts?

I'm facing the following problem: The have one grid and each grid cell has a position. One example of such grid could be the following one:

_________________________________________
| | | | | |
| (0,0) | (1,0) | (2,0) | (3,0) | (4,0) |
|_______|_______|_______|_______|_______|
| | | | | |
| (0,1) | (1,1) | (2,1) | (3,1) | (4,1) |
|_______|_______|_______|_______|_______|
| | | | | |
| (0,2) | (1,2) | (2,2) | (3,2) | (4,2) |
|_______|_______|_______|_______|_______|
| | | | | |
| (0,3) | (1,3) | (2,3) | (3,3) | (4,3) |
|_______|_______|_______|_______|_______|
| | | | | |
| (0,4) | (1,4) | (2,4) | (3,4) | (4,4) |
|_______|_______|_______|_______|_______|


I need to create a function that, given a number N, creates groups of quadratic NxN amounts of that number at most. For instance, for N = 2, each group would contain at most a 2x2 cells, group 1 would contain [(0, 0) (1, 0) (0, 1) (1, 1)], group 2 [(2, 0) (3, 0) (2, 1) (3, 1)], group 3 would just contain [(4, 0) (4, 1)] and so on.

I'm using C#, so this is conceptually a Group by operation, I decided to use LINQ Group By function, however it requires a lambda expression that must calculate a number that must be the same for each group. Therefore I'm looking at an expression that, for my problem with n = 2, must return the same number for [(0, 0) (1, 0) (0, 1) (1, 1)], another number for [(2, 0) (3, 0) (2, 1) (3, 1)], a different one for [(4, 0) (4, 1)], etc...

Which kind of expression could fulfil that property?

Thank you

Answer

In order to use a 'Group By'-operation, you need to define a key to group on. In this case, if it's about a grid, the only possible key I could think of is the result of a calculation based on the index of the row/column in the grid. I find it a bit difficult to explain the calculation I chose in plain text so I hope the example below does the talk for me.

Does this answer or help with your question?

Test data / settings

var grid = new List<List<string>>();

grid.Add(new List<string>(new[] { "0,0", "1,0", "2,0", "3,0", "4,0" }));
grid.Add(new List<string>(new[] { "0,1", "1,1", "2,1", "3,1", "4,1" }));

grid.Add(new List<string>(new[] { "0,2", "1,2", "2,2", "3,2", "4,2" }));
grid.Add(new List<string>(new[] { "0,3", "1,3", "2,3", "3,3", "4,3" }));

grid.Add(new List<string>(new[] { "0,4", "1,4", "2,4", "3,4", "4,4" }));

// Quadratic group factor.
int n = 2;

Solution 1 - Lambda expression

var result_1 = grid

    // Create quadratic groups by calculating the combined index of the row+column with the quadratic group factor.
    .SelectMany(r =>
        r.GroupBy(c =>
            (int)Math.Floor((double)grid.IndexOf(r) / (double)n)
                + "_" +
            (int)Math.Floor((double)r.IndexOf(c) / (double)n)
        )
    )

    // Combine all same keys together in one group.
    .GroupBy(g => g.Key)

    // Get all results per group.
    .Select(gg => gg.SelectMany(g => g).ToList())

    // ToList() because it's easier to inspect the value of the result while debugging.
    .ToList();

// Short version:
var result_2 = grid
    .SelectMany(r =>
        r.GroupBy(c =>
            (int)Math.Floor((double)grid.IndexOf(r) / (double)n) + "_" + (int)Math.Floor((double)r.IndexOf(c) / (double)n)
        )
    )
    .GroupBy(g => g.Key)
    .Select(gg => gg.SelectMany(g => g).ToList())
    .ToList();

Solution 2 - Oldschool loop, probably easier/better to understand.

var result_3 = new List<List<string>>();

// Range (amount of both 'rows' and 'columns' since it's a grid).
int range = (int)Math.Ceiling((double)grid.Count / (double)n);

// Loop through 'rows'.
for(var y = 0; y < range; y++)
{
    int skipRowsAmount = (y * n);
    int takeRowsAmount = n;

    // Get all 'rows' to split in groups.
    var rows = grid.Skip(skipRowsAmount).Take(takeRowsAmount).ToList();

    // Loop through 'columns'.
    for (var x = 0; x < range; x++)
    {
        int skipColumnsAmount = (x * n);
        int takeColumnsAmount = n;

        // Get all 'columns' from all 'rows' to split in groups.
        var quadraticColumns = rows.SelectMany(l => l.Skip(skipColumnsAmount).Take(takeColumnsAmount)).ToList();

        // Add the quadratic columns group to the result.
        result_3.Add(quadraticColumns);
    }
}
Comments