二进制数组在 C# 中减少映射到矩形
Binary Array Reduce Map to Rectangles in C#
给定以下 c# 数组:
const bool X = true, _ = false;
bool[,] array = new bool[,] {
{ X, X, X, X, _, X, X, X, _, _ },
{ _, _, _, _, _, X, X, X, X, _ },
{ X, X, X, X, X, X, X, X, X, X },
{ X, X, X, X, X, X, X, X, X, X },
{ _, X, X, X, _, X, _, _, X, X },
{ _, X, X, X, _, X, X, _, X, X },
{ _, _, _, X, X, _, _, _, _, _ },
{ X, X, _, X, X, _, X, X, X, _ },
{ _, X, X, _, _, X, X, X, X, X },
{ _, _, X, X, _, _, X, X, X, X },
};
视觉上是这样的:
是否有任何现有方法可以将其转换为尽可能少的矩形区域?
一个可能的解决方案可能是这样的:
理想情况下,我正在寻找可以生成矩形列表的东西:
public IEnumerable<Rectangle> ReduceMap(bool[,] map)
{
List<Rectangle> rects = new List<Rectangle>();
int width = map.GetLength(0), height = map.GetLength(1);
// Reduce
// ....?
return rects;
}
结果类似于:
var output = new List<Rectangle>
{
new Rectangle(0, 0, 4, 1),
new Rectangle(5, 0, 3, 2),
new Rectangle(8, 1, 1, 1),
new Rectangle(0, 2, 10, 2),
// ... etc
};
速度也很重要,矩形越少越好,但找到一个合适的解决方案的计算成本不应过高。
很抱歉,如果之前有人回答过这个问题,如果有人知道在哪里看,那就太好了!
这是一个有趣的问题。
您是否看过以下文章,它提供了有关此主题的良好信息并讨论了解决此问题的不同方法(四叉树、形态学、图形):
Rectangular decomposition of binary images
这来自Ojdo在这个问题中的回答Link
我会开始寻找那里!希望这对您有所帮助。
这是我自己能想到的最好的,如果有人可以对此进行改进,我很乐意将其标记为答案。
这种方法使用多次传递来合并数组,并且似乎很好地减少了数组,大致符合我上面的图表,虽然绝对不是最优的。
我创建了一个 fiddle 用于测试:
https://dotnetfiddle.net/3iuIe6
using System;
using System.Collections.Generic;
using System.Drawing;
public class Program
{
private const string pattern1 = @"
XXXX_XXX__
_____XXXX_
XXXXXXXXXX
XXXXXXXXXX
_XXX_X__XX
_XXX_XX_XX
___XX_____
XX_XX_XXX_
_XX__XXXXX
__XX__XXXX
";
private const string pattern2 = @"
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
";
private const string pattern3 = @"
X_XXXXXXXX
X_XXXXXXXX
X_XXXXXXXX
X_XXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXX_XX
XXXXXXX_XX
XXXXXXX_XX
XXXXXXX_XX
";
private const string pattern4 = @"
X_XXXXXXXX
X_XX__XXXX
X_XXXXXXXX
X_XXXX__XX
XXXXX_XXXX
XXX__XXXXX
XXXX_XX_XX
XXX_XXX_XX
XXXXX_X_XX
XXX_XXX_XX
";
private const string pattern5 = @"
XXXXXXXXXX
__XXXXXXXX
____XXXXXX
_____XXXXX
______XXXX
______XXXX
_____XXXXX
____XXXXXX
__XXXXXXXX
XXXXXXXXXX
";
public static void Main()
{
bool[,] map = PatternTo2DArray(pattern1);
//bool[,] map = PatternTo2DArray(pattern2);
//bool[,] map = PatternTo2DArray(pattern3);
//bool[,] map = PatternTo2DArray(pattern4);
//bool[,] map = PatternTo2DArray(pattern5);
var rects = ReduceMap(map);
int index = 0;
foreach (var rect in rects)
{
Console.WriteLine("{0}: {{ {1}, {2}, {3}, {4} }}", ((index + 1).ToString().PadLeft(1, '0')), rect.X, rect.Y, rect.Width, rect.Height);
index++;
}
var total = DumpMap(map);
Console.WriteLine("\r\nOptimised Map From: {0} to {1}", total, index);
}
public static IEnumerable<Rectangle> ReduceMap(bool[,] map)
{
int width = map.GetLength(0), height = map.GetLength(1);
MapElement[,] bin = new MapElement[width, height];
// Reduce
// Step 1: Convert to map elements:
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
{
if (map[x, y])
bin[x, y] = new MapElement() { X = x, Y = y, Width = 1, Height = 1, Set = true };
}
// Step 2: Process the bin map and generate a collection of Rectangles horizontally
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
// Only care about this if we are set.
if (bin[x, y].Set)
{
// Scan forward and link this tile with its following tiles
int xx = 0;
for (int xForward = x + 1; xForward < width; xForward++)
{
if (!bin[xForward, y].Set)
break;
// We can link this...
bin[xForward, y].Set = false;
bin[x, y].Width++;
xx++; // Skip over these tiles.
}
x += xx;
}
}
}
// Step 3: Process the bin map veritically and join any blocks that have equivalent blocks below them.
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
// Only care about this if we are set.
if (bin[x, y].Set)
{
// Scan down and link this tile with its following tiles
for (int yDown = y + 1; yDown < height; yDown++)
{
if (!bin[x, yDown].Set || bin[x, yDown].Width != bin[x, y].Width) // We might be able to link this if it's the same size
break;
bin[x, yDown].Set = false;
bin[x, y].Height++;
}
}
}
}
// Step 4: Convert map to rectangles
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
// Only care about this if we are set.
if (bin[x, y].Set)
{
var b = bin[x, y];
yield return new Rectangle(b.X, b.Y, b.Width, b.Height);
}
}
}
}
public static int DumpMap(bool[,] map)
{
Console.WriteLine("");
var @out = 0;
int width = map.GetLength(0), height = map.GetLength(1);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
Console.Write(map[x, y] ? "X" : "_");
@out++;
}
Console.WriteLine();
}
return @out;
}
public static int CountDataPoints(bool[,] map)
{
var @out = 0;
int width = map.GetLength(0), height = map.GetLength(1);
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
if (map[x, y])
@out++;
return @out;
}
public static bool[,] PatternTo2DArray(string pattern)
{
var lines = new List<string>(pattern.Split('\n'));
for (int i = lines.Count - 1; i > -1; i--)
{
string line = lines[i].TrimEnd('\r');
if (line.Length == 0)
lines.RemoveAt(i);
else
lines[i] = line;
}
var @out = new bool[lines[0].Length, lines.Count];
for (int y = 0; y < lines.Count; y++)
{
string line = lines[y];
for (int x = 0; x < line.Length; x++)
{
@out[x, y] = !(line[x] == '_');
}
}
return @out;
}
internal struct MapElement
{
public int X;
public int Y;
public int Width;
public int Height;
public bool Set;
}
}
给定以下 c# 数组:
const bool X = true, _ = false;
bool[,] array = new bool[,] {
{ X, X, X, X, _, X, X, X, _, _ },
{ _, _, _, _, _, X, X, X, X, _ },
{ X, X, X, X, X, X, X, X, X, X },
{ X, X, X, X, X, X, X, X, X, X },
{ _, X, X, X, _, X, _, _, X, X },
{ _, X, X, X, _, X, X, _, X, X },
{ _, _, _, X, X, _, _, _, _, _ },
{ X, X, _, X, X, _, X, X, X, _ },
{ _, X, X, _, _, X, X, X, X, X },
{ _, _, X, X, _, _, X, X, X, X },
};
视觉上是这样的:
是否有任何现有方法可以将其转换为尽可能少的矩形区域?
一个可能的解决方案可能是这样的:
理想情况下,我正在寻找可以生成矩形列表的东西:
public IEnumerable<Rectangle> ReduceMap(bool[,] map)
{
List<Rectangle> rects = new List<Rectangle>();
int width = map.GetLength(0), height = map.GetLength(1);
// Reduce
// ....?
return rects;
}
结果类似于:
var output = new List<Rectangle>
{
new Rectangle(0, 0, 4, 1),
new Rectangle(5, 0, 3, 2),
new Rectangle(8, 1, 1, 1),
new Rectangle(0, 2, 10, 2),
// ... etc
};
速度也很重要,矩形越少越好,但找到一个合适的解决方案的计算成本不应过高。
很抱歉,如果之前有人回答过这个问题,如果有人知道在哪里看,那就太好了!
这是一个有趣的问题。
您是否看过以下文章,它提供了有关此主题的良好信息并讨论了解决此问题的不同方法(四叉树、形态学、图形):
Rectangular decomposition of binary images
这来自Ojdo在这个问题中的回答Link
我会开始寻找那里!希望这对您有所帮助。
这是我自己能想到的最好的,如果有人可以对此进行改进,我很乐意将其标记为答案。
这种方法使用多次传递来合并数组,并且似乎很好地减少了数组,大致符合我上面的图表,虽然绝对不是最优的。
我创建了一个 fiddle 用于测试: https://dotnetfiddle.net/3iuIe6
using System;
using System.Collections.Generic;
using System.Drawing;
public class Program
{
private const string pattern1 = @"
XXXX_XXX__
_____XXXX_
XXXXXXXXXX
XXXXXXXXXX
_XXX_X__XX
_XXX_XX_XX
___XX_____
XX_XX_XXX_
_XX__XXXXX
__XX__XXXX
";
private const string pattern2 = @"
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
";
private const string pattern3 = @"
X_XXXXXXXX
X_XXXXXXXX
X_XXXXXXXX
X_XXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXX_XX
XXXXXXX_XX
XXXXXXX_XX
XXXXXXX_XX
";
private const string pattern4 = @"
X_XXXXXXXX
X_XX__XXXX
X_XXXXXXXX
X_XXXX__XX
XXXXX_XXXX
XXX__XXXXX
XXXX_XX_XX
XXX_XXX_XX
XXXXX_X_XX
XXX_XXX_XX
";
private const string pattern5 = @"
XXXXXXXXXX
__XXXXXXXX
____XXXXXX
_____XXXXX
______XXXX
______XXXX
_____XXXXX
____XXXXXX
__XXXXXXXX
XXXXXXXXXX
";
public static void Main()
{
bool[,] map = PatternTo2DArray(pattern1);
//bool[,] map = PatternTo2DArray(pattern2);
//bool[,] map = PatternTo2DArray(pattern3);
//bool[,] map = PatternTo2DArray(pattern4);
//bool[,] map = PatternTo2DArray(pattern5);
var rects = ReduceMap(map);
int index = 0;
foreach (var rect in rects)
{
Console.WriteLine("{0}: {{ {1}, {2}, {3}, {4} }}", ((index + 1).ToString().PadLeft(1, '0')), rect.X, rect.Y, rect.Width, rect.Height);
index++;
}
var total = DumpMap(map);
Console.WriteLine("\r\nOptimised Map From: {0} to {1}", total, index);
}
public static IEnumerable<Rectangle> ReduceMap(bool[,] map)
{
int width = map.GetLength(0), height = map.GetLength(1);
MapElement[,] bin = new MapElement[width, height];
// Reduce
// Step 1: Convert to map elements:
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
{
if (map[x, y])
bin[x, y] = new MapElement() { X = x, Y = y, Width = 1, Height = 1, Set = true };
}
// Step 2: Process the bin map and generate a collection of Rectangles horizontally
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
// Only care about this if we are set.
if (bin[x, y].Set)
{
// Scan forward and link this tile with its following tiles
int xx = 0;
for (int xForward = x + 1; xForward < width; xForward++)
{
if (!bin[xForward, y].Set)
break;
// We can link this...
bin[xForward, y].Set = false;
bin[x, y].Width++;
xx++; // Skip over these tiles.
}
x += xx;
}
}
}
// Step 3: Process the bin map veritically and join any blocks that have equivalent blocks below them.
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
// Only care about this if we are set.
if (bin[x, y].Set)
{
// Scan down and link this tile with its following tiles
for (int yDown = y + 1; yDown < height; yDown++)
{
if (!bin[x, yDown].Set || bin[x, yDown].Width != bin[x, y].Width) // We might be able to link this if it's the same size
break;
bin[x, yDown].Set = false;
bin[x, y].Height++;
}
}
}
}
// Step 4: Convert map to rectangles
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
// Only care about this if we are set.
if (bin[x, y].Set)
{
var b = bin[x, y];
yield return new Rectangle(b.X, b.Y, b.Width, b.Height);
}
}
}
}
public static int DumpMap(bool[,] map)
{
Console.WriteLine("");
var @out = 0;
int width = map.GetLength(0), height = map.GetLength(1);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
Console.Write(map[x, y] ? "X" : "_");
@out++;
}
Console.WriteLine();
}
return @out;
}
public static int CountDataPoints(bool[,] map)
{
var @out = 0;
int width = map.GetLength(0), height = map.GetLength(1);
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
if (map[x, y])
@out++;
return @out;
}
public static bool[,] PatternTo2DArray(string pattern)
{
var lines = new List<string>(pattern.Split('\n'));
for (int i = lines.Count - 1; i > -1; i--)
{
string line = lines[i].TrimEnd('\r');
if (line.Length == 0)
lines.RemoveAt(i);
else
lines[i] = line;
}
var @out = new bool[lines[0].Length, lines.Count];
for (int y = 0; y < lines.Count; y++)
{
string line = lines[y];
for (int x = 0; x < line.Length; x++)
{
@out[x, y] = !(line[x] == '_');
}
}
return @out;
}
internal struct MapElement
{
public int X;
public int Y;
public int Width;
public int Height;
public bool Set;
}
}