【问题标题】:Binary Array Reduce Map to Rectangles in C#二进制数组在 C# 中将映射缩减为矩形
【发布时间】:2020-12-15 06:57:22
【问题描述】:

给定以下 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
    };

速度也很重要,矩形越少越好,但找到一个合适的解决方案不应该在计算上过于昂贵。

抱歉,如果之前已经回答过这个问题,如果有人知道在哪里看,那就太好了!

【问题讨论】:

  • 只是为了理解需求:你想解决什么问题?应用程序是什么?
  • 我认为这是 完美 解决方案的 NP 难题。
  • @Fildor:我正在尝试将一组已知的矩形多边形粘合在一起,以用于应用于平铺贴图的动态照明阴影贴图算法。将所有瓦片单独装壳太贵了,减少这个空间将允许动态重新计算。
  • @Dai:同意,虽然不需要完美的解决方案。
  • 这听起来像是四叉树压缩。

标签: c# arrays algorithm optimization reduction


【解决方案1】:

这是一个有趣的问题。
您是否看过以下文章,它提供了有关此主题的良好信息并讨论了解决此问题的不同方法(四叉树、形态学、图形): Rectangular decomposition of binary images
这来自 Ojdo 在这个问题中的回答 Link
我会开始在那里寻找!希望这会有所帮助。

【讨论】:

    【解决方案2】:

    这是我自己能想到的最好的,如果有人可以对此进行改进,我很乐意将其标记为答案。

    此方法使用多次传递来合并数组,并且似乎可以很好地减少数组,大致符合我上面的图表,尽管绝对不是最佳的。

    我创建了一个用于测试的小提琴: 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;
        }
    }
    
    

    【讨论】:

      猜你喜欢
      • 2014-09-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-17
      • 2012-11-12
      相关资源
      最近更新 更多