3D-Arrays - jetzt auch serialisierbar!

    • Allgemein

    Es gibt 1 Antwort in diesem Thema. Der letzte Beitrag () ist von Artentus.

      3D-Arrays - jetzt auch serialisierbar!

      Jeder kennt das Problem: dreidimensionale Arrays (T(, ,) bzw. T[, ,]) sind nicht serialisierbar. Das Problem habe ich seit heute gelöst!
      (muss jetzt nichts heißen, da das Problem sicher mehr als nur mich betroffen haben und sich entsprechend einige damit befassten)
      Aber ich habe es trotzdem geschafft!
      Das Array ist zwar noch nicht IEnumerable-kompatibel, aber das wird, hoffentlich, noch kommen.
      Derzeit ist es noch eine ganz normale Klasse, mit der ihr schön arbeiten könnt.

      Die Klasse habe ich ursprünglich in C# geschrieben, wird hier somit "nur" übersetzt werden, wodurch u.U. Fehler auftreten können.
      Einige Elemente habe ich so gekennzeichnet, dass ihr die gerne löschen könnt, da diese für das Array ansich nicht wichtig sind. Dies soll zudem eine Alternative zum Dictionary(Of Vector3, T) bzw. Dictionary(Of Point3, T) sein.
      Wobei dies Elemente mit XYZ-Koordinaten sind.

      Nun, was benötigen wir ersteinmal für so ein Anliegen? Natürlich das Array selbst.
      Da ich sagte, dass dies ein serialisierbares Array wird, könnt ihr euch natürlich vorstellen, dass dies ein eindimensionales Array sein wird, also definieren wir das als:
      VB

      VB.NET-Quellcode

      1. Dim T() Array
      C#

      C-Quellcode

      1. T[] Array;

      Jetzt ist natürlich die Frage: woher den Index eines Elements nehmen?
      Stellen wir uns ein eindimensionales Array mit der Länge 5 vor. Dann sind die Indexe so:

      Quellcode

      1. 0 => 0
      2. 1 => 1
      3. 2 => 2
      4. 3 => 3
      5. 4 => 4

      Also ist der Index = X, wenn X das angesprochene Element ist
      bei einem zwei dimensionalen Array sähe das so aus:

      Quellcode

      1. 0 => 0,0
      2. 1 => 0,1
      3. 2 => 0,2
      4. 3 => 1,0
      5. 4 => 1,1
      6. 5 => 1,2
      7. 6 => 2,0
      8. 7 => 2,1
      9. 8 => 2,2

      Hier gilt dann: Index = Länge * X + Y
      da 3 * 1 + 2 = 5.
      Für das drei dimensionale Array möchte ich die Beziehungen nicht angeben, da dies doch zu lang wäre.
      Dort gilt: Index = ((X * Länge) + Y) * Länge + Z.
      Warum X als erstes angegen wird: die Indexe sollen mit Z um eins steigen, mit Y um die Länge und mit X um Länge².
      Also haben wir auch schon die nächste Funktion für die Klasse: Index(x, y, z).
      VB

      VB.NET-Quellcode

      1. Public Function Index(x As Integer, y As Integer, z As Integer) As Integer
      2. Return ((x * k) + y) * k + z
      3. End Function
      C#

      C-Quellcode

      1. public int Index(int x, int y, int z) {
      2. return ((x * k) + y) * k + z;
      3. }

      Hier ist nur noch ein Problem: was ist k?
      Wie vorher schon angesprochen, gibt es eine Konstante Länge, die angegeben werden muss.
      Daher muss eine Eigenschaft Dimensions erzeugt werden, die dann die Eigenschaften enthält.
      VB

      VB.NET-Quellcode

      1. Private _dimensions As Integer
      2. Public Property Dimensions As Integer
      3. Get
      4. Return _dimensions
      5. End Get
      6. Private Set(value As Integer)
      7. _dimensions = value
      8. End Set
      9. End Property
      C#

      C-Quellcode

      1. public int Dimensions {get; private set;}

      Jetzt kann das k auch durch Dimensions ersetzt werden.
      Da wir jetzt die zwei nötigen Dinge für die Initialisierung haben, sollten wir uns an den Konstruktor machen.
      Hier müssen das Array und die Dimensionen definiert werden:
      VB

      VB.NET-Quellcode

      1. Public Sub New(dimensions As Integer)
      2. Me.array = New T(Dimensions) {}
      3. Me.Dimensions = dimensions
      4. End Sub
      C#

      C-Quellcode

      1. public Array3D(int dimensions) {
      2. this.array = new T[dimensions];
      3. this.Dimensions = dimensions;
      4. }

      Theoretisch ist die Klasse damit fertig, nur leider gibt es dort ein Problem: was ist mit dem Zugriff auf das Array?
      Dafür gibt es die Default Property bzw. this.
      Dort kann der Array-Zugriff einfach gestaltet werden und ebenfalls zurückgegeben werden.
      VB

      VB.NET-Quellcode

      1. Public Default Property Item(x As Integer, y As Integer, z As Integer) As T
      2. Get
      3. Return array(Index(x, y, z))
      4. End Get
      5. Set(value As T)
      6. array(Index(x, y, z)) = value
      7. End Set
      8. End Property
      C#

      C-Quellcode

      1. public T this[int x, int y, int z] {
      2. get {
      3. return array[Index(x, y, z)];
      4. }
      5. set {
      6. array[Index(x, y, z)] = value;
      7. }
      8. }

      Damit ist die Klasse für die grobe Nutzung fertig!

      Wenn ihr noch erweiterte Funktionalität haben wollt, könnt ihr noch weiterlesen, ansonsten könnt ihr das nun so nutzen. Ich werde beides in VB und C# mit der erweiterten Funktionalität anhängen, sodass ihr den Code so übernehmen könnt.

      Da wir nur wissen, welcher Index zu welcher Position gehört und nicht wissen, welche Position zu welchem Index gehört, müssen wir uns einiger Mathematik bedienen. Das ist auch der eigentliche Sinn hinter der erweiterten Funktionalität, die ich bisher implementiert habe.
      Wir definieren ersteinmal zwei Variable flag = -1 und move = -1, diese werden später wichtig sein. Diese sollten auf Private sein, damit nicht jeder auf diese zugriff hat.

      Machen wir uns erst einmal eine Eigenschaft um eine Zahl zu bekommen.
      VB

      VB.NET-Quellcode

      1. Public ReadOnly Property Flag() As Integer
      2. Get
      3. If _flag = -1 Then
      4. flag = 0
      5. Dim move As Integer = 1
      6. While Dimensions >> move <> 0
      7. _flag = _flag Or (Dimensions >> move)
      8. move += 1
      9. End While
      10. End If
      11. Return _flag
      12. End Get
      13. End Property
      C#

      C-Quellcode

      1. public int Flag {
      2. get {
      3. if (flag == -1) {
      4. flag = 0;
      5. for (int move = 1; Dimensions >> move != 0; move++)
      6. flag |= Dimensions >> move;
      7. }
      8. return flag;
      9. }
      10. }

      Es wird also die komplette Zahl von Anfang an um ein Bit verschoben, sodass die größte Zahl, die wir bekommen, Dimensions - 1 ist.
      Diese Property beschreibt das Bit-Muster, mit dem wir am Ende den Index in eine Koordinate umrechnen.
      Die nächste Property wird die Anzahl der zu verschiebenden Bits darstellen.
      VB

      VB.NET-Quellcode

      1. Public ReadOnly Property Move() As Integer
      2. Get
      3. If _move = -1 Then
      4. _move = 0
      5. Dim bitShift As Integer = 0
      6. While Flag >> bitShift <> 0
      7. If ((Flag >> bitShift) And 1) = 1 Then
      8. _move += 1
      9. End If
      10. bitShift += 1
      11. End While
      12. End If
      13. Return _move
      14. End Get
      15. End Property
      C#

      C-Quellcode

      1. public int Move {
      2. get {
      3. if (move == -1) {
      4. move = 0;
      5. for (int bitShift = 0; Flag >> bitShift != 0; bitShift++)
      6. if (((Flag >> bitShift) & 1) == 1)
      7. move++;
      8. }
      9. return move;
      10. }
      11. }

      Hier wird aus dem Flag berechnet, wie viele Schritte getan werden müssen, um von der einen Koordinate zur nächsten zu gelangen. Hier ist allerdings ein Fehler enthalten, sodass nur Zahlen der Basis 2 2^n korrekt behandelt werden.
      Sollte jemand wissen, wie dies korrigiert werden kann, bitte melden!

      Nach dem die zwei Properties eingerichtet wurden, müssen die Werte korrekt eingesetzt werden:
      VB

      VB.NET-Quellcode

      1. Public Function Resolve(index As Integer) As Point3
      2. Dim logicFlag As Integer = Flag
      3. Dim bitMove As Integer = Move
      4. Dim x As Integer = (index >> (bitMove * 2)) And logicFlag
      5. Dim y As Integer = (index >> bitMove) And logicFlag
      6. Dim z As Integer = index And logicFlag
      7. Return New Point3(x, y, z)
      8. End Function
      C#

      C-Quellcode

      1. public Point3 Resolve(int index) {
      2. int logicFlag = Flag;
      3. int bitMove = Move;
      4. int x = (index >> (bitMove * 2)) & logicFlag;
      5. int y = (index >> bitMove) & logicFlag;
      6. int z = index & logicFlag;
      7. return new Point3(x, y, z);
      8. }

      Es werden die Koordinaten also zur richtigen Position gerückt und anschließend mit dem Vergleichsmuster verglichen. Am Ende kommt dann die korrekte Koordinate heraus.
      Ihr solltet allerdings nur Dimensionen mit der Basis 2 nutzen, da sonst Probleme beim Resolven entstehen (es werden falsche Koordinaten zurückgegeben).
      Ich übernehme keine Garantie, dass der VB-Code korrekt ist, da ich diesen nur durch einen Konverter gejagt habe.
      Dateien
      • Array3D.zip

        (2,77 kB, 115 mal heruntergeladen, zuletzt: )
      Ich hab zwar den Sinn von "Flag" und "Move" nicht verstanden, ab schau mal hier:
      Spoiler anzeigen

      C-Quellcode

      1. public class ExtendedArray<T>
      2. {
      3. T[] values;
      4. int dimension;
      5. int[] bounds;
      6. public ExtendedArray(int dimension, int[] bounds)
      7. {
      8. if (bounds.Length != dimension) throw new ArgumentException();
      9. this.dimension = dimension;
      10. this.bounds = bounds;
      11. values = new T[bounds.Aggregate((x, y) => x * y)];
      12. }
      13. public int GetBounds(int index)
      14. {
      15. return bounds[index];
      16. }
      17. public int Dimension
      18. {
      19. get
      20. {
      21. return dimension;
      22. }
      23. }
      24. public T this[int[] indices]
      25. {
      26. get
      27. {
      28. return values[GetIndex(indices)];
      29. }
      30. set
      31. {
      32. values[GetIndex(indices)] = value;
      33. }
      34. }
      35. private int GetDimensionSize(int dimension)
      36. {
      37. if (dimension == 0) return 1;
      38. return bounds.Take(dimension).Aggregate((x, y) => x * y);
      39. }
      40. private int GetIndex(int[] indices)
      41. {
      42. int result = 0;
      43. for (int i = 0; i < indices.Length; i++)
      44. {
      45. result += indices[i] * GetDimensionSize(i);
      46. }
      47. return result;
      48. }
      49. public int[] IndicesOf(T item)
      50. {
      51. int[] result = new int[dimension];
      52. int index = Array.IndexOf(values, item);
      53. for (int i = dimension - 1; i >= 0; i--)
      54. {
      55. result[i] = index / GetDimensionSize(i);
      56. index -= result[i];
      57. }
      58. return result;
      59. }
      60. }

      Diese Klasse mapped ein beliebig dimensionales Array mit beliebigen Abmessungen in ein eindimensionales Array und zurück.

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