【算法】十一月阳光下的阴影面积
十一月的阳光透过窗户,照射在一位笑起来甜美、青春洋溢的女子的办公桌上。小悦,一个总是以高马尾造型亮相的软件工程师,展现出她的干练与活力。那乌黑亮丽的长发轻盈飘动,仿佛在诉说着她的独特魅力。她的眉眼如画,那双明亮的眼睛里闪烁着对知识的渴望和对技术挑战的热情。
这一天,她收到了一封来自医院的邮件,邮件中提到的扫描设备技术更新问题让她感到有些挑战。然而,对于技术挑战,小悦总是充满了好奇心和热情。她决定主动联系医院,表达自己愿意参与这个项目的意愿。幸运的是,医院方面很快回复了她的邮件,并安排了一次电话会议。
在电话会议中,小悦与医院的管理人员和相关领域的专家进行了交流。他们的声音充满了对新技术和新思维的渴望。小悦也意识到这次机会对于自己的事业发展可能是一个重大的突破。
不久之后,小悦成功加入了医院方面的团队。在团队中,她发现了很多优秀的专家和工程师。其中有一位资深医生对扫描设备的需求非常了解,而另一位工程师则对图像处理算法有深入的研究。小悦深知自己在这个团队中担任着关键的角色,必须充分发挥自己的优势。
在团队的合作中,小悦与大家建立了良好的合作关系。她不断地与团队成员沟通和交流,了解他们的需求和想法。她的声音总是温柔而自信,让人感到安心和信任。同时,她也向团队成员分享了自己的经验和专业知识,为项目的进展做出了巨大的贡献。
然而,随着项目的深入,小悦逐渐发现了一些技术难题。其中最大的问题是如何计算阴影部分的面积并集。为了解决这个问题,她不断地查阅文献、研究算法,并尝试了多种方法。有时候,她会陷入深深的困境,甚至整晚都无法入睡。但是,她从未放弃过对这个问题的探索和解决。
经过一段时间的努力,小悦终于提出了一种有效的方法来计算阴影部分的面积并集。这个方法不仅得到了团队的认可和支持,也成功地解决了项目中的一大难题。小悦的贡献让整个团队都感到非常惊喜和敬佩。
在接下来的时间中,小悦和团队继续努力工作,终于成功地开发出了一款新的扫描设备程序设计。这款程序采用了小悦提出的阴影面积计算方法,有效地提高了扫描的准确性和效率。医院方面对这款程序非常满意,并决定将其投入使用。
在这个过程中,小悦学到了很多东西。她不仅在技术方面取得了突破和成长,还学会了如何与不同背景的人合作和沟通。她深刻地认识到人际关系的重要性以及如何利用人际关系来拓展自己的视野和能力。同时,她也体验到了帮助他人的喜悦和成就感。
小悦面临的问题是,医学影像重建:医学影像(如CT、MRI等)通常是由多个切片图像组成的,这些图像可能存在重叠或交叉的区域。矩形面积并集算法可以用于计算这些图像之间的重叠区域,从而实现准确的图像重建和融合。
她需要开发一个名为Calculate的方法,该方法接受一个二维数组作为参数,其中每个子数组表示一个矩形的坐标。在这个例子中,三个矩形的坐标分别为[1,2,5,6]、[1,3,4,5]和[3,1,5,4]。并返回期望的面积并集。
每个矩形表示为:[x0,y0,x1,y1]
(x0,y0)-矩形左下角的坐标
(x1,y1)-矩形右上角的坐标
图例(面积=18):
算法实现1:
1 private class Rectangle // 定义一个名为Rectangle的私有类 2 { 3 public long x0; // 矩形的左边界 4 public long y0; // 矩形的底边界 5 public long x1; // 矩形的右边界 6 public long y1; // 矩形的顶边界 7 public bool ToDelete = false; // 标记是否需要删除该矩形 8 9 public Rectangle(long x0, long y0, long x1, long y1) // 构造函数,用于初始化矩形对象的边界值 10 { 11 this.x0 = x0; 12 this.x1 = x1; 13 this.y0 = y0; 14 this.y1 = y1; 15 } 16 17 public long S() // 计算矩形的面积 18 { 19 return (x1 - x0) * (y1 - y0); 20 } 21 22 public bool IsInside(Rectangle r) // 判断当前矩形是否完全包含另一个矩形r 23 { 24 return ((r.x0 >= x0) && (r.x1 <= x1) && (r.y0 >= y0) && (r.y1 <= y1)) ? true : false; 25 } 26 27 public bool IsOutside(Rectangle r) // 判断当前矩形是否完全在另一个矩形r的外部 28 { 29 return ((r.x0 >= x1) || (r.x1 <= x0) || (r.y0 >= y1) || (r.y1 <= y0)) ? true : false; 30 } 31 32 public List<Rectangle> GetIntersection(Rectangle r) // 获取当前矩形与另一个矩形r的相交部分 33 { 34 List<Rectangle> rests = new List<Rectangle>(); 35 if (r.x0 < x0) 36 rests.Add(new Rectangle(r.x0, r.y0, x0, r.y1)); 37 if (r.x1 > x1) 38 rests.Add(new Rectangle(x1, r.y0, r.x1, r.y1)); 39 if (r.y0 < y0) 40 rests.Add(new Rectangle(Math.Max(r.x0, x0), r.y0, Math.Min(r.x1, x1), y0)); 41 if (r.y1 > y1) 42 rests.Add(new Rectangle(Math.Max(r.x0, x0), y1, Math.Min(r.x1, x1), r.y1)); 43 return rests; 44 } 45 } 46 47 public static long Calculate(IEnumerable<int[]> rectangles) // 定义一个名为Calculate的静态方法,用于计算矩形的最大面积 48 { 49 long maxS = 0; // 最大面积的初始值为0 50 List<Rectangle> lastRectangle = new List<Rectangle>(rectangles.Select(x => new Rectangle((long)x[0], (long)x[1], (long)x[2], (long)x[3]))); // 将传入的矩形参数转换为Rectangle对象,并添加到lastRectangle列表中 51 while (lastRectangle.Count > 0) // 当lastRectangle列表不为空时,进行循环 52 { 53 Rectangle top = lastRectangle.First(); // 获取lastRectangle列表的第一个矩形对象 54 lastRectangle.Remove(top); // 从lastRectangle列表中移除该矩形对象 55 List<Rectangle> intersect = new List<Rectangle>() { top }; // 创建一个名为intersect的列表,初始值为包含top矩形对象的列表 56 var intersected = lastRectangle.Where(x => !x.IsOutside(top)); // 从lastRectangle列表中筛选出与top矩形相交的矩形对象,并存储在intersected变量中 57 foreach (Rectangle r in intersected) // 遍历intersected列表中的每个矩形对象 58 { 59 intersect.RemoveAll(x => r.IsInside(x)); // 从intersect列表中移除完全被r矩形包含的矩形对象 60 List<Rectangle> newIntersections = new List<Rectangle>(); 61 foreach (Rectangle x in intersect) // 遍历intersect列表中的每个矩形对象 62 if (!r.IsOutside(x)) // 如果r矩形与x矩形相交 63 { 64 newIntersections.AddRange(r.GetIntersection(x)); // 获取r矩形与x矩形的相交部分,并添加到newIntersections列表中 65 x.ToDelete = true; // 标记x矩形需要删除 66 } 67 intersect.RemoveAll(x => x.ToDelete); // 从intersect列表中移除需要删除的矩形对象 68 intersect.AddRange(newIntersections); // 将newIntersections列表中的矩形对象添加到intersect列表中 69 if (intersect.Count == 0) // 如果intersect列表为空,跳出循环 70 break; 71 } 72 if (intersect.Count > 0) // 如果intersect列表不为空 73 maxS += intersect.Sum(x => x.S()); // 将intersect列表中每个矩形对象的面积相加,并累加到maxS变量中 74 } 75 return maxS; // 返回最大面积maxS 76 }
首先,矩形的相交关系是指两个矩形是否有共同的区域。我们可以通过判断两个矩形的边界是否有重叠来确定它们是否相交。如果两个矩形的边界有重叠,则它们相交;否则,它们不相交。
其次,矩形的包含关系是指一个矩形是否完全包含另一个矩形。我们可以通过比较两个矩形的边界来确定包含关系。如果一个矩形的边界完全包含在另一个矩形的边界内部,则前者包含后者;否则,前者不包含后者。
在算法中,我们首先将传入的矩形参数转换为Rectangle
对象,并存储在lastRectangle
列表中。然后,通过循环处理lastRectangle
列表中的矩形对象,找到与当前矩形相交的其他矩形对象,并计算它们的相交部分。
在处理每个矩形对象时,我们首先找到与当前矩形相交的矩形对象,并将它们存储在intersected
变量中。然后,我们遍历intersected
列表中的每个矩形对象,将完全被当前矩形包含的矩形对象从intersect
列表中移除。接下来,对于与当前矩形相交的每个矩形对象,我们计算它们的相交部分,并将相交部分添加到intersect
列表中。最后,我们将intersect
列表中的矩形对象的面积相加,并累加到maxS
变量中。
通过这种逐步计算矩形的相交部分,我们可以找到一组矩形的最大面积。这是因为我们通过不断更新intersect
列表来剔除已经被其他矩形完全包含的矩形,只保留与其他矩形相交的部分。最终,我们将intersect
列表中的矩形对象的面积相加,得到这组矩形的最大面积。
这个算法的数学原理基于矩形的几何性质和集合运算的概念,通过对矩形的相交和包含关系进行处理,最终得到最大面积并集。
算法实现2:
1 struct Rect // 定义一个名为Rect的结构体 2 { 3 public int l, r, t, b; // 定义四个整型变量,分别表示矩形的左、右、上、下边界 4 public long area => (long)(r - l) * (t - b); // 定义一个名为area的只读属性,表示矩形的面积 5 public bool exist => l < r && b < t; // 定义一个名为exist的只读属性,表示矩形是否存在 6 7 public static Rect Create(params int[] c) => // 定义一个名为Create的静态方法,用于创建一个新的Rect对象 8 new Rect() { l = c[0], b = c[1], r = c[2], t = c[3] }; 9 10 public bool intersects(Rect rc, out Rect result) => // 定义一个名为intersects的方法,用于判断两个矩形是否相交,并返回相交部分的矩形对象 11 (result = Create( 12 Math.Max(l, rc.l), Math.Max(b, rc.b), 13 Math.Min(r, rc.r), Math.Min(t, rc.t))) 14 .exist; 15 16 public Rect[] octants(int n = int.MinValue, int p = int.MaxValue) => // 定义一个名为octants的方法,用于将当前矩形分成八个象限,并返回八个象限的矩形对象数组 17 new [] { 18 Create(n, n, l, b), 19 Create(n, b, l, t), 20 Create(n, t, l, p), 21 Create(l, t, r, p), 22 Create(r, t, p, p), 23 Create(r, b, p, t), 24 Create(r, n, p, b), 25 Create(l, n, r, b) 26 }; 27 } 28 29 class OctTreeNode // 定义一个名为OctTreeNode的类 30 { 31 Rect origin; // 定义一个名为origin的Rect对象,表示当前节点所代表的矩形 32 Rect[] oct_bounds; // 定义一个名为oct_bounds的Rect对象数组,表示当前节点所代表的矩形被分成的八个象限 33 OctTreeNode[] oct_values; // 定义一个名为oct_values的OctTreeNode对象数组,表示当前节点所代表的矩形被分成的八个象限所代表的子节点 34 35 public OctTreeNode(Rect rc) // 定义一个构造函数,用于创建一个新的OctTreeNode对象 36 { 37 origin = rc; // 将传入的Rect对象赋值给origin变量 38 oct_bounds = rc.octants(); // 将当前矩形分成八个象限,并赋值给oct_bounds变量 39 oct_values = new OctTreeNode[8]; // 创建一个长度为8的OctTreeNode对象数组,并赋值给oct_values变量 40 } 41 42 public void insert(Rect rc) // 定义一个名为insert的方法,用于向当前节点插入一个新的矩形对象 43 { 44 for (int i = 0; i < 8; i++) // 遍历oct_bounds数组中的每个矩形对象 45 { 46 if (oct_bounds[i].intersects(rc, out var part)) // 如果当前矩形与遍历到的矩形相交 47 { 48 oct_values[i] ?. insert(part); // 如果oct_values数组中第i个元素不为null,则递归调用insert方法,将part矩形插入到oct_values数组中第i个元素所代表的子节点中 49 oct_values[i] ??= new OctTreeNode(part); // 如果oct_values数组中第i个元素为null,则创建一个新的OctTreeNode对象,并赋值给oct_values数组中第i个元素 50 } 51 } 52 } 53 54 public long area => origin.area + // 定义一个名为area的只读属性,表示当前节点所代表的矩形的面积加上所有子节点所代表的矩形的面积之和 55 oct_values.Sum(v => v?.area ?? 0); // 遍历oct_values数组中的每个元素,如果元素不为null,则获取它所代表的矩形的面积,否则返回0 56 } 57 58 public static class Edm // 定义一个名为Edm的静态类 59 { 60 public static long Calculate(IEnumerable<int[]> rc) // 定义一个名为Calculate的静态方法,用于计算一组矩形的最大面积 61 { 62 var en = rc.Select(Rect.Create).GetEnumerator(); // 将传入的矩形参数转换为Rect对象,并获取一个枚举器 63 if (!en.MoveNext()) return 0; // 如果枚举器没有下一个元素,则直接返回0 64 65 var tree = new OctTreeNode(en.Current); // 创建一个新的OctTreeNode对象,并将枚举器的第一个元素作为参数传入 66 while (en.MoveNext()) tree.insert(en.Current); // 遍历枚举器中的每个元素,并将它们插入到tree中 67 return tree.area; // 返回tree所代表的矩形的面积 68 } 69 }
这段代码实现了一个八叉树算法,用于计算一组矩形的最大面积。
八叉树算法是一种经典的数据结构和算法,其历史可以追溯到20世纪60年代。它最早被用于计算机图形学和计算机视觉领域,用于处理空间分割和区域查询等问题。
八叉树最早由法国计算机科学家Frits van der Hoeven在1966年引入,用于在计算机图形学中进行空间分割和区域查询。八叉树的名字来源于其树状结构,每个节点有八个子节点,对应于三维空间中的八个象限。
随后,八叉树在计算机视觉领域得到广泛应用,用于处理图像和空间数据。例如,八叉树可以用于图像压缩、图像搜索、碰撞检测等应用中。
随着计算机硬件和算法的发展,八叉树的变种和改进也被提出。例如,四叉树是八叉树的二维版本,用于处理二维空间数据。此外,还有基于八叉树的自适应分割方法和多分辨率表示方法等。
八叉树算法的应用领域不仅限于计算机图形学和计算机视觉,还可以用于地理信息系统、医学图像处理、物理模拟等领域。它提供了一种高效的数据结构和算法,可以用于处理多维空间数据和进行空间查询。
-
Rect
结构体表示一个矩形对象,其中包含矩形的左、右、上、下边界,以及计算矩形面积和判断矩形是否存在的属性。 -
Rect
结构体还定义了一个intersects
方法,用于判断两个矩形是否相交,并返回相交部分的矩形对象。 -
Rect
结构体还定义了一个octants
方法,用于将当前矩形分成八个象限,并返回八个象限的矩形对象数组。 -
OctTreeNode
类表示一个八叉树节点,每个节点代表一个矩形。该类包含一个origin
变量表示当前节点所代表的矩形,以及一个oct_bounds
数组表示当前节点所代表的矩形被分成的八个象限,以及一个oct_values
数组表示八个象限所代表的子节点。 -
OctTreeNode
类定义了一个insert
方法,用于向当前节点插入一个新的矩形对象。该方法通过遍历八个象限的矩形对象,判断当前矩形与遍历到的矩形是否相交,如果相交则递归调用insert
方法将相交部分的矩形插入到对应的子节点中。 -
OctTreeNode
类还定义了一个area
属性,表示当前节点所代表的矩形的面积加上所有子节点所代表的矩形的面积之和。 -
Edm
静态类定义了一个Calculate
方法,用于计算一组矩形的最大面积。该方法首先将传入的矩形参数转换为Rect
对象,并创建一个八叉树节点。然后,遍历矩形对象并将它们插入到八叉树中。最后,返回八叉树所代表的矩形的面积作为结果。
这个算法的数学原理基于矩形的几何性质和八叉树的概念,通过构建八叉树来处理矩形的相交和包含关系,最终得到一组矩形的最大面积并集。
算法2中有些简写的cSharp语法,解释如下:
1 //1.这段代码使用了C#语言的Lambda表达式和对象初始化器。Lambda表达式用于定义一个匿名方法,而对象初始化器用于在创建对象的同时对其属性进行初始化。 2 public static Rect Create(params int[] c) => 3 new Rect() { l = c[0], b = c[1], r = c[2], t = c[3] }; 4 5 //这段代码定义了一个名为Create的静态方法,其目的是创建一个矩形对象。该方法接受一个可变长度的整数数组作为参数,数组中的元素按照特定的顺序表示矩形的左边界、底边界、右边界和顶边界。 6 //以下是另一种常用写法: 7 public static Rect Create(params int[] c) 8 { 9 int left = c[0]; 10 int bottom = c[1]; 11 int right = c[2]; 12 int top = c[3]; 13 14 Rect rect = new Rect(); 15 rect.l = left; 16 rect.b = bottom; 17 rect.r = right; 18 rect.t = top; 19 20 return rect; 21 } 22 23 //2.同一 24 public bool intersects(Rect rc, out Rect result) => 25 (result = Create( 26 Math.Max(l, rc.l), Math.Max(b, rc.b), 27 Math.Min(r, rc.r), Math.Min(t, rc.t))) 28 .exist; 29 30 //常用写法: 31 public bool Intersects(Rect rc, out Rect result) 32 { 33 int left = Math.Max(l, rc.l); 34 int bottom = Math.Max(b, rc.b); 35 int right = Math.Min(r, rc.r); 36 int top = Math.Min(t, rc.t); 37 38 result = Create(left, bottom, right, top); 39 40 return result.exist; 41 } 42 /* 43 在这个写法中,首先根据两个矩形的左边界、底边界、右边界和顶边界的最大值和最小值,分别计算出相交部分的矩形的左边界、底边界、右边界和顶边界。 44 45 然后,使用这些计算结果调用Create方法创建一个新的矩形对象,并将其赋值给result参数。 46 47 最后,返回新创建的矩形对象的exist属性,表示两个矩形是否相交。 48 49 这个写法与原始代码的功能相同,但使用了更加明确的变量名和更加传统的语法,使其更容易理解。*/
算法2和算法1都是用于计算矩形的面积交集的实现,但它们使用了不同的数据结构和算法。
算法1使用了一个二维数组来表示每个点的覆盖情况,并使用扫描线算法来计算矩形的面积交集。这个算法的优点是实现简单,但需要额外的空间来存储覆盖情况,并且在处理大量矩形时可能会变得非常慢。
相比之下,算法2使用了八叉树的数据结构来表示矩形,并使用递归的方式来插入和查询矩形。这个算法的优点是可以高效地处理大量矩形,并且可以快速地计算矩形的面积交集。但缺点是实现相对复杂,需要额外的空间来存储八叉树节点,而且在处理高维数据时可能不太适用。
总的来说,这两个算法都有各自的优缺点,可以根据具体情况选择适合的算法。如果处理的数据量不是很大,或者需要实现的算法比较简单,那么可以选择算法1;如果处理的数据量比较大,或者需要高效地计算矩形的面积交集,那么可以选择算法2。
测试用例:
1 namespace Solution { 2 using NUnit.Framework; 3 using System; 4 using System.Collections.Generic; 5 using System.Linq; 6 using static NUnit.Framework.Assert; 7 8 9 public class RandSort 10 { 11 public static IEnumerable<int[]> Shuffle(List<int[]> recs) 12 { 13 var rnd = new Random(); 14 var order = new List<double>(); 15 for (int i = 0; i < recs.Count; i++) { 16 order.Add(rnd.NextDouble()); 17 } 18 var orderArray = order.ToArray(); 19 var recsArray = recs.ToArray(); 20 Array.Sort(orderArray, recsArray); 21 return recsArray; 22 } 23 } 24 25 [TestFixture] 26 public class BasicTests 27 { 28 [Test] 29 public void ZeroRectangles() 30 { 31 AreEqual(0, Edm.Calculate(Enumerable.Empty<int[]>())); 32 } 33 34 [Test] 35 public void OneRectangle() 36 { 37 AreEqual(1, Edm.Calculate(new [] { new [] {0,0,1,1}})); 38 } 39 40 [Test] 41 public void OneRectangleV2() 42 { 43 AreEqual(22, Edm.Calculate(new [] { new [] {0, 4, 11, 6}})); 44 } 45 46 [Test] 47 public void TwoRectangles() 48 { 49 AreEqual(2, Edm.Calculate(new [] { new [] {0,0,1,1}, new [] {1,1,2,2}})); 50 } 51 52 [Test] 53 public void TwoRectanglesV2() 54 { 55 AreEqual(4, Edm.Calculate(new [] { new [] {0,0,1,1}, new [] {0,0,2,2}})); 56 } 57 58 [Test] 59 public void ThreeRectangles() 60 { 61 AreEqual(36, Edm.Calculate(new [] { new [] {3,3,8,5}, new [] {6,3,8,9}, new [] {11,6,14,12}})); 62 } 63 } 64 65 [TestFixture] 66 public class ExpandedTests 67 { 68 [Test] 69 public void RectanglesWithoutIntersections() 70 { 71 var recs = new [] { 72 new [] { 1, 1, 2, 2 }, 73 new [] { 2, 2, 3, 3 }, 74 new [] { 3, 3, 4, 4 }, 75 new [] { 4, 4, 5, 5 }, 76 new [] { 2, 1, 3, 2 } 77 }; 78 79 AreEqual(5, Edm.Calculate(recs)); 80 } 81 82 83 [Test] 84 public void RectanglesWithSimpleIntersections() 85 { 86 var recs = new [] { 87 new [] { 1, 1, 2, 2 }, 88 new [] { 1, 4, 2, 7 }, 89 new [] { 1, 4, 2, 6 }, 90 new [] { 1, 4, 4, 5 }, 91 new [] { 2, 5, 6, 7 }, 92 new [] { 4, 3, 7, 6 }, 93 }; 94 95 AreEqual(21, Edm.Calculate(recs)); 96 } 97 98 [Test] 99 public void RectanglesWithSimpleIntersectionsV2() 100 { 101 var recs = new [] { 102 new [] { 1, 3, 4, 5 }, 103 new [] { 2, 1, 4, 7 }, 104 new [] { 3, 4, 5, 6 }, 105 new [] { 6, 6, 8, 7 }, 106 new [] { 5, 3, 8, 4 }, 107 new [] { 6, 0, 7, 3 }, 108 }; 109 110 AreEqual(24, Edm.Calculate(recs)); 111 } 112 113 [Test] 114 public void DifficultCommonFaces() 115 { 116 var rnd = new Random(); 117 int stepX = rnd.Next(10,20); 118 int stepY = rnd.Next(10,20); 119 int startX = rnd.Next(0, 1000); 120 int startY = rnd.Next(0, 1000); 121 int count = rnd.Next(1000, 1500); 122 123 var recs = new List<int[]>(); 124 125 for (var i = 0; i < count; i++) 126 { 127 var x = startX + i * stepX; 128 var y = startY + i * stepY; 129 recs.Add(new [] { x, y, x + 1, y + 1 }); 130 recs.Add(new [] { x + 1, y, x + 3, y + 2 }); 131 recs.Add(new [] { x, y + 2, x + 3, y + 3 }); 132 recs.Add(new [] { x + 3, y, x + 4, y + 3 }); 133 recs.Add(new [] { x + 2, y + 3, x + 4, y + 5 }); 134 } 135 136 var recsArray = RandSort.Shuffle(recs); 137 AreEqual(15 * count, Edm.Calculate(recsArray)); 138 } 139 140 141 [Test] 142 public void DifficultLocatedFarAway() 143 { 144 var rnd = new Random(); 145 int stepX = rnd.Next(1000,2000); 146 int stepY = rnd.Next(1000,2000); 147 int startX = rnd.Next(0, 1000); 148 int startY = rnd.Next(0, 1000); 149 int count = rnd.Next(1000, 1500); 150 151 var recs = new List<int[]>(); 152 153 for (var i = 0; i < count; i++) 154 { 155 var x = startX + i * stepX; 156 var y = startY + i * stepY; 157 recs.Add(new [] { x, y, x + 202, y + 300 }); 158 recs.Add(new [] { x + 100, y + 500, x + 500, y + 765 }); 159 recs.Add(new [] { x + 150, y + 330, x + 170, y + 360 }); 160 } 161 162 var recsArray = RandSort.Shuffle(recs); 163 AreEqual(167200 * count, Edm.Calculate(recsArray)); 164 } 165 166 167 [Test] 168 public void DifficultNestedRectangles() 169 { 170 var rnd = new Random(); 171 int stepX = rnd.Next(10,200); 172 int stepY = rnd.Next(10,200); 173 int startX = rnd.Next(0, 1000); 174 int startY = rnd.Next(0, 1000); 175 int count = rnd.Next(1000, 1500); 176 177 var recs = new List<int[]>(); 178 179 for (var i = 0; i < count; i++) 180 { 181 var x = startX + i * stepX; 182 var y = startY + i * stepY; 183 184 recs.Add(new [] { x, y, x + 1, y + 1 }); 185 recs.Add(new [] { x, y, x + 1, y + 3 }); 186 recs.Add(new [] { x, y + 1, x + 3, y + 2 }); 187 recs.Add(new [] { x, y + 3, x + 4, y + 4 }); 188 recs.Add(new [] { x + 2, y, x + 6, y + 2 }); 189 recs.Add(new [] { x + 3, y + 3, x + 6, y + 5 }); 190 } 191 192 var recsArray = RandSort.Shuffle(recs); 193 AreEqual(21 * count, Edm.Calculate(recsArray)); 194 } 195 196 197 [Test] 198 public void DifficultRectanglesWithLotsOfIntersections() 199 { 200 var rnd = new Random(); 201 int stepX = rnd.Next(10,200); 202 int stepY = rnd.Next(10,200); 203 int startX = rnd.Next(0, 1000); 204 int startY = rnd.Next(0, 1000); 205 int count = rnd.Next(1000, 1500); 206 207 var recs = new List<int[]>(); 208 209 for (var i = 0; i < count; i++) 210 { 211 var x = startX + i * stepX; 212 var y = startY + i * stepY; 213 214 recs.Add(new [] { x, y + 2, x + 2, y + 4 }); 215 recs.Add(new [] { x + 1, y + 3, x + 3, y + 5 }); 216 recs.Add(new [] { x + 1, y + 1, x + 3, y + 3 }); 217 recs.Add(new [] { x + 7, y + 3, x + 8, y + 4 }); 218 recs.Add(new [] { x + 8, y + 2, x + 9, y + 7 }); 219 recs.Add(new [] { x + 6, y + 2, x + 9, y + 7 }); 220 recs.Add(new [] { x + 3, y + 5, x + 10,y + 6 }); 221 recs.Add(new [] { x + 3, y + 2, x + 6, y + 3 }); 222 recs.Add(new [] { x + 2, y + 4, x + 4, y + 7 }); 223 recs.Add(new [] { x + 9, y, x + 10,y + 3 }); 224 } 225 226 var recsArray = RandSort.Shuffle(recs); 227 AreEqual(39 * count, Edm.Calculate(recsArray)); 228 } 229 230 231 [Test] 232 public void DifficultRectanglesWithLongSides() 233 { 234 var rnd = new Random(); 235 int stepX = rnd.Next(100000,111000); 236 int stepY = rnd.Next(100000,111000); 237 int startX = rnd.Next(0, 1000); 238 int startY = rnd.Next(0, 1000); 239 int count = rnd.Next(1000, 1500); 240 241 var recs = new List<int[]>(); 242 243 for (var i = 0; i < count; i++) 244 { 245 var x = startX + i * stepX; 246 var y = startY + i * stepY; 247 248 recs.Add(new [] { x, y, x + 30000, y + 1 }); 249 recs.Add(new [] { x, y + 1, x + 1, y + 30001 }); 250 recs.Add(new [] { x + 30000, y + 1, x + 30001, y + 30001 }); 251 } 252 253 var recsArray = RandSort.Shuffle(recs); 254 AreEqual(90000 * count, Edm.Calculate(recsArray)); 255 } 256 257 258 [Test] 259 public void DifficultRectanglesWithCommonFacesV2() 260 { 261 var rnd = new Random(); 262 int stepX = 0; //rnd.Next(100000,111000); 263 int stepY = rnd.Next(10,200); 264 int startX = rnd.Next(0, 1000); 265 int startY = rnd.Next(0, 1000); 266 int count = rnd.Next(1000, 1500); 267 268 var recs = new List<int[]>(); 269 270 for (var i = 0; i < count; i++) 271 { 272 var x = startX + i * stepX; 273 var y = startY + i * stepY; 274 275 recs.Add(new [] { x, y, x + 1, y + 1 }); 276 recs.Add(new [] { x + 1, y, x + 3, y + 2 }); 277 recs.Add(new [] { x, y + 2, x + 3, y + 3 }); 278 recs.Add(new [] { x + 3, y, x + 4, y + 3 }); 279 recs.Add(new [] { x + 2, y + 3, x + 4, y + 5 }); 280 } 281 282 var recsArray = RandSort.Shuffle(recs); 283 AreEqual(15 * count, Edm.Calculate(recsArray)); 284 } 285 286 287 [Test] 288 public void DifficultRectanglesLocatedFarAwayV2() 289 { 290 var rnd = new Random(); 291 int stepX = 0; //rnd.Next(100000,111000); 292 int stepY = rnd.Next(1000,2000); 293 int startX = rnd.Next(0, 1100); 294 int startY = rnd.Next(0, 1100); 295 int count = rnd.Next(1000, 1500); 296 297 var recs = new List<int[]>(); 298 299 for (var i = 0; i < count; i++) 300 { 301 var x = startX + i * stepX; 302 var y = startY + i * stepY; 303 304 recs.Add(new [] { x, y, x + 202, y + 300 }); 305 recs.Add(new [] { x + 100, y + 500, x + 500, y + 765 }); 306 recs.Add(new [] { x + 150, y + 330, x + 170, y + 360 }); 307 } 308 309 var recsArray = RandSort.Shuffle(recs); 310 AreEqual(167200 * count, Edm.Calculate(recsArray)); 311 } 312 313 314 [Test] 315 public void DifficultNestedRectanglesV2() 316 { 317 var rnd = new Random(); 318 int stepX = 0; //rnd.Next(100000,111000); 319 int stepY = rnd.Next(10,200); 320 int startX = rnd.Next(0, 1100); 321 int startY = rnd.Next(0, 1100); 322 int count = rnd.Next(1000, 1500); 323 324 var recs = new List<int[]>(); 325 326 for (var i = 0; i < count; i++) 327 { 328 var x = startX + i * stepX; 329 var y = startY + i * stepY; 330 331 recs.Add(new [] { x, y, x + 1, y + 1 }); 332 recs.Add(new [] { x, y, x + 1, y + 3 }); 333 recs.Add(new [] { x, y + 1, x + 3, y + 2 }); 334 recs.Add(new [] { x, y + 3, x + 4, y + 4 }); 335 recs.Add(new [] { x + 2, y, x + 6, y + 2 }); 336 recs.Add(new [] { x + 3, y + 3, x + 6, y + 5 }); 337 } 338 339 var recsArray = RandSort.Shuffle(recs); 340 AreEqual(21 * count, Edm.Calculate(recsArray)); 341 } 342 343 [Test] 344 public void DifficultRectanglesWithLotsOfIntersectionsV2() 345 { 346 var rnd = new Random(); 347 int stepX = 0; //rnd.Next(100000,111000); 348 int stepY = rnd.Next(10,200); 349 int startX = rnd.Next(0, 1100); 350 int startY = rnd.Next(0, 1100); 351 int count = rnd.Next(1000, 1500); 352 353 var recs = new List<int[]>(); 354 355 for (var i = 0; i < count; i++) 356 { 357 var x = startX + i * stepX; 358 var y = startY + i * stepY; 359 360 recs.Add(new [] { x, y + 2, x + 2, y + 4 }); 361 recs.Add(new [] { x + 1, y + 3, x + 3, y + 5 }); 362 recs.Add(new [] { x + 1, y + 1, x + 3, y + 3 }); 363 recs.Add(new [] { x + 7, y + 3, x + 8, y + 4 }); 364 recs.Add(new [] { x + 8, y + 2, x + 9, y + 7 }); 365 recs.Add(new [] { x + 6, y + 2, x + 9, y + 7 }); 366 recs.Add(new [] { x + 3, y + 5, x + 10,y + 6 }); 367 recs.Add(new [] { x + 3, y + 2, x + 6, y + 3 }); 368 recs.Add(new [] { x + 2, y + 4, x + 4, y + 7 }); 369 recs.Add(new [] { x + 9, y, x + 10,y + 3 }); 370 } 371 372 var recsArray = RandSort.Shuffle(recs); 373 AreEqual(39 * count, Edm.Calculate(recsArray)); 374 } 375 376 377 [Test] 378 public void DifficultRectanglesWithLongSidesV2() 379 { 380 var rnd = new Random(); 381 int stepX = 0; //rnd.Next(100000,111000); 382 int stepY = rnd.Next(100000,111200); 383 int startX = rnd.Next(0, 1100); 384 int startY = rnd.Next(0, 1100); 385 int count = rnd.Next(1000, 1500); 386 387 var recs = new List<int[]>(); 388 389 for (var i = 0; i < count; i++) 390 { 391 var x = startX + i * stepX; 392 var y = startY + i * stepY; 393 394 recs.Add(new [] { x, y, x + 30000, y + 1 }); 395 recs.Add(new [] { x, y + 1, x + 1, y + 30001 }); 396 recs.Add(new [] { x + 30000, y + 1, x + 30001, y + 30001 }); 397 } 398 399 var recsArray = RandSort.Shuffle(recs); 400 AreEqual(90000 * count, Edm.Calculate(recsArray)); 401 } 402 403 private static long Solve(IEnumerable<int[]> rectangles) 404 { 405 if (!rectangles.Any()) return 0; 406 rectangles = rectangles.OrderBy(r => r[0]).ToList(); 407 var xs = rectangles.Select(r=>r[0]).Concat(rectangles.Select(r=>r[2])).Distinct().OrderBy(x=>x).ToList(); 408 var scan = new List<int[]>(); 409 // long recIndex = 0; 410 long area = 0; 411 long scanLeft = xs[0]; 412 xs.RemoveAt(0); 413 using(var recsEnum = rectangles.GetEnumerator()) 414 { 415 bool hasMoreRec = recsEnum.MoveNext(); 416 417 xs.ForEach(scanRight => 418 { 419 // add rectangles to scan that align on scan left... 420 for(;hasMoreRec && recsEnum.Current[0] == scanLeft; hasMoreRec = recsEnum.MoveNext()) 421 { 422 scan.Add(recsEnum.Current); 423 } 424 425 scan.Sort((a,b) => a[1] - b[1]); // order by top 426 long height = 0; 427 long lastY = long.MinValue; // last y accounted for in height 428 scan.ForEach(s => 429 { 430 long top = s[1]; 431 long bottom = s[3]; 432 if (lastY < bottom) // overlaps, add height of overlapping portion 433 { 434 height += bottom - Math.Max(lastY, top); 435 lastY = bottom; 436 } 437 }); 438 439 // area of rectangles that overlap scan: height * width 440 area += height * (scanRight - scanLeft); 441 442 // proceding left-to-right, so remove the scan rectangles whose right-side is to the left of current scan 443 scan.RemoveAll(r=>r[2] <= scanRight); 444 scanLeft = scanRight; 445 }); 446 } 447 448 return area; 449 } 450 451 [Test] 452 public void DifficultRandomTests() 453 { 454 const int scale = 100000; 455 var rnd = new Random(); 456 for(int i=0; i<25; i++) 457 { 458 int sx=rnd.Next(0,scale); 459 int sy=rnd.Next(0,scale); 460 var recs = Enumerable.Range(0,rnd.Next(300,500)).Select(_ => new [] { sx, sy, sx + rnd.Next(0,scale), sy + rnd.Next(0, scale) }).ToArray(); 461 var expected = Solve(recs); 462 var actual = Edm.Calculate(recs); 463 AreEqual(expected, actual); 464 } 465 } 466 } 467 }