Effiziente Fill Operation durchführen

  • C#
  • .NET (FX) 4.5–4.8

Es gibt 8 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

    Effiziente Fill Operation durchführen

    Guten Abend,
    ich entwickle für das Community-Projekt ( Minecraft-Klon Monogame) ein See Biom.

    Dafür schrieb ich ein Algorithmus um die maximale See-Höhe eines Chunks zu bestimmen:

    C#-Quellcode

    1. List<int> Heights = new List<int>();
    2. int[,] IntMap;
    3. private void FindSea()
    4. {
    5. Heights = Heights.OrderBy(p => p).ToList();
    6. int MinCounter = 0;
    7. int MinHeight = 0;
    8. IntMap = new int[HeightMap.GetUpperBound(0), HeightMap.GetUpperBound(1)];
    9. // 1 = Border
    10. // 0 = Neutral
    11. // 2 = Flood
    12. int Width = IntMap.GetUpperBound(0) + 1;
    13. int Height = IntMap.GetUpperBound(1) + 1;
    14. while (MinCounter < 5)
    15. {
    16. MinHeight = Heights[MinCounter];
    17. for (int i = 0; i < HeightMap.GetUpperBound(0); i++)
    18. {
    19. for (int j = 0; j < HeightMap.GetUpperBound(1); j++)
    20. {
    21. bool left = false;
    22. bool right = false;
    23. bool top = false;
    24. bool bot = false;
    25. if (i - 1 >= 0)
    26. left = (int)HeightMap[i - 1, j] == MinHeight;
    27. else left = true;
    28. if (i + 1 < IntMap.GetUpperBound(0))
    29. right = (int)HeightMap[i + 1, j] == MinHeight;
    30. else right = true;
    31. if (j - 1 >= 0)
    32. top = (int)HeightMap[i, j - 1] == MinHeight;
    33. else top = true;
    34. if (j + 1 < IntMap.GetUpperBound(0))
    35. bot = (int)HeightMap[i, j + 1] == MinHeight;
    36. else bot = true;
    37. if ((int)HeightMap[i, j] == MinHeight)
    38. {
    39. if (!left || !right || !top || !bot)
    40. IntMap[i, j] = 1;
    41. }
    42. }
    43. }
    44. //CHECK WHETER BORDERS ARE CONNECTED.
    45. int Counter = 0;
    46. Point[] OffenseField = new Point[Width * Height + 65];
    47. // ADD INITIAL OFFENSE FIELDS
    48. for (int i = 0; i < Width; i++)
    49. {
    50. if (IntMap[i, 0] != 1)
    51. OffenseField[Counter++] = new Point(i, 0);
    52. }
    53. for (int i = 0; i < Width; i++)
    54. {
    55. if (IntMap[i, Height - 1] != 1)
    56. OffenseField[Counter++] = new Point(i, Height - 1);
    57. }
    58. for (int j = 0; j < Height; j++)
    59. {
    60. if (IntMap[0, j] != 1)
    61. OffenseField[Counter++] = new Point(0, j);
    62. }
    63. for (int j = 0; j < Height; j++)
    64. {
    65. if (IntMap[Width - 1, j] != 1)
    66. OffenseField[Counter++] = new Point(Width - 1, j);
    67. }
    68. for (int i = 0; i < Counter; i++)
    69. {
    70. Point mid = OffenseField[i];
    71. Point left = new Point();
    72. Point right = new Point();
    73. Point top = new Point();
    74. Point bot = new Point();
    75. if (mid.X - 1 >= 0 && IntMap[mid.X - 1, mid.Y] != 1)
    76. left = new Point(mid.X - 1, mid.Y);
    77. if (mid.X + 1 < Width && IntMap[mid.X + 1, mid.Y] != 1)
    78. right = new Point(mid.X + 1, mid.Y);
    79. if (mid.Y - 1 >= 0 && IntMap[mid.X, mid.Y - 1] != 1)
    80. top = new Point(mid.X, mid.Y - 1);
    81. if (mid.Y + 1 < Height && IntMap[mid.X, mid.Y + 1] != 1)
    82. bot = new Point(mid.X, mid.Y + 1);
    83. if (!OffenseField.Contains(left))
    84. OffenseField[Counter++] = left;
    85. if (!OffenseField.Contains(right))
    86. OffenseField[Counter++] = right;
    87. if (!OffenseField.Contains(top))
    88. OffenseField[Counter++] = top;
    89. if (!OffenseField.Contains(bot))
    90. OffenseField[Counter++] = bot;
    91. if (IntMap[mid.X, mid.Y] != 1)
    92. IntMap[mid.X, mid.Y] = 2;
    93. }
    94. int CounterField = 0;
    95. int CounterRed = 0;
    96. int CounterBorder = 0;
    97. for (int i = 0; i < HeightMap.GetUpperBound(0); i++)
    98. {
    99. for (int j = 0; j < HeightMap.GetUpperBound(1); j++)
    100. {
    101. if (IntMap[i, j] == 1)
    102. {
    103. int left = -1;
    104. int right = -1;
    105. int top = -1;
    106. int bot = -1;
    107. if (i - 1 >= 0)
    108. left = IntMap[i - 1, j];
    109. if (i + 1 < Width)
    110. right = IntMap[i + 1, j];
    111. if (j - 1 >= 0)
    112. top = IntMap[i, j - 1];
    113. if (j + 1 < Height)
    114. bot = IntMap[i, j + 1];
    115. if (bot == 0)
    116. CounterField++;
    117. if (top == 0)
    118. CounterField++;
    119. if (right == 0)
    120. CounterField++;
    121. if (left == 0)
    122. CounterField++;
    123. if (bot == 2)
    124. CounterRed++;
    125. if (top == 2)
    126. CounterRed++;
    127. if (right == 2)
    128. CounterRed++;
    129. if (left == 2)
    130. CounterRed++;
    131. if (CounterRed == 0 && CounterField > 0)
    132. IntMap[i, j] = 0;
    133. if (CounterRed > 0 && CounterField > 0)
    134. IntMap[i, j] = 1;
    135. if (CounterRed > 0 && CounterField == 0)
    136. IntMap[i, j] = 2;
    137. CounterField = 0;
    138. CounterRed = 0;
    139. }
    140. if (IntMap[i, j] == 0)
    141. CounterBorder++;
    142. }
    143. }
    144. if (CounterBorder > 0)
    145. {
    146. SeaHeight = Heights[MinCounter];
    147. break;
    148. }
    149. else
    150. MinCounter++;
    151. }
    152. }


    Sieht imho widerlich aus.

    Es versucht ein geschlossenes System festzustellen... das erreicht es dadurch, dass er ein ganzes Array füllt .
    Dabei werden natürlich die Koordinaten bewahrt, die von Obstakel umkreist werden.

    Das Füllen geschieht so:

    C#-Quellcode

    1. //CHECK WHETER BORDERS ARE CONNECTED.
    2. int Counter = 0;
    3. Point[] OffenseField = new Point[Width * Height + 65];
    4. // ADD INITIAL OFFENSE FIELDS
    5. for (int i = 0; i < Width; i++)
    6. {
    7. if (IntMap[i, 0] != 1)
    8. OffenseField[Counter++] = new Point(i, 0);
    9. }
    10. for (int i = 0; i < Width; i++)
    11. {
    12. if (IntMap[i, Height - 1] != 1)
    13. OffenseField[Counter++] = new Point(i, Height - 1);
    14. }
    15. for (int j = 0; j < Height; j++)
    16. {
    17. if (IntMap[0, j] != 1)
    18. OffenseField[Counter++] = new Point(0, j);
    19. }
    20. for (int j = 0; j < Height; j++)
    21. {
    22. if (IntMap[Width - 1, j] != 1)
    23. OffenseField[Counter++] = new Point(Width - 1, j);
    24. }
    25. for (int i = 0; i < Counter; i++)
    26. {
    27. Point mid = OffenseField[i];
    28. Point left = new Point();
    29. Point right = new Point();
    30. Point top = new Point();
    31. Point bot = new Point();
    32. if (mid.X - 1 >= 0 && IntMap[mid.X - 1, mid.Y] != 1)
    33. left = new Point(mid.X - 1, mid.Y);
    34. if (mid.X + 1 < Width && IntMap[mid.X + 1, mid.Y] != 1)
    35. right = new Point(mid.X + 1, mid.Y);
    36. if (mid.Y - 1 >= 0 && IntMap[mid.X, mid.Y - 1] != 1)
    37. top = new Point(mid.X, mid.Y - 1);
    38. if (mid.Y + 1 < Height && IntMap[mid.X, mid.Y + 1] != 1)
    39. bot = new Point(mid.X, mid.Y + 1);
    40. if (!OffenseField.Contains(left))
    41. OffenseField[Counter++] = left;
    42. if (!OffenseField.Contains(right))
    43. OffenseField[Counter++] = right;
    44. if (!OffenseField.Contains(top))
    45. OffenseField[Counter++] = top;
    46. if (!OffenseField.Contains(bot))
    47. OffenseField[Counter++] = bot;
    48. if (IntMap[mid.X, mid.Y] != 1)
    49. IntMap[mid.X, mid.Y] = 2;
    50. }


    Ich generiere ein Array willkürlicher Größe( Width * Height + 65 ) und erzeuge ein 3x3 Pattern mit "Top", "Bot", "Left", "Right"... wenn diese nicht Obstakel sind, werden sie gefüllt... dauert nur lange.

    Gibt es einen effizienteren Füll Algorithmus?
    Lieben Dank!


    Post scriptum: Es funktioniert schon so ...(siehe Anhang) soll jedoch auch in 500x500 großen Arrays in unter 1 Sekunde einen See finden.
    Bilder
    • mc_sea.png

      1,34 MB, 1.920×1.080, 104 mal angesehen
    • seafinder.png

      6,32 kB, 756×538, 103 mal angesehen
    Und Gott alleine weiß alles am allerbesten und besser.

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „φConst“ ()

    Nevermid, habe ein HashSet benutzt, jetzt funktioniert es perfekt!

    In einem 582x475 großem Array wird in unter 2 Sekunden ein See gefunden!

    Pff, lol, falscher Alarm, doch nicht.
    Und Gott alleine weiß alles am allerbesten und besser.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „φConst“ ()

    φConst schrieb:

    Gibt es einen effizienteren Füll Algorithmus?
    Höchstwahrscheinlich.

    Aber ich hab noch keinen Plan, wie dein bisheriger Algo funktioniert, nicht mal, was der eiglich machen soll.

    Kannst du nicht einen brauchbaren Methoden-Rumpf angeben, mit Parametern und Return-Value?
    Sowas definiert in klarer Weise (anhand der Datentypen), was hineingeht, und was bei rauskommen soll.

    C#-Quellcode

    1. HashSet<Point> OffenseField = new HashSet<Point>();
    2. for (int i = 0; i < OffenseField.Count; i++)
    3. {
    4. Point mid = OffenseField.ElementAt(i);
    5. Point left = new Point();
    6. Point right = new Point();
    7. Point top = new Point();
    8. Point bot = new Point();
    9. if (mid.X - 1 >= 0 && IntMap[mid.X - 1, mid.Y] != 1)
    10. left = new Point(mid.X - 1, mid.Y);
    11. if (mid.X + 1 < Width && IntMap[mid.X + 1, mid.Y] != 1)
    12. right = new Point(mid.X + 1, mid.Y);
    13. if (mid.Y - 1 >= 0 && IntMap[mid.X, mid.Y - 1] != 1)
    14. top = new Point(mid.X, mid.Y - 1);
    15. if (mid.Y + 1 < Height && IntMap[mid.X, mid.Y + 1] != 1)
    16. bot = new Point(mid.X, mid.Y + 1);
    17. if (!OffenseField.Contains(left))
    18. OffenseField.Add(left);
    19. if (!OffenseField.Contains(right))
    20. OffenseField.Add(right);
    21. if (!OffenseField.Contains(top))
    22. OffenseField.Add(top);
    23. if (!OffenseField.Contains(bot))
    24. OffenseField.Add(bot);
    25. IntMap[mid.X, mid.Y] = 2;
    26. }


    Diese Sektion der Methode füllt das Array.

    Felder die von Obstakel umkreist werden können nicht gefüllt werden.
    Wenn nun ein Feld nach der Füllung noch "klar" ist, also "leer", so ist die Höhe die dem Obstakel X zugewiesen ist, eine ideale Höhe für einen See...

    Das ist auch egal, ich suche nur nach einer effizienteren Variante eines Füll-Algorithmus'

    Zurzeit funktioniert es folgendergestalt:

    Gegeben sei eine Liste mit dem Typen Point.
    Der Liste werden Initial-Werte ( Initial-Angriffs-Punkte) hinzugefügt.

    Nun wird in einer For-Schleife Top, Bot, Left, Right - sofern eines dieser nicht Obstakel ist - in die Liste abgebildet... dies geschieht solange bis eben das ganze Array gefüllt ist.
    Problem: Anzahl der Elemente einer Liste steigt exponentiell, die Methode Contains ist für solche Größen nicht optimiert..

    Ich suche nach einer effizienteren Variante.
    Und Gott alleine weiß alles am allerbesten und besser.
    Ups - sorry - ich war etwas ungenau. Ich meinte nicht Methoden-Rumpf, sondern Methoden-Signatur.
    Also nicht den Inhalt der Methode, sondern nur ihre Deklaration.
    Dort wird ja bestimmt, was reingeht, und was zurückkommt.

    Aus diesem Code-Abschnitt ohne Anfang und Ende kann man nix weiter-entwickeln.
    Ausserdem ist dein wirklicher Code mit Sicherheit anders, denn hier zeigst du nichts von der Initial-Befüllung.
    Also der Code wie hier gezeigt würde die Schleife ühaupt nicht betreten.

    Ich sehe auch kein Array, was befüllt wird, und die gezeigte Contains-Methode - nämlich Hashset.Contains() ist sehr wohl für beliebig grosse Größen optimiert.
    Was hier suboptimal ist, ist die Hashset.ElementAt(i) - Extension.

    Und eine Liste kommt im Code auch nicht vor, deren Elemente exponentiell ansteigen würden.

    Und wie gesagt das Ziel der Veranstaltung ist mir unklar. Soll in ausgewählte IntMap-Felder 2 eingetragen werden - ist das das Ziel?

    (ich weiß auch nicht, was ein Obstakel ist, und wie man mit obigem Code rausfindet, um welches Feld - damit sind die IntMap-Felder gemeint? - son Obstakel herumkreist)
    hast du eine Füllhöhe, mit welcher gefüllt werden soll, oder ein Volumen? Volumen ist natürlich komplizierter.

    Mir fällt spontan ein Ansatz über OctTrees ein:
    Am jeweiligen Node werden nur die Ränder überprüft um somit ganze Nodes auszuschließen, oder eben nicht. Dann natürlich immer in niedrigeren Ebenen weiter suchen.

    Ist nur eine Grundsätzliche Idee, nie probiert und keine Ahnung wie effizient das nachher wäre.
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Eigentlich eine gute Idee.... oh man!
    Du hast recht( falls du das meinst)!
    Ich versuche einfach das Innere eines Umkreises zu füllen... wenn Feld X ein Obstakel ist, dann ist der Initial-Angriffspunkt bei Feld X.
    Wenn es nun ein Umkreis ist, dann hat jedes Feld Y das gefüllt ist, einen Feld X .

    Mit anderen Worten: Statt das ganze Array zu füllen, fülle ich nur die Bereiche im Obstakel.
    Und Gott alleine weiß alles am allerbesten und besser.
    Hat echt geklappt!!!!!

    C#-Quellcode

    1. List<int> Heights = new List<int>();
    2. int[,] IntMap;
    3. private void FindSea()
    4. {
    5. Heights = Heights.OrderBy(p => p).ToList();
    6. int MinCounter = 0 ;
    7. int MinHeight = 0;
    8. IntMap = new int[HeightMap.GetUpperBound(0) + 1, HeightMap.GetUpperBound(1) + 1];
    9. // 1 = Border
    10. // 0 = Neutral
    11. // 2 = Flood
    12. int Width = IntMap.GetUpperBound(0) + 1;
    13. int Height = IntMap.GetUpperBound(1) + 1;
    14. List<Point> Obstacles = new List<Point>();
    15. while (true)
    16. {
    17. MinHeight = Heights[MinCounter];
    18. for (int i = 0; i < HeightMap.GetUpperBound(0) + 1; i++)
    19. {
    20. for (int j = 0; j < HeightMap.GetUpperBound(1) + 1; j++)
    21. {
    22. bool left = true;
    23. bool right = true;
    24. bool top = true;
    25. bool bot = true;
    26. if (i - 1 >= 0)
    27. left = (int)HeightMap[i - 1, j] <= MinHeight;
    28. if (i + 1 < IntMap.GetUpperBound(0))
    29. right = (int)HeightMap[i + 1, j] <= MinHeight;
    30. if (j - 1 >= 0)
    31. top = (int)HeightMap[i, j - 1] <= MinHeight;
    32. if (j + 1 < IntMap.GetUpperBound(0))
    33. bot = (int)HeightMap[i, j + 1] <= MinHeight;
    34. if ((int)HeightMap[i, j] == MinHeight)
    35. {
    36. if (!left || !right || !top || !bot)
    37. {
    38. IntMap[i, j] = 1;
    39. Obstacles.Add(new Point(i, j));
    40. }
    41. }
    42. }
    43. }
    44. // CHECK WHETER BORDERS ARE CONNECTED.
    45. for (int i = 0; i < IntMap.GetUpperBound(0) + 1; i++)
    46. {
    47. for (int j = 0; j < IntMap.GetUpperBound(1) + 1; j++)
    48. {
    49. if (IntMap[i, j] != 1)
    50. {
    51. // COUNT
    52. if (Obstacles.Count(p => p.X == i) > 1 && Obstacles.Count(p => p.Y == j) > 1)
    53. IntMap[i, j] = 2;
    54. }
    55. }
    56. }
    57. // CLEAR
    58. int Counter = 0;
    59. int MinY = Obstacles.OrderBy(p => p.Y).ToList()[0].Y;
    60. int MaxY = Obstacles.OrderBy(p => p.Y).ToList()[Obstacles.Count - 1].Y;
    61. for (int k = 0; k < MaxY - MinY - 2; k++)
    62. {
    63. for (int i = 0; i < IntMap.GetUpperBound(0) + 1; i++)
    64. {
    65. for (int j = 0; j < IntMap.GetUpperBound(1) + 1; j++)
    66. {
    67. if (IntMap[i, j] == 2)
    68. {
    69. int left = -1;
    70. int right = -1;
    71. int top = -1;
    72. int bot = -1;
    73. if (i - 1 >= 0)
    74. left = IntMap[i - 1, j];
    75. if (i + 1 < Width)
    76. right = IntMap[i + 1, j];
    77. if (j - 1 >= 0)
    78. top = IntMap[i, j - 1];
    79. if (j + 1 < Height)
    80. bot = IntMap[i, j + 1];
    81. if (bot == 0)
    82. Counter++;
    83. if (top == 0)
    84. Counter++;
    85. if (right == 0)
    86. Counter++;
    87. if (left == 0)
    88. Counter++;
    89. if (Counter > 0)
    90. IntMap[i, j] = 0;
    91. }
    92. Counter = 0;
    93. }
    94. }
    95. }
    96. break;
    97. }
    98. }


    Der Algorithmus ist verdammt schnell!

    Noch eine Frage: Gibt es eine effizientere Methode zum Zählen spezifischer Elemente einer Liste statt .Count(a => a == b)?
    Bilder
    • seafinder_improved.png

      11,77 kB, 1.508×537, 92 mal angesehen
    Und Gott alleine weiß alles am allerbesten und besser.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „φConst“ ()

    φConst schrieb:

    Gibt es eine effizientere Methode zum Zählen spezifischer Elemente einer Liste statt .Count(a => a == b)?
    Das ist eiglich schon ziemlich effizient - oder hast du wirklich Probleme damit?
    Ansonsten, wenn die Liste sortiert ist, kann man evtl. was mit BinärSuche machen - ist auch schon für Array und List(Of T) im Framework enthalten.