Angepinnt [Sammelthread] Knobel-Aufgaben, knifflige Algorithmen, elegante Lösungen

    • VB.NET

    Es gibt 178 Antworten in diesem Thema. Der letzte Beitrag () ist von Thunderbolt.

      Ich habe den Beitrag zur Verständnis des ersten Punktes bearbeitet.

      Angrenzende Flächen: Flächen, die eine unterschiedliche Nummer haben. Da field[x] = 0 gilt, sind die Flächen mit 1, 2, 3 in der potentiellen Auswahl.
      Es sollen diejenigen Punkte pro Zahl zurückgegeben werden, die am Nächsten sind.
      Beispielsweise müsste für 2 der Punkt rechts unterhalb von x zurückgegeben werden.
      Jetzt muss ich aber noch mal fragen.
      1. Was passiert, wenn ich zwei voneinander getrennte Flächen mit der selben Zahl habe?
      2. Sind nur die Zahlen der Flächen gesucht, die direkt an die Fläche, in der sich der Startpunkt befindet, angrenzen (im Beispiel bedeutet das: ist auch 1 gesucht)?
      3. Wie werden die Abstände gemessen? Luftlinie oder in Schritten, und wenn Schritte, was sind Schritte, nur horizontal und vertikal oder auch diagonal?
      1. Wird als eine Fläche behandelt.
      2. Ja, nur die angrenzenden Flächen in der Fläche des Startpunktes
      3. Die Länge ist nur zur Bestimmung der nächsten Fläche. Quadratwurzel((Startpunkt.X - Ziel.X)² + (Startpunkt.Y - Ziel.Y)²).

      AliveDevil schrieb:


      Es soll nun eine Funktion geschrieben werden, die ausgehend von dem Startpunkt, alle angrenzenden Flächen (Zahlen) bestimmt, und zu jeder Zahl den nächsten Punkt zurückgibt.
      Das scheint mir bisserl überkompliziert.
      Zunächst mal muss doch v.a. ein "RandFinder" geschrieben werden, der alle Randpunkte der Fläche ermittelt.
      Von diesen Randpunkten aus dann die dem Startpunkt nächsten Punkte der angrenzenden Flächen zu finden sollte einfach sein.

      btw: Hat die Aufgabe ieinen praktischen Bezug?
      Jap, es hat einen praktischen Nutzen - in einem Mapgenerator, der durch Farbflächen Funktionen aufruft, die wiederum auf anderen Funktionen basieren.
      Der RandFinder ist die einfachste Variante, die mir auch eingefallen ist, nur habe ich es nicht geschafft, eine Variante davon zu entwickeln, die irgendwie funktioniert.
      Ein Anfang könnte Floodfill sein - das Problem: der Algorithmus ist unendlich, wenn am Quellarray nichts geändert wird, was nicht passieren kann, da das Arrays mehrere hunderttausend Elemente beinhalten kann.
      Ich habe mir auch die Arbeit gemacht, dass ganze umzuwandeln, nur wie gesagt: das Teil ist derzeit unendlich.
      Spoiler anzeigen

      C-Quellcode

      1. public static void Main()
      2. {
      3. Queue<Point> q = new Queue<Point>();
      4. q.Enqueue(Point.Empty);
      5. while (q.Count > 0)
      6. {
      7. Point p = q.Dequeue();
      8. if (isValid(p))
      9. {
      10. Console.WriteLine("{0}", p);
      11. Point w = p, e = p, n = p, s = p;
      12. n.Y += 1;
      13. s.Y -= 1;
      14. while (isValid(w)) { w.X += 1; }
      15. while (isValid(e)) { e.X -= 1; }
      16. for (int x = e.X; x <= w.X; x++)
      17. {
      18. if (p.X != x)
      19. {
      20. n.X = s.X = x;
      21. //Console.WriteLine("\t{0}", p);
      22. if (isValid(n)) { q.Enqueue(n); }
      23. if (isValid(s)) { q.Enqueue(s); }
      24. }
      25. //System.Threading.Thread.Sleep(125);
      26. }
      27. }
      28. }
      29. }
      30. static bool isValid(Point location) { return Math.Sqrt(location.X * location.X + location.Y * location.Y) < 5; }
      Ich wollts erst versuchen, nur exakt am Rand entlang zu gehen, aber dann ist mir aufgefallen, dass "Inseln" auf die Art verschluckt werden. Floodfill scheint mir hier auch die einzig gescheite Lösung, auch wenns warscheinlich vergleichsweise Langsam und viel Speicher belegt. Meine Implementation sollte nicht unendlich sein, zumindest falls ich nichts falsch gemacht hab.
      Ist noch nicht ganz fertig, kommt später noch. ;)
      Ob es lange dauert oder nicht, spielt keine Rolle. Die Anwendung darf für die Erzeugung gerne die ein oder andere Minute länger brauchen. Wie gesagt: es ist "nur" ein MapGenerator der intern verwendet wird. Effizienz ist dabei ebenfalls vollkommen unbedeutend.
      Sobald ein fertiger Algorithmus steht, sollte es einfacher sein, den zu verbessern.
      Hier:
      Spoiler anzeigen

      C#-Quellcode

      1. public static class AreaMagic
      2. {
      3. /// <summary>
      4. /// Gets the surrounding points of a point.
      5. /// </summary>
      6. public static Point[] GetSurroundingPoints(Point point)
      7. {
      8. var result = new Point[8];
      9. result[0] = new Point(point.X + 1, point.Y);
      10. result[1] = new Point(point.X + 1, point.Y + 1);
      11. result[2] = new Point(point.X, point.Y + 1);
      12. result[3] = new Point(point.X - 1, point.Y + 1);
      13. result[4] = new Point(point.X - 1, point.Y);
      14. result[5] = new Point(point.X - 1, point.Y - 1);
      15. result[6] = new Point(point.X, point.Y - 1);
      16. result[7] = new Point(point.X + 1, point.Y - 1);
      17. return result;
      18. }
      19. /// <summary>
      20. /// Finds the border of the shape the specified point belongs to.
      21. /// </summary>
      22. public static Point[] FindBorder(int[,] area, Point point, out Point[] interior)
      23. {
      24. if (area == null)
      25. throw new ArgumentNullException("area");
      26. if (point.X < area.GetLowerBound(0) || point.X > area.GetUpperBound(0)
      27. || point.Y < area.GetLowerBound(1) || point.Y > area.GetUpperBound(1))
      28. throw new ArgumentOutOfRangeException("startPoint");
      29. int shapeKey = area[point.X, point.Y];
      30. var inactivePoints = new List<Point>();
      31. var activePoints = new List<Point>(new[] { point });
      32. var borderPoints = new List<Point>();
      33. while (activePoints.Count > 0)
      34. {
      35. var newActivePoints = new List<Point>();
      36. foreach (Point p in activePoints)
      37. {
      38. bool isBorderPoint = false;
      39. Point[] surroundingPoints = GetSurroundingPoints(p);
      40. foreach (Point p1 in surroundingPoints)
      41. {
      42. if (p1.X < area.GetLowerBound(0) || p1.X > area.GetUpperBound(0)
      43. || p1.Y < area.GetLowerBound(1) || p1.Y > area.GetUpperBound(1)
      44. || area[p1.X, p1.Y] != shapeKey)
      45. {
      46. isBorderPoint = true;
      47. continue;
      48. }
      49. if (inactivePoints.Contains(p1))
      50. continue;
      51. if (!newActivePoints.Contains(p1))
      52. newActivePoints.Add(p1);
      53. }
      54. if (isBorderPoint)
      55. borderPoints.Add(p);
      56. }
      57. inactivePoints.AddRange(activePoints);
      58. activePoints = newActivePoints;
      59. }
      60. interior = inactivePoints.ToArray();
      61. return borderPoints.ToArray();
      62. }
      63. /// <summary>
      64. /// Finds the border of the shape the specified point belongs to.
      65. /// </summary>
      66. public static Point[] FindBorder(int[,] area, Point point)
      67. {
      68. Point[] interior;
      69. return FindBorder(area, point, out interior);
      70. }
      71. /// <summary>
      72. /// Finds the nearest points of the shapes adjacent to the specified point's shape.
      73. /// </summary>
      74. public static Dictionary<int, Point> FindAdjacentShapes(int[,] area, Point point)
      75. {
      76. Point[] borderPoints = FindBorder(area, point);
      77. int shapeKey = area[point.X, point.Y];
      78. var pointsToCheck = new List<Point>();
      79. foreach (Point p in borderPoints)
      80. {
      81. Point[] surroundingPoints = GetSurroundingPoints(p);
      82. foreach (Point p1 in surroundingPoints)
      83. {
      84. if (p1.X < area.GetLowerBound(0) && p1.X > area.GetUpperBound(0)
      85. && p1.Y < area.GetLowerBound(1) && p1.Y > area.GetUpperBound(1)
      86. && area[p1.X, p1.Y] != shapeKey && !pointsToCheck.Contains(p1))
      87. {
      88. pointsToCheck.Add(p1);
      89. }
      90. }
      91. }
      92. var distances = new Dictionary<int, double>();
      93. var points = new Dictionary<int, Point>();
      94. foreach (Point p in pointsToCheck)
      95. {
      96. int key = area[p.X, p.Y];
      97. int dX = p.X - point.X;
      98. int dY = p.Y - point.Y;
      99. double distance = dX * dX + dY * dY;
      100. if (!distances.ContainsKey(key) || distances[key] > distance)
      101. {
      102. distances[key] = distance;
      103. points[key] = p;
      104. }
      105. }
      106. return points;
      107. }
      108. }
      Kannst du es ausprobieren? Ich kanns gerade nicht, muss demnächst leider schlafen gehen und ne Testanwendung würde hier glaube ich großzügiger ausfallen.

      AliveDevil schrieb:

      Zudem darf keine Rekursion angewandt werden.

      AliveDevil schrieb:

      Sobald ein fertiger Algorithmus steht, sollte es einfacher sein, den zu verbessern.
      Diese beiden Aussagen widersprechen sich deutlich.
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!
      Für Flächen ohne Inseln ist ein Randfinder am effizientesten. Bei mit Inseln muss halt ein Floodfill mit Rand-Detection her.

      Nur die Randpunkte einer Fläche zu merken ist eh bisserl speicherschonender.

      so ich hab mal ein Werk von mir umgebastelt als Basis einer TestSolution. Die Daten werden als 1-dimensionales Array in der LedDesignDts.DesignRow gespeichert, aber ich hab Konverter beiliegen, mit denen man von 1D <-> 2D konvertieren kann:

      VB.NET-Quellcode

      1. Imports System.Runtime.CompilerServices
      2. <Microsoft.VisualBasic.HideModuleName()> _
      3. Public Module ArrayConverter
      4. <Extension()> _
      5. Public Function ToSquare2D(Of T)(arr As T()) As T(,)
      6. Dim sqrt = Math.Sqrt(arr.Length)
      7. Dim size = CInt(sqrt)
      8. If size <> sqrt Then Throw New Exception("das eindimensionale array kann nicht in ein quadratisches 2-dimensionales Array umgewandelt werden")
      9. Dim result(size - 1, size - 1) As T
      10. Using enrt = arr.GetEnumeratorX
      11. For y = 0 To size - 1
      12. For x = 0 To size - 1
      13. enrt.MoveNext()
      14. result(y, x) = enrt.Current
      15. Next
      16. Next
      17. End Using
      18. Return result
      19. End Function
      20. <Extension()> _
      21. Public Function ToArray(Of T)(arr2D As T(,)) As T()
      22. Dim size = arr2D.GetLength(0)
      23. Dim result(size * size - 1) As T
      24. Dim i = 0
      25. For Each n In arr2D
      26. result(i) = n
      27. i += 1
      28. Next
      29. Return result
      30. End Function
      31. End Module

      Dateien

      ErfinderDesRades schrieb:

      Für Flächen ohne Inseln ist ein Randfinder am effizientesten. Bei mit Inseln muss halt ein Floodfill mit Rand-Detection her.
      Bildverarbeitung:
      1. Laplace-Operator.
      2. Diskriminierung am Histogramm-Minimum, Laplace-Operator.
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!
      @Artentus
      mit einige Änderungen konnte ich die Lösung dazu bewegen, zu funktionieren.
      Eine Bedingung, die ich nicht nannte: es darf nicht diagonal gegangen werden, was am Ende keine Probleme darstellt.
      Die Funktion public static Point[] FindBorder(int[,] area, Point point, out Point[] interior) liefert die Punkte zurück, die gerade nicht außerhalb des Bereiches liegen. Die Definition war Punkte, die auf der Außenfläche liegen. Die Funktion public static Dictionary<int, Point> FindAdjacentShapes(int[,] area, Point point) liefert mir kein Ergebnis, was für mich nicht weiter schlimm ist, da ich auch anderweitig die Dinge testen kann.
      Ein weiterer Punkt: field[area.X, area.Y] geht davon aus, dass das Array so aufgebaut ist:

      Quellcode

      1. Y1 Y2 Y3 Y4
      2. X1 11 12 13 14
      3. X2 21 22 23 24
      4. X3 31 32 33 34
      5. X4 41 42 43 44

      Ich habe mich vermutlich falsch ausgedrückt, als ich sagte, dass das Feld mit X;Y definiert wird.


      So .. da dieser Beitrag jetzt über eine Stunde geschrieben wurde, habe ich keine Ahnung mehr, wovon ich eigentlich am Anfang geschrieben habe.
      Die veränderte, funktionierende Variante ist im Expander zu finden:
      Spoiler anzeigen

      C#-Quellcode

      1. using System;
      2. using System.Collections.Generic;
      3. using System.Drawing;
      4. using System.Linq;
      5. using System.Text;
      6. using System.Threading.Tasks;
      7. namespace ConsoleApplication1
      8. {
      9. class Program
      10. {
      11. static void Main(string[] args)
      12. {
      13. int[,] field = {
      14. {1,1,1,1,1,1,1},
      15. {1,0,0,0,0,0,1},
      16. {1,0,1,0,1,0,1},
      17. {1,0,0,0,0,0,1},
      18. {1,0,1,0,1,0,1},
      19. {1,0,0,2,0,0,1},
      20. {1,1,1,1,1,1,1}
      21. };
      22. var t = AreaMagic.FindBorder(field, new Point(3, 3));
      23. var s = AreaMagic.FindAdjacentShapes(field, new Point(3, 3));
      24. }
      25. }
      26. public static class AreaMagic
      27. {
      28. /// <summary>
      29. /// Gets the surrounding points of a point.
      30. /// </summary>
      31. public static Point[] GetSurroundingPoints(Point point, bool diagonal = false)
      32. {
      33. Point[] result;
      34. if (diagonal)
      35. {
      36. result = new Point[8];
      37. result[0] = new Point(point.X + 1, point.Y);
      38. result[1] = new Point(point.X + 1, point.Y + 1);
      39. result[2] = new Point(point.X, point.Y + 1);
      40. result[3] = new Point(point.X - 1, point.Y + 1);
      41. result[4] = new Point(point.X - 1, point.Y);
      42. result[5] = new Point(point.X - 1, point.Y - 1);
      43. result[6] = new Point(point.X, point.Y - 1);
      44. result[7] = new Point(point.X + 1, point.Y - 1);
      45. }
      46. else
      47. {
      48. result = new Point[4];
      49. result[0] = new Point(point.X + 1, point.Y);
      50. result[1] = new Point(point.X, point.Y + 1);
      51. result[2] = new Point(point.X - 1, point.Y);
      52. result[3] = new Point(point.X, point.Y - 1);
      53. }
      54. return result;
      55. }
      56. /// <summary>
      57. /// Finds the border of the shape the specified point belongs to.
      58. /// </summary>
      59. public static Point[] FindBorder(int[,] area, Point point, out Point[] interior)
      60. {
      61. if (area == null)
      62. throw new ArgumentNullException("area");
      63. if (point.X < area.GetLowerBound(0) || point.X > area.GetUpperBound(0)
      64. || point.Y < area.GetLowerBound(1) || point.Y > area.GetUpperBound(1))
      65. throw new ArgumentOutOfRangeException("startPoint");
      66. int shapeKey = area[point.X, point.Y];
      67. var inactivePoints = new List<Point>();
      68. var activePoints = new List<Point>(new[] { point });
      69. var borderPoints = new List<Point>();
      70. while (activePoints.Count > 0)
      71. {
      72. var newActivePoints = new List<Point>();
      73. foreach (Point p in activePoints)
      74. {
      75. Point[] surroundingPoints = GetSurroundingPoints(p);
      76. foreach (Point p1 in surroundingPoints)
      77. {
      78. if (p1.X < area.GetLowerBound(0) || p1.X > area.GetUpperBound(0)
      79. || p1.Y < area.GetLowerBound(1) || p1.Y > area.GetUpperBound(1)
      80. || area[p1.X, p1.Y] != shapeKey)
      81. {
      82. if (!borderPoints.Contains(p1))
      83. borderPoints.Add(p1);
      84. continue;
      85. }
      86. if (inactivePoints.Contains(p1))
      87. continue;
      88. if (!newActivePoints.Contains(p1))
      89. newActivePoints.Add(p1);
      90. }
      91. }
      92. inactivePoints.AddRange(activePoints);
      93. activePoints = newActivePoints;
      94. }
      95. interior = inactivePoints.ToArray();
      96. return borderPoints.ToArray();
      97. }
      98. /// <summary>
      99. /// Finds the border of the shape the specified point belongs to.
      100. /// </summary>
      101. public static Point[] FindBorder(int[,] area, Point point)
      102. {
      103. Point[] interior;
      104. return FindBorder(area, point, out interior);
      105. }
      106. /// <summary>
      107. /// Finds the nearest points of the shapes adjacent to the specified point's shape.
      108. /// </summary>
      109. public static Dictionary<int, Point> FindAdjacentShapes(int[,] area, Point point)
      110. {
      111. // Änderung in einen LINQ Ausdruck, weil es mir einfacher erschien.
      112. //Point[] borderPoints = FindBorder(area, point);
      113. //int shapeKey = area[point.X, point.Y];
      114. //var l = borderPoints.ToLookup(item => area[item.X, item.Y], item => item);
      115. //var d = l.ToDictionary(item => item.Key, item => item.OrderBy(checkPoint =>
      116. //{
      117. // int dX = checkPoint.X - point.X;
      118. // int dY = checkPoint.Y - point.Y;
      119. // return dX * dX + dY * dY;
      120. //}).First());
      121. //return d;
      122. // Einzeiler. Obiges in "kurz"
      123. // area[item.X, item.Y] muss genutzt werden, da andernfalls die Dictionary-Konvertierung nicht funktioniert.
      124. return FindBorder(area, point).ToLookup(item => area[item.X, item.Y], item => item).ToDictionary(item => item.Key, item => item.OrderBy(checkPoint =>
      125. {
      126. int dX = checkPoint.X - point.X;
      127. int dY = checkPoint.Y - point.Y;
      128. return dX * dX + dY * dY;
      129. }).First());
      130. }
      131. }
      132. }


      @RodFromGermany
      Die zwei Aussagen schließen sich nicht aus, da ein Zwischenergebnis durchaus eine rekursive Funktion sein kann, das Endergebnis aber iterativ sein muss (Crowdthinking, mir fällt der deutsche Begriff dazu nicht ein).

      @ErfinderDesRades
      Wenn du mir sagen könntest, wie man die Anwendung verwendet, wäre das sehr nett. Auf Anhieb steige ich da nämlich gerade nicht durch.

      Ich danke jedem, der mir hier bei der Findung der Lösung beigetragen hat.

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

      AliveDevil schrieb:

      Die Funktion public static Point[] FindBorder(int[,] area, Point point, out Point[] interior) liefert die Punkte zurück, die gerade nicht außerhalb des Bereiches liegen. Die Definition war Punkte, die auf der Außenfläche liegen.
      Ja, ich weiß, war auch so beabsichtigt. Ich weiß, dass eigentlich das andere gesucht ist, aber die Funktion ist nur ein Zwischenschritt und nur so semantisch sinnvoll. Der Border sind nunmal nur diese Punkte, hätte ich es anders gemacht, dann wäre das zwar in diesem Fall richtig, aber nicht allgemein.

      AliveDevil schrieb:

      ​Die Funktion public static Dictionary<int, Point> FindAdjacentShapes(int[,] area, Point point) liefert mir kein Ergebnis
      Das ist schade. Darf ich fragen, was der Fehler ist?
      Eigentlich war es so geplant:
      Schritt 1: die Randpunkte mit FindBorder holen.
      Schritt 2: die an diese Punkte angrenzenden Punkte, die eine andere zahl haben, sind die äußeren Punkte.
      Schritt 3: von diesen Punkten wiederum alle durchgehen und den nächsten finden - und zwar für jede vorkommende Zahl einen.

      AliveDevil schrieb:

      Ich habe mich vermutlich falsch ausgedrückt, als ich sagte, dass das Feld mit X;Y definiert wird.
      Was meintest du denn eigentlich, wenn nicht ein solches Array? Das würde mich jetzt doch mal interessieren.
      Nach meiner Definition wäre das Array mit Y;X definiert worden.

      Jap, genau das, was du eigentlich wolltest, funktionierte nicht. Ich hatte mich auch nicht weiter mit der Nicht-Funktionalität beschäftigt, sondern eher damit, die Funktion FindBorders() anzupassen.
      Es soll zudem nur der nächste Partner gesucht werden, nicht alle möglichen Flächen, die eventuell beteiligt sein könnten. Das es dann nicht allgemein gilt, ist dann klar.
      Hast du selbst deinen Code mit meinem Sample-Array getestet? Es werden entweder zuviele oder zuwenige Punkte angegeben (FindAdjacentShapes lieferte mir jedesmal {} zurück).

      Die Funktion kann ja trotzdem so bestehen bleiben, denn ansich funktioniert sie - würde sie nicht {} zurückgeben.

      AliveDevil schrieb:

      Nach meiner Definition wäre das Array mit Y;X definiert worden.
      Wirklich? Kann man das so sagen? Imo hätte es ja auch sein können, das die Y-Dimension die erste im Array ist. Da ein Array keinerlei räumliche Anhaltspunkte liefert, hätte das Bild auch einfach an der Vertikalen gespiegelt sein können und an der Indizierung des Arrays hätte sich nichts geändert. Es hängt eben nur davon ab, wie man die Koordinaten bei der Darstellung wählt, geht man von der allgemeinen Konvention aus, dann wäre das Bild von dir ein Y-X-System, kein X-Y-System, also genau das, was du wolltest.

      AliveDevil schrieb:

      Es soll zudem nur der nächste Partner gesucht werden, nicht alle möglichen Flächen, die eventuell beteiligt sein könnten.
      Hä, warum kommt dann ein Dictionary raus? Ich bin gerade verwirrt.
      ich hab jetzt einen Randfinder gebastelt. Nach Art der Turtle-Programmierung. Also ein Objekt kann in 4 Richtungen voranschreiten, und bei Kollision musses halt die Richtung ändern.
      Dafür hab ich mir eine extra Richtung-Klasse ausgedacht:

      VB.NET-Quellcode

      1. Imports System.Diagnostics
      2. <DebuggerDisplay("{Value}")> _
      3. Public Structure Direction
      4. Private Shared _Directions As Integer = 4
      5. Public Value As Integer
      6. Private Shared Function cyclicAddition(left As Integer, right As Integer) As Integer
      7. cyclicAddition = (left + right) Mod _Directions
      8. If cyclicAddition < 0 Then cyclicAddition += _Directions
      9. End Function
      10. Public Shared Operator +(ByVal left As Direction, ByVal right As Integer) As Direction
      11. Return New Direction With {.Value = cyclicAddition(left.Value, right)}
      12. End Operator
      13. Public Shared Operator -(ByVal left As Direction, ByVal right As Integer) As Direction
      14. Return New Direction With {.Value = cyclicAddition(left.Value, -right)}
      15. End Operator
      16. Public Shared Widening Operator CType(ByVal initialData As Integer) As Direction
      17. Return New Direction With {.Value = initialData}
      18. End Operator
      19. Public Function Move(pt As Point) As Point
      20. If (Value And 1) = 1 Then 'horizontal
      21. pt.X += 2 - Value
      22. Else 'vertical
      23. pt.Y += Value - 1
      24. End If
      25. Return pt
      26. End Function
      27. End Structure
      Also nix besonderes, nur dass man unendlich in-/de-crementieren kann ohne Zahlen-Überlauf.
      Achja, und dann kann eine Direction einen Point um einen Schritt moven.

      und die Turtle ist auch überraschend übersichtlich:

      VB.NET-Quellcode

      1. Imports System.ComponentModel
      2. Public Class Turtle : Implements INotifyPropertyChanged, INotifyPropertyChanging
      3. Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
      4. Public Event PropertyChanging As PropertyChangingEventHandler Implements INotifyPropertyChanging.PropertyChanging
      5. Public Direction As New Direction
      6. ''' <summary> this validator is meant to be set from outside </summary>
      7. Public Canstep As Func(Of Point, Boolean)
      8. Private _Location As Point = Nothing
      9. Public Property Location() As Point
      10. Get
      11. Return _Location
      12. End Get
      13. Set(value As Point)
      14. If _Location = value Then Return
      15. RaiseEvent PropertyChanging(Me, New PropertyChangingEventArgs("Location"))
      16. _Location = value
      17. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Location"))
      18. End Set
      19. End Property
      20. ''' <summary> try move in its Direction </summary>
      21. Public Function TryStep() As Boolean
      22. Dim pt = Direction.Move(_Location)
      23. If Not Canstep(pt) Then Return False
      24. Location = pt
      25. Return True
      26. End Function
      27. ''' <summary> move along the Border - Border on left hand </summary>
      28. Public Sub StepAgainstBorder()
      29. 'suche rechtsrum eine freie Richtung. Wenn gefunden, 1 Schritt, dann Richtung decrementieren damit wieder gegen die Grenze gerichtet.
      30. For i = 0 To 3
      31. If TryStep() Then
      32. Direction -= 1
      33. Return
      34. End If
      35. Direction += 1
      36. Next
      37. End Sub
      38. End Class
      die muss halt 2 Step-Modi haben, weil die Art, wie sie an einer Grenze entlanggeht ist ganz anders, als wenn sie von ieinem Punkt gradeaus läuft, um eine Grenze erstmal aufzufinden.
      Son Turtle findet allerdings keine Inseln (bzw. wenn doch, dann hält er sie für die Aussen-Grenzen seiner Welt).
      Für Insel-Detection müsste man das Innere der gefundenen Randlinie nochmal zeilenweise durchlaufen.


      Ich hab das mit Timer und pipapo gemacht - guckt sich sehr hübsch.
      Dateien
      • TurtleTester.zip

        (69,95 kB, 242 mal heruntergeladen, zuletzt: )
      Einfaches Rätsel ;) :

      Gegeben sei ein binärer Baum. (Eine Struktur zum (z.B.) Speichern von Daten, bei der ein Knoten lediglich 2 Knoten unter sich haben darf).
      Man hat also einen Urknoten, der hat 2 Knoten unter sich, die widerum 2 unter sich haben, usw. Pro "Ebene"/"Level" also doppelt so viele Knoten.

      Die Frage ist nun:
      Wie könnte ein Algorithmus aussehen, der den Baum in vollständiger Balance hält, egal wie groß er wird?
      (Damit ist gemeint, dass, egal an welchem Knoten man steht, die Differenz der totalen Anzahl der linken oder rechten (Unter-)Knoten eines jeden Knoten auf beiden Seiten [ggf. mit deren Unterknoten] den Wert 1 nicht überschreitet).
      Die ersten haben sicher schon Ideen ;)

      Lösung (soll sein):
      Ein Projekt, dass a) kompiliert und dem b) dem zum Beispiel 100 Knoten (eine Zahl) hingeworfen werden können und am Ende eine Liste von 100 Zahlen zurück gibt. Diese 100 Zahlen (ID's der Knoten) stehen für die Reihenfolge, wie die Knoten zu verteilen sind. Hä? Okay: Alle (möglichen) Knoten des Baumes werden 1. von oben nach unten und dann 2. von links nach Rechts durch numeriert. Somit hat jeder Knoten seine Zahl.

      Quellcode

      1. 0
      2. 1 2
      3. 3 4 5 6
      4. 7 8 9 10 11 12 13 14

      Der Knoten (0) ist der Wurzelknoten. (1) ist der linke Child von (0). (2) ist der rechte Child von (0). (3) ist der linke Child von (1), (4) der rechte. (5) ist der Linke von (2), etc.
      Das Ergebnis ist - wie gesagt - eine Reihenfolge. Gibt man dem Algorithmus hier die Zahl 10, sollte folgendes zurückgegeben werden:
      0 - 1 - 2 - 3 - 5 - 4 - 6 - 7 - 11 - 9
      Warum? Weil egal an welchem Knoten im "fertigen" Baum man steht, die Differenz der Anzahl der Knoten rechts oder links davon ist nicht größer als 1.
      Schaut man sich Knoten 0 an, hat seine linke Seite 5 Knoten unter sich (1, 3, 4, 7, 9), seine rechte Seite 4 Knoten (2, 5, 6, 11). Linke Seite plus eins, Bedingung erfüllt.
      Schaut man auf Knoten 1, so haben sowohl seine linke Seite (3, 7), als auch seine rechte (4, 9) gleich viele Knoten unter sich. Bedingung erfüllt.

      Kleine Einschränkung:
      Es dürfen ausschließlich Objekte des .Net Frameworks verwendet werden (keine eigenen Klassen) und um den Stand zur
      Zeit der Berechnung festzuhalten, lediglich
      ein List(of Integer) und ein List(of object). Außerdem alle nativen Datentypen und deren Grundrechenoperationen.

      Gut ist, wer es raus bekommen hat, perfekt ist, dessen Funktion am längsten Überlebt, je mehr Knoten reingegeben werden.

      Ich glaube nicht jeder von euch kommt SOFORT drauf, ist eher ne Denkaufgabe.
      Die Lösung ist technisch eigentlich ziemlich einfach, bei mir gings :| :S :huh: ?( 8| 8o :whistling: