【演算法】十一月陽光下的陰影面積

lanedm發表於2023-11-03

十一月的陽光透過窗戶,照射在一位笑起來甜美、青春洋溢的女子的辦公桌上。小悅,一個總是以高馬尾造型亮相的軟體工程師,展現出她的幹練與活力。那烏黑亮麗的長髮輕盈飄動,彷彿在訴說著她的獨特魅力。她的眉眼如畫,那雙明亮的眼睛裡閃爍著對知識的渴望和對技術挑戰的熱情。

這一天,她收到了一封來自醫院的郵件,郵件中提到的掃描裝置技術更新問題讓她感到有些挑戰。然而,對於技術挑戰,小悅總是充滿了好奇心和熱情。她決定主動聯絡醫院,表達自己願意參與這個專案的意願。幸運的是,醫院方面很快回復了她的郵件,並安排了一次電話會議。

在電話會議中,小悅與醫院的管理人員和相關領域的專家進行了交流。他們的聲音充滿了對新技術和新思維的渴望。小悅也意識到這次機會對於自己的事業發展可能是一個重大的突破。

不久之後,小悅成功加入了醫院方面的團隊。在團隊中,她發現了很多優秀的專家和工程師。其中有一位資深醫生對掃描裝置的需求非常瞭解,而另一位工程師則對影像處理演算法有深入的研究。小悅深知自己在這個團隊中擔任著關鍵的角色,必須充分發揮自己的優勢。

在團隊的合作中,小悅與大家建立了良好的合作關係。她不斷地與團隊成員溝通和交流,瞭解他們的需求和想法。她的聲音總是溫柔而自信,讓人感到安心和信任。同時,她也向團隊成員分享了自己的經驗和專業知識,為專案的進展做出了巨大的貢獻。

然而,隨著專案的深入,小悅逐漸發現了一些技術難題。其中最大的問題是如何計算陰影部分的面積並集。為了解決這個問題,她不斷地查閱文獻、研究演算法,並嘗試了多種方法。有時候,她會陷入深深的困境,甚至整晚都無法入睡。但是,她從未放棄過對這個問題的探索和解決。

經過一段時間的努力,小悅終於提出了一種有效的方法來計算陰影部分的面積並集。這個方法不僅得到了團隊的認可和支援,也成功地解決了專案中的一大難題。小悅的貢獻讓整個團隊都感到非常驚喜和敬佩。

在接下來的時間中,小悅和團隊繼續努力工作,終於成功地開發出了一款新的掃描裝置程式設計。這款程式採用了小悅提出的陰影面積計算方法,有效地提高了掃描的準確性和效率。醫院方面對這款程式非常滿意,並決定將其投入使用。

在這個過程中,小悅學到了很多東西。她不僅在技術方面取得了突破和成長,還學會了如何與不同背景的人合作和溝通。她深刻地認識到人際關係的重要性以及如何利用人際關係來擴充自己的視野和能力。同時,她也體驗到了幫助他人的喜悅和成就感。


小悅面臨的問題是,醫學影像重建:醫學影像(如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年引入,用於在計算機圖形學中進行空間分割和區域查詢。八叉樹的名字來源於其樹狀結構,每個節點有八個子節點,對應於三維空間中的八個象限。

隨後,八叉樹在計算機視覺領域得到廣泛應用,用於處理影像和空間資料。例如,八叉樹可以用於影像壓縮、影像搜尋、碰撞檢測等應用中。

隨著計算機硬體和演算法的發展,八叉樹的變種和改進也被提出。例如,四叉樹是八叉樹的二維版本,用於處理二維空間資料。此外,還有基於八叉樹的自適應分割方法和多解析度表示方法等。

八叉樹演算法的應用領域不僅限於計算機圖形學和計算機視覺,還可以用於地理資訊系統、醫學影像處理、物理模擬等領域。它提供了一種高效的資料結構和演算法,可以用於處理多維空間資料和進行空間查詢。

  1. Rect結構體表示一個矩形物件,其中包含矩形的左、右、上、下邊界,以及計算矩形面積和判斷矩形是否存在的屬性。

  2. Rect結構體還定義了一個intersects方法,用於判斷兩個矩形是否相交,並返回相交部分的矩形物件。

  3. Rect結構體還定義了一個octants方法,用於將當前矩形分成八個象限,並返回八個象限的矩形物件陣列。

  4. OctTreeNode類表示一個八叉樹節點,每個節點代表一個矩形。該類包含一個origin變數表示當前節點所代表的矩形,以及一個oct_bounds陣列表示當前節點所代表的矩形被分成的八個象限,以及一個oct_values陣列表示八個象限所代表的子節點。

  5. OctTreeNode類定義了一個insert方法,用於向當前節點插入一個新的矩形物件。該方法透過遍歷八個象限的矩形物件,判斷當前矩形與遍歷到的矩形是否相交,如果相交則遞迴呼叫insert方法將相交部分的矩形插入到對應的子節點中。

  6. OctTreeNode類還定義了一個area屬性,表示當前節點所代表的矩形的面積加上所有子節點所代表的矩形的面積之和。

  7. 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 }

 

相關文章