Reputation: 9
I'm working on a project and as part of my process, I need to generate all the possible cell combination for given start point while avoiding the invalid cells (The cells filled with black. see down below).
The grid is NxM and each cell can have different width and height. The cell combination shape should not exceed the max Height and Width.
How can I efficiently generate all the combinations (or Only the biggest) for each position? (the grid can be huge).
We are able to go in 4 directions. (up, right, bottom, left)
Example:
S is the start cell. and the goal is to find all the possible cell combinations that can be placed (without exceeding the maximum width and height). In this example MAX_WIDTH - 4, MAX_HEIGHT - 7.
One option could be:
and so on..
My Idea is to use a pathfinding algorithm such as Dijkstra or A*. (Each Node is the cell) and the end goal is when we exceed to maximum lengths. I should take into account the option that maybe I'll need to give a score for shape (by the number of vertices etc...)
I tried to go each direction with DFS, but it's working only on small grids.
My Code:
public class CombinationCreator
{
/// <summary>
/// The grid that contain 2D array of cells.
/// </summary>
private readonly Grid _grid;
/// <summary>
/// Current state of the cell combination shape, used in the recursive function.
/// </summary>
private List<Cell> CurrentState { get; set; } = new List<Cell>();
/// <summary>
/// List of directions used for scanning the cells.
/// </summary>
private readonly List<(int row, int column)> Directions = new List<(int row, int column)>()
{
(0, 1), // right
(1, 0), // bottom
(0, -1), // left
(-1, 0) // top
};
/// <summary>
/// List of all the possible combinations.
/// </summary>
public List<List<Cell>> Results { get; set; } = new List<List<Cell>>();
public CombinationCreator(Grid grid)
{
_grid = grid;
}
/// <summary>
/// Get i , j indexes of the cell. (will be the starting index)
/// </summary>
public List<List<Cell>> GetCombinations(int i, int j)
{
double MAX_WIDTH = 4;
double MAX_HEIGHT = 7;
List<List<Cell>> result = new List<List<Cell>>;
return GenerateCombination(i, j, MAX_WIDTH, MAX_HEIGHT);
}
/// <summary>
/// Recursive function to generate all the possible cell combinations by scanning the grid.
/// </summary>
private void GenerateCombinations(int i, int j, double maxWidth, double maxHeight, int depth = 0, double minX = double.MaxValue,
double maxX = double.MinValue, double minY = double.MaxValue, double maxY = double.MinValue)
{
if (depth > 50)
{ // to prevent Infinite Recursion
return;
}
if (ShouldSavePreviousState(i, j, maxWidth, maxHeight, minX, maxX, minY, maxY))
{ // If the current cell will exceed maxWidth and maxHeight then we need to save the previous state.
Results.Add(new List<Cell>(CurrentState));
}
else
{
if (i >= 0 && i < _grid.Cells.GetLength(0) &&
j >= 0 && j < _grid.Cells.GetLength(1))
{
Cell currentCell = _grid.Cells[i, j];
if (!currentCell.Marked && currentCell.Valid && !currentCell.IsUsed)
{
currentCell.Marked = true;
CurrentState.Add(currentCell);
minX = Math.Min(minX, currentCell.TopLeft.x);
maxX = Math.Max(maxX, currentCell.BottomRight.x);
minY = Math.Min(minY, currentCell.BottomRight.y);
maxY = Math.Max(maxY, currentCell.TopLeft.y);
foreach (var direction in Directions)
{ // Go each direction
int newRow = i + direction.row;
int newCol = j + direction.column;
GenerateCombinations(newRow, newCol, maxWidth, maxHeight, depth + 1, minX, maxX, minY, maxY);
}
CurrentState.RemoveAt(CurrentState.Count - 1);
currentCell.Marked = false;
}
}
}
}
/// <summary>
/// Check if the current cell will exceed maxWidth and maxHeight or the cell is the boundaries.
/// then we need to save the previous state.
/// </summary>
private bool ShouldSavePreviousState(int i, int j, double maxWidth, double maxHeight, double minX = double.MaxValue,
double maxX = double.MinValue, double minY = double.MaxValue, double maxY = double.MinValue)
{
double currentWidth = maxX - minX;
double currentHeight = maxY - minY;
if (i >= 0 && i < _grid.Cells.GetLength(0) &&
j >= 0 && j < _grid.Cells.GetLength(1))
{
Cell currentCell = _grid.Cells[i, j];
if (!currentCell.Marked && currentCell.Valid && !currentCell.IsUsed)
{
double nextMinX = Math.Min(minX, currentCell.TopLeft.x);
double nextMaxX = Math.Max(maxX, currentCell.BottomRight.x);
double nextMinY = Math.Min(minY, currentCell.BottomRight.y);
double nextMaxY = Math.Max(maxY, currentCell.TopLeft.y);
double nextWidth = nextMaxX - nextMinX;
double nextHeight = nextMaxY - nextMinY;
if (nextHeight > maxHeight || nextWidth > maxWidth)
{
return true;
}
}
if (!currentCell.Valid || currentCell.IsUsed)
{
if (currentHeight > maxHeight || currentWidth > maxWidth)
{
return false;
}
return true;
}
}
return false;
}
}
Cell Class:
public class Cell
{
public bool Marked { get; set; } // Indicates when already saw the cell during the scanning
public bool Valid { get; set; } // Indicates if we can use the cell ( white cell ).
public bool IsUsed { get; set; } // Indicates if another shape use this cell.
public Point TopLeft { get; set; } // The top left point
public Point BottomRight { get; set; } // The bottom right point
}
Upvotes: 0
Views: 176
Reputation: 20472
While this may well be soluble using graph theory, a more straightforward approach is to tackle it using computational geometry.
An algorithm to enumerate valid ( as specified in question ) cell combinations:
- LOOP CA over cells
- Create E, a rectangle of maximum combination size,
located with top right at top right of cell CA
- IF start cell not completely included inside E
- CONTINUE to next CA cell
- CLEAR current combination
- LOOP CB over cells
- IF CB completely included inside E AND not forbidden
- Add cell CB to current combination
- Add current combination to list of combinations
For this input
myGrid.setVert({0.5, 1, 1.5, 2, 2.5, 3, 2.5, 2});
myGrid.setHorz({0.5, 1, 1.5, 2, 2.5, 3, 2.5, 2});
myGrid.setForbid({{5, 2}});
myGrid.setStartCell({4, 4});
myGrid.setMaxCombSize({6, 7});
The list of cell combinations is
12 valid cell combinations found
2 1, 3 1, 4 1, 2 2, 3 2, 4 2, 2 3, 3 3, 4 3, 2 4, 3 4, 4 4,
3 1, 4 1, 3 2, 4 2, 3 3, 4 3, 3 4, 4 4,
4 1, 5 1, 4 2, 4 3, 5 3, 4 4, 5 4,
2 2, 3 2, 4 2, 2 3, 3 3, 4 3, 2 4, 3 4, 4 4,
3 2, 4 2, 3 3, 4 3, 3 4, 4 4,
4 2, 4 3, 5 3, 4 4, 5 4,
2 3, 3 3, 4 3, 2 4, 3 4, 4 4,
3 3, 4 3, 3 4, 4 4,
4 3, 5 3, 4 4, 5 4,
2 4, 3 4, 4 4, 2 5, 3 5, 4 5,
3 4, 4 4, 3 5, 4 5,
4 4, 5 4, 4 5, 5 5,
( Each row is a valid combination. The number pairs are the zero-based column and row indices of combined cells. )
Here is a graphical display of the first combination
( The starting cell is indicated by "S", and the blue outlined cells are included in the combination. )
Here is the C++ code that does the enumeration
void cGrid::EnumerateCombinations()
{
auto startCellDim = rectDim(myStartCell);
// loop E over cells
for (int rowE = 0; rowE < myHorz.size(); rowE++)
{
for (int colE = 0; colE < myVert.size(); colE++)
{
// create rectangle E of maximum combination size with E cell at top left
auto Ecell = rectDim(std::make_pair(colE, rowE));
rect_ltrb_t E{
Ecell[0], Ecell[1],
Ecell[0] + myMaxCombSize.first, Ecell[1] + myMaxCombSize.second};
// check that E includes the start cell
if (!isInside(startCellDim, E))
continue;
std::vector<std::pair<int, int>> comb;
// loop over cells
for (int row = 0; row < myHorz.size(); row++)
for (int col = 0; col < myHorz.size(); col++)
{
// if cell completely inside E and valid
auto cell = rectDim(std::make_pair(col, row));
if (isInside(cell, E) && (!isForbidden(std::make_pair(col, row))))
{
// add to current combination
comb.push_back(std::make_pair(col, row));
}
}
// add current combination to list
myListComb.push_back(comb);
}
}
}
The C++ code for the complete application is at https://github.com/JamesBremner/so76407006 The application displays the cell combination with the largest total area. The user can advance the display through other valid combinations in order of decreasing area.
Upvotes: 0