I have an array of rectangles. Some of them might intersect, forming a pair:

While others might intersect, forming a chain:

And, of course, some of them might be alone.
I want to form a list that will include all chains, pairs and single rectangles. For example, if we have an array of rectangles that looks like that:

I want to get following list:
{ { A, B, C, D, E },
{ F },
{ G },
{ H, J },
{ K, L },
{ M, N, O } }
where A, B, C, D, E, etc. are corresponding Rectangles.
So far I tried forming all possible permutations with this code:
var rectangles = new Rectangle[] { ..., ..., ... }; // my rectangles
var rectanglePermutations = new IEnumerable<IEnumerable<Rectangle>>();
// make all permutations with 2, 3, 4 (and so on) elements
for (var i = 2; i < rectangles.Length; i++)
{
rectanglePermutations.AddRange(GetPermutations(rectangles, i));
}
IEnumerable<IEnumerable<T>> GetPermutations<T>(IEnumerable<T> items, int count)
{
var i = 0;
foreach (var item in items)
{
if (count == 1)
{
yield return new T[] { item };
}
else
{
foreach (var result in GetPermutations(items.Skip(i + 1), count - 1))
{
yield return new T[] { item }.Concat(result);
}
}
i++;
}
}
Then I was trying to find if all rectangles inside each enumerable in rectanglePermutations can form a chain:
var result = new List<List<Rectangle>>();
foreach (var permutations in rectanglePermutations)
{
var chain = new List<Rectangle>(permutations);
var canFormNewRectangles = true;
do
{
var newlyFormedRectanglesCount = 0;
// get all possible pairs
var pairs = GetPermutations(chain.Distinct(), 2);
chain.Clear();
foreach (var pair in pairs)
{
// if rectangles in pair intersect
var union = Rectangle.Union(pair.First(), pair.Last());
if (union.Width * union.Height > 0)
{
// replace them with a new rectangle
chain.Add(new Rectangle(pair.Min(r => r.Left), pair.Min(r => r.Top), pair.Max(r => r.Right) - pair.Min(r => r.Left), pair.Min(r => r.Top) - pair.Max(r => r.Bottom)));
newlyFormedRectanglesCount++;
}
}
// if we can't make any more new rectangles
if (newlyFormedRectanglesCount == 0)
{
canFormNewRectangles = false;
}
}
while (canFormNewRectangles);
// if all rectangles made a single chain
if (chain.Count == 1)
{
result.Add(permutation.ToList());
}
}
// add single rectangles to result that are not used in any chains
var usedRectangles = chains.SelectMany(chain => chain).Distinct();
result.AddRange(rectangles.Except(usedRectangles).ToList());
The problem is that code above is ineffective and slow. For example, if we have a 5-rectangle-chain, a 3-rectangle-chain and 2 separate rectangles (10 in total), we'll get 1013 possible permutations from first part of code, and at least 11160 iterations from second part of code (in fact, we'll be getting twice as much for given example). Using that to find only 2 suitable combinations is inefficient. I'm pretty sure there's a better approach and need some help with that.

The first step to solve this should be to figure out what rectangles intersect with each other. I think the fast solution should be an R-Tree, but as long as the number of triangles is limited an exhaustive check should work well enough. To store all the intersections we can use a Microsoft.Collections.Extensions.MultiValueDictionary, another alternative would be a matrix.
We can view all the rectangles as a graph, so to figure out all the rectangles connected to one rectangle we need a way to traverse this graph, for example by using depth first iteration. I like to use a generic iterator block for this, but some prefer recursive solutions:
The
visitedset ensures rectangles are only returned once, even if there is loops in the graph. To find all the groups of rectangles we simply need to call this method repetedly:Note that we keep the same visited set, so that we do not worry about including a rectangle twice.
This would then be called like