• Das Erstellen neuer Accounts wurde ausgesetzt. Bei berechtigtem Interesse bitte Kontaktaufnahme über die üblichen Wege. Beste Grüße der Admin

[FRAGE] Array und clone

m1au

New member
Hallo :)

Ich habe leider ein Problemchen mit dem Klonen von Objekten.

Zuerst mal ein Beispiel, wo alles so funktioniert, wie man es sich erwartet:
PHP:
<script>
   "use strict";
   
   class C {
      constructor(id) {
         this.id = id;
         this.next = null; // Verweis auf eine weitere C-Instanz
      }
      clone(id) { // eine Kopie der aktuellen C-Instanz erstellen und zurückgeben
         let o = new C(id);
         o.next = this.next; // null oder ein Objekt vom Typ C
         return o;
      }
   }

   let o0 = new C(0);
   let o1 = new C(1);
   o1.next = o0;
   console.log(o1.id, o1.next.id, o0.id); // 1 0 0
</script>
Dieses Script funktioniert allerdings nur mit Google Chrome. Die anderen Browser können mit "class" noch nicht umgehen.

Hier habe ich eine Methode "clone". Darin erstelle ich eine Kopie der aktuellen Instanz und gebe sie zurück.
Auf der Konsole werden die id´s so ausgegeben, wie man sich das erwartet.

Nun lasse ich die Klasse C unverändert, schreibe aber das Hauptprogramm ein wenig um.
Jetzt arbeite ich mit Arrays und der clone-Methode:
PHP:
<script>
   "use strict";
   
   class C {
      constructor(id) {
         this.id = id;
         this.next = null; // Verweis auf eine weitere C-Instanz
      }
      clone(id) { // eine Kopie der aktuellen C-Instanz erstellen und zurückgeben
         let o = new C(id);
         o.next = this.next; // null oder ein Objekt vom Typ C
         return o;
      }
   }

   let a = [new C(0), new C(1)]; // Array
   a[1].next = a[0];
   console.log(a[1].id, a[1].next.id, a[0].id); // 1 0 0
   
   // Eine Kopie des Arrays erstellen:
   let clonedA = [];
   for(let i = 0; i < a.length; ++i) {
      clonedA[i] = a[i].clone(100 + i);
   }
   console.log(clonedA[1].id, clonedA[1].next.id, clonedA[0].id); // 101 0 100
</script>
In der ersten Konsole-Ausgabe passt wieder alles, in der zweiten jedoch nicht. 101 und 100 stimmen, die 0 jedoch nicht. Die 0 zeigt, dass ein geklontes Objekt immer noch auf ein Original-Objekt verweist. Und das will ich nicht. Es soll auf das entsprechende Objekt im geklonten Array verweisen.

Wie löst man dieses Problem denn am Besten?
 
die Objekte in .next hast du ja auch nicht geklont, also referenzieren sie das Original. (shallow copy vs. deep copy)
Wenn ich in der clone-Methode das next klone, erhalte ich ein komplett neues Objekt, das nichts mit den Objekten im Array clonedA zu tun hat.

Ich hätte aber gerne, dass sich next dann auf ein Objekt in clonedA bezieht. Die zweite Konsole-Ausgabe sollte folgendermaßen aussehen: 101 100 100
Das heißt, das next von clonedA[1] soll sich auf clonedA[0] beziehen. Aber wie mache ich das am Besten?

Übrigens danke für den Hinweis darauf, dass es mit FF 45 schon funktioniert :)
 
Zuletzt bearbeitet:
Dieses deep copy bringt 2 Probleme mit sich.

Hier ein Beispiel:
PHP:
<script>
   "use strict";
   
   class C {
      constructor(id) {
         this.id = id;
         this.next = null; // Verweis auf eine weitere C-Instanz
      }
      clone(id) { // eine Kopie der aktuellen C-Instanz erstellen und zurückgeben
         let o = new C(id);
         o.next = this.next == null ? null : this.next.clone(id); // null oder ein geklontes Objekt vom Typ C
         return o;
      }
   }

   let a = [new C(0), new C(1)]; // Array
   a[1].next = a[0];
   console.log(a[1].id, a[1].next.id, a[0].id); // 1 0 0
   
   // Eine Kopie des Arrays erstellen:
   let clonedA = [];
   for(let i = 0; i < a.length; ++i) {
      clonedA[i] = a[i].clone(100 + i);
   }
   
   // Problem 1: statt 101 100 100 erhalte ich 101 101 100
   console.log(clonedA[1].id, clonedA[1].next.id, clonedA[0].id); // 101 101 100
   
   // Problem 2: das geklonte next-Objekt von clonedA[1] bezieht sich nicht auf clonedA[0]
   console.log(clonedA[1].next == clonedA[0]); // false
   console.log(clonedA[1].next === clonedA[0]); // false
</script>
Das erste Problem ist, dass in diesem Programm das geklonete Objekt und sein geklontes next-Objekt nun beide die id 101 haben. Das könnte ich natürlich ändern, indem ich der clone-Methode 2 id´s übergebe. Aber das Hauptprogramm hat womöglich gar keinen Überblick darüber, welche id das geklonte next-Objekt bekommen soll.

Das zweite Problem hatte ich schon erwähnt. Das geklonte next-Objekt hat nichts mit den Objekten im geklonten Array clonedA zu tun. Das hätte ich aber gerne. Das next vom clonedA[1] soll dann auf clonedA[0] verweisen, sodass dann auf der Konsole 101 100 100 ausgegeben wird.

Außerdem gibt es noch ein Problem, wenn die Objekte beim next wechselseitig aufeinander beziehen. Dann habe ich Kreisläufer.

- - - Aktualisiert - - -

Mir ist bislang nur eine einzige Lösung dazu eingefallen, aber die gefällt mir nicht:
PHP:
<script>
   "use strict";
   
   class C {
      constructor(id) {
         this.id = id;
         this.next = null; // Index auf eine weitere C-Instanz
      }
      clone(id) { // eine Kopie der aktuellen C-Instanz erstellen und zurückgeben
         let o = new C(id);
         o.next = this.next;
         return o;
      }
   }

   let a = [new C(0), new C(1)]; // Array
   a[1].next = 0; // jetzt nicht das Objekt, sondern nur der Array-Index übergeben
   console.log(a[1].id, a[a[1].next].id, a[0].id); // 1 0 0
   
   // Eine Kopie des Arrays erstellen:
   let clonedA = [];
   for(let i = 0; i < a.length; ++i) {
      clonedA[i] = a[i].clone(100 + i);
   }
   
   console.log(clonedA[1].id, clonedA[clonedA[1].next].id, clonedA[0].id); // 101 100 100
   
   console.log(clonedA[clonedA[1].next] == clonedA[0]); // true
   console.log(clonedA[clonedA[1].next] === clonedA[0]); // true
</script>
Ich übergebe hier nicht nicht ein Objekt, sondern nur einen Array-Index an next.

Jetzt erfordert die Klasse C aber unbedingt, dass next wirklich ein Index übergeben wird. Das Hauptprogramm muss sich darauf einstellen, ob es will, oder nicht. Hat es gar kein Array, muss es eines anlegen. Und das gefällt mir nicht.
 
Zuletzt bearbeitet:
Außerdem gibt es noch ein Problem, wenn die Objekte beim next wechselseitig aufeinander beziehen. Dann habe ich Kreisläufer.
das ist etwas, daß deine clone-Methode beachten muß (man kann ja auch DOM-Konten klonen, und die haben jede Menge Zirkulare Referenzen (= Kreisläufer))

Aber das Hauptprogramm hat womöglich gar keinen Überblick darüber, welche id das geklonte next-Objekt bekommen soll.
wenn das so ist, kannst du eben kein deep copy machen. aber mit einem Closure als Counter kommt man schon recht weit.
 
Du kannst ein Closure verwenden, um aufeinanderfolgende Zahlen zu erhalten (da du schon bei ES6 bist, kannst du eigentlich auch gleich Generatoren verwenden). und solange wie die Funktion im Scope definiert ist, kannst du an jeder Stelle diese Counter-Funktion aufrufen.
 
Tut mir leid, aber so gut bin ich in JavaScript nicht. Ich verstehe leider nicht, was du hier meinst. Wie würde denn mein kleines Testscript mit dieser Closure aussehen?
 
Code:
// as closure
function nextInt(start)
{
    var counter = start || 0;
    return function () {
        return counter++;
    };
}
var myGen = nextInt(100);
myGen(); // 100
myGen(); // 101

// as generator
function* nextInt(start)
{
    var counter = start || 0;
    while (true) {
        yield counter++;
    }
}
var myGen = nextInt(100);
myGen.next().value; // 100
myGen.next().value; // 101
 
Wenn ich im Array 1000 Einträge habe, kann in einer C-Instanz das next auf jede der anderen Array-Element verweisen.
Also das next der C-Instanz von Arrayelement 0 könnte auf die C-Instanz von Arrayelement 123 verweisen.
Das next verweist nicht immer auf die C-Instanz des unmittelbar folgenden Arrayelements.
Darum befürchte ich, dass mir dieser Generator nicht viel weiterhelfen wird.
 
soweit ich das sehe, bzw. das was du zeigst sollte eher anders modelliert werden.
also deine klasse c sollte bestehen aus einem array und einer verketteten liste.
clonen sollte man dann eine instanz von c und nicht elemente in einem array in welchem zufällig die elemente deiner liste enthalten sind.
das array in deiner klasse c benötigst du dann auch nur zum iterieren, sprich in dem array sind alle elemente der liste enthalten, du kannst über sie iterieren ohne zirkularitäten beachten zu müssen
die elemente der liste solltest du überhaupt nicht nach außen geben. keine ahnung, was du im anwendungsfall benötigst, hier im beispiel benötigst du nur ein array von ids, die du in einer memberfunktion zusammenstellst und nach außen gibst.

- - - Aktualisiert - - -

Darum befürchte ich, dass mir dieser Generator nicht viel weiterhelfen wird.
du hast
Code:
[FONT=Courier New][(*), (*), (*), (*)]         
  |    |    |    |
 [1]  [2]  [3]->[4]-*
  ^    |         ^  |
  |    |         |
  |    *---------*  |
  *-----------------*[/FONT]
ein array(hier [(*), (*), (*), (*)]) das enthält referenzen auf objekte (hier (*))
die objekte (hier [1] bis [4]) haben eine id (hier die 1 bis 4) und eine verzeigerung (hier als pfeile)
wenn du das kopieren willst, erzeugst du erst ein neues array mit neuen objekten und neuen ids über den generator
dann gehst du elementweise durch das alte array, siehst nach auf welches element mit welcher id es zeigt, die id suchst du im alten array und setzt dann bei dem neuen array mit gleichem element-index den next-pointer auf das element aus dem neuen array, welches der gesuchten id entspricht
 
Den zweiten Teil habe ich verstanden. Schaut wohl folgendermaßen dann aus:
PHP:
<script>
   "use strict";
   
   class C {
      constructor(id) {
         this.id = id;
         this.next = null;
      }
      clone(id) { 
         let o = new C(id);
         o.next = this.next; 
         return o;
      }
   }

   let a = [new C(0), new C(1), new C(2), new C(3)]; // Array (beginne hier mit 0, statt mit 1)
   a[1].next = a[3];
   a[2].next = a[3];
   a[3].next = a[0];
   
   // Zahlengenerator:
   function nextInt(start) {
      var counter = start || 0;
      return function () {
         return counter++;
      };
   }
   let myGen = nextInt(100);
   
   // eine Kopie vom Array anlegen:
   let clonedA = [];
   let count = a.length;
   for(let i = 0; i < count; ++i) {
      clonedA[i] = a[i].clone(myGen()); // clone kopiert einfach mal alles, was in C ist (auch wenn next zu diesem Zeitpunkt noch falsch ist)
   }
     
   // next anpassen, sodass sie auf die clonedA-Elemente verweisen:
   for(let i = 0; i < count; ++i) {
      let next = a[i].next;
      if(next !== null) {
         clonedA[i].next = clonedA[next.id];
      }
   }
   
   // nur zum Debuggen:
   function log(array) {
      for(let i = 0; i < array.length; ++i) { 
         console.log(array[i].id);
         let next = array[i].next;
         if(next === null) {
            console.log(null);
         }
         else {
            console.log(next.id);
         }
      }
   }
   log(clonedA);
</script>
Danke für den Hinweis. Ist wirklich besser, als diese Index-Variante von mir. :)

Nur deinen ersten Teil habe ich nicht verstanden. Die Sache mit dem Array und der verketteten Liste. Das array soll nicht im Hauptprogramm liegen, sondern in der C-Klasse? Bei 1000 C-Instanzen habe ich dann 1000 mal das Array definiert? Und warum benötige ich einerseits ein Array und zusätzlich noch eine Liste?
 
ungetested
für a[1].next = a[3]; würde man wohl besser eine methode schreiben
das array besser als private member anlegen
Code:
function nextInt(start)
{
  var counter = start || 0;
  return function ()
  {
    return counter++;
  };
}
let myGen = nextInt(100);

class C extends Array
{
  constructor(gen)
  {
    this.gen = gen;
  }
  addNew()
  { 
    this.push({
      id: this.gen(),
      next: null;
    })
  },
  clone()
  {
    let ret = new C(this.gen);
    for(let i = 0; i < this.length; ++i)
    {
      ret.addNew();
    }
    for(let i = 0; i < this.length; ++i)
    {
      if (this[i].next !== null)
      {
        let n = this.findIndex(function(e)
        {
          return e.id === this[i].next.id;
        });
        // n = -1 => fehler
        ret[i].next = ret[n];
      }
    }
    return ret;
  }
}
let a = new C(myGen)
a.addNew();
a.addNew();
a.addNew();
a.addNew();
a[1].next = a[3];
a[2].next = a[3];
a[3].next = a[0];
let b = a.clone();
 
Wahnsinn! Dank eurer Hilfe habe ich jetzt tatsächlich noch das loop-subdivision mit Daten in einer halfedge-Datenstruktur hinbekommen!! Danke :) Ach übrigens ... falls euch das interessiert, kann es hier noch reinposten. Aber jetzt lese ich mir noch den vorhergenden Eintrag durch :)
 
Die HTML-Datei index.html: 'Untitled Post' | TextUploader.com
Und die Javascript-Datei js.js: pastebin - Anonymous - post number 3410084

Interessant wird es in der JavaScript-Datei erst ab der Zeile 1236. Dort gehts los mit dieser halfEdge-Datenstruktur. Die besteht aus den Klassen HalfEdge, Face, Vertex und HalfEdgeDataStructure.

In der Funktion start, die vom Hauptprogramm aufgerufen wird, kann man sich aussuchen, mit welchem Modell man arbeitet. Entweder ein Tetraeder, eine Sphere oder ein Icosahedron. Danach rufe ich 3x die Methode subdivide auf. D. h. das Modell wird 3fach verfeinern und weichgezeichnet.

In subdivide arbeite ich nach dem Loop-Schema. D. h. aus 1 Dreieck mache ich 4 Dreiecke.

Damit man dann im WebGL die Dreiecke noch sieht, habe ich ganz bewusst auf ein Phong-Shading verzichtet.
 
Zurück
Oben