Stack Copy Constructor

Hallo liebe Community,

ich soll einen Copy Constructor schreiben für einen Stack. Der Stack hat die üblichen Methoden:
class StringStack
empty()
peek() Gibt aktuelles Element zurück
pop() löscht aktuelles Element und gibt aktuelles zurück
push(String s) legt neues Element auf den Stack

Die Klasse StringStackEntry hat folgende Eigenschaften:
private StringStackEntry next;
private String s

ich soll jetzt einen Copy Konstruktor schreiben, sodass man durch folgende Aufrufe eine deep copy machen kann:
StringStrack original = new StringStack();
den kann man ja durch Schleifen füllen.
und durch folgenden Aufruf dann die Kopie: StringStack copy = new StringStack(original);

Habe das mit folgendem Code versucht (meine Idee war den durch einen temporären Stack zu kopieren, das funktioniert aber so irgendwie nicht):

	   StringStack tmp = new StringStack();
	   StringStack copy = new StringStack();
	   
	   
	   while(!original.empty()){
		   tmp.push(original.pop());
	   }
	   
	   while(!tmp.empty()){
		   String tmps = new String(tmp.peek());
		   original.push(tmp.peek());
		   copy.push(tmps);
	   }
	   
   }```

Danke schonmal :)

String tmps = new String(tmp.peek()); muss ein pop sein, sonst liest er immer das Gleiche, also insgesamt:

while(!tmp.empty()){
		   String tmps = new String(tmp.pop());
		   original.push(tmps);
		   copy.push(tmps);
	   }

Sowas kann man auch mit eingestreuten String.err.println(...)-Aufrufen (“Debugger für Arme”), die wichtige Zwischenwerte ausgeben, feststellen.

von String eine Kopie ist bestimmt übertrieben,
darf ruhig überall als dasselbe Objekt enthalten sein, ist immutable

edit: copy im Konstruktor auch nicht angebracht, das eigene Objekt, this, befüllen

Alles klar danke euch :slight_smile:
mit folgendem Code funktioniert es nun:

public StringStack(StringStack original){

	   StringStack tmp = new StringStack();
	   
	   
	   
	   while(!original.empty()){
		   tmp.push(original.pop());
	   }
	   
	   while(!tmp.empty()){
		   String tmps = tmp.pop();
		   original.push(tmps);
		   this.push(tmps);
	   }
	   
   }

Noch eine Frage: Wie würde denn bei diesem Fall eine Shallow Copy aussehen? Irgendwie komme ich nicht drauf.

was bedeuten denn die beiden Copy-Arten, welche inneren Attribute oder sonstigen referenzierten Objekte könnten betroffen sein und wie beurteilst du dies?
etwas Text von dir wäre gut, sonst klingt ‚ich komme nicht drauf‘ nur nach ‚so, dann erzählt mal alles‘ :wink:

während du dagegen zur ersten Frage vorbildlich schon eigenen Code gepostet hast,
den man gut korrigieren konnte ohne erst ‚wie sehen denn deine Versuche aus?‘ nachzufragen

ich beschränke mich mal für diese Nachfrage zu einer evtl.-Eigen-Korrektur, auch schon halbe Antwort:
wenn auf diesen Punkt hingewiesen und explizit eine Deep Copy gefragt war, ist es möglich dass doch new String() als Lösung bevorzugt wird,
sinnvoll in der Praxis ist es nicht, aber mag der Definition näher kommen

Hallo,

wir wissen jetzt, wie die öffentliche API aussieht, kennen aber nicht die Interna.

Bitte beachten: Bei zwei Stacks dreht sich die Reihenfolge der Elemente um!

Wie eine Shallow Copy aussehen mag, ist Interpretationssache des Dozenten.

Da es hier um Strings geht, wird sich eine Shallow Copy nicht groß von einer Deep Copy unterscheiden.

Ist deine Struktur z. B. Array-basiert, dann könnte auch einfach eine Kopie des Arrays oder eine Kopie der Referenz gemeint sein.

Ich müsste die Struktur “nachbilden”, um das zu zeigen, aber möchte ich nicht.

Kannst du noch weitere Einzelheiten/Details/Implementierungen zeigen/posten?

Grüße

Ganz einfach: Der Original-Stack hält doch eine Referenz auf den „obersten“ StringStackEntry. Wenn du die diese Referenz auch bei der Kopie setzt, hast du eine flache Kopie.

Ich finde es etwas unschön, dass das Objekt, von dem Kopiert wird, manipuliert wird (mit push und pop). Wenn man da an parallele Threads denkt, ist das ziemlich hässlich. Besser wäre es, wenn man die Elemente des Stacks abrufen könnte, ohne sie aus dem Stack zu entfernen und wieder einfügen zu müssen.

Also würde ich sagen so:

	   this.first = original.first;
   }```

Ja, das sieht richtig aus. In diesem Fall könnte es sogar sein, dass eine flache Kopie ausreicht, nämlich dann, wenn StringStackEntry unveränderlich ist (also weder das String-Element noch die next-Referenz nachträglich geändert werden können). Wenn sich zwei Collections dagegen veränderliche Daten teilen (etwa ein Array), würden sie ihre Änderungen gegenseitig sehen können, und deshalb eine tiefe Kopie benötigen.

für veränderbare Datenstrukturen gibt es daher noch die naheliegende Variante,
allein die internen Strukturen deep zu kopieren bzw. schlicht unabhängig von außen normal neu zu befüllen,
aber die referenzierten fremden Einträge dabei nur zu übernehmen statt auch zu kopieren,

könnte man auch als die eigentliche Shallow Copy ansehen, aber Definitionen sind Schall und Rauch,
siehe etwa language agnostic - What is the difference between a deep copy and a shallow copy? - Stack Overflow

Shallow copies duplicate as little as possible. A shallow copy of a collection is a copy of the collection structure, not the elements. With a shallow copy, two collections now share the individual elements.

für ArrayList & Co. ist jedenfalls nur das ArrayList-Hauptobjekt mit denselben referenzierten Unterstrukturen unbrauchbar,
und eine echte Deep Copy mit Kopie der enthaltenen Objekte (vielleicht gesamter vernetzter Objektstand des Programms…) allgemein abwegig,

allein eine Übernahme der Elemente standardmäßig sinnvoll, und das ist es natürlich auch was Konstruktoren wie ArrayList(Collection c) machen,
ob es nun als Shallow Copy durchgeht oder nicht, nur das ist das sinnvolle Vorgehen


die clone-Methode von ArrayList kopiert das interne Array, ‚shallow copy‘ drangeschrieben

     * Returns a shallow copy of this <tt>ArrayList</tt> instance.  (The
     * elements themselves are not copied.)
     *
     * @return a clone of this <tt>ArrayList</tt> instance
     */
    public Object clone() {
        try {
            @SuppressWarnings("unchecked")
                ArrayList<E> v = (ArrayList<E>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError();
        }
    }

Alles klar :slight_smile: . Danke für eure schnelle und gute Hilfe :slight_smile:

Alles zu Ungenau!

  1. Wenn ich schon schreibe, Implementierung her, wieso ignorierst du meinen Beitrag dann einfach? Hältst du mich für inkompetent?

  2. Nach deiner Beschreibung und ich die Glaskugel angeschmissen habe, musst du folgende Implementierung haben:

    Element last;

    Stack() {
    }

    Stack(Stack stack, boolean deep) {
        if (deep) {
            Stack tmp = new Stack();
            while (!stack.isEmpty()) {
                tmp.push(stack.pop());
            }

            while (!tmp.isEmpty()) {
                String s = tmp.pop();
                stack.push(s);
                push(s); // Overridable method call in constructor

                // oder:
                // stack.push(new String(s)); // String constructor invocation
                // push(new String(s));
            }
        } else {
            last = stack.last;
        }
    }

    void push(String s) {
        last = new Element(s, last);
    }

    String pop() {
        Element e = last;
        last = last.prev;
        return e.s;
    }

    boolean isEmpty() {
        return last == null;
    }
}

class Element {
    String s;
    Element prev;

    Element(String s, Element prev) {
        this.s = s;
        this.prev = prev;
    }
}

    public static void main(String[] args) {
        Stack s1 = new Stack();
        Stack s2 = new Stack(s1, false);

        s1.push("eins");
        s1.push("zwei");
        s1.push("drei");

        System.out.println("s2.isEmpty() = " + s2.isEmpty());

        while (!s1.isEmpty()) {
            System.out.println("s1.pop() = " + s1.pop());
        }

        // +++ +++ +++

        s1 = new Stack();

        s1.push("eins");
        s1.push("zwei");
        s1.push("drei");

        s2 = new Stack(s1, false);
        System.out.println("s2.isEmpty() = " + s2.isEmpty());

        while (!s1.isEmpty()) {
            System.out.println("s1.pop() = " + s1.pop());
        }
        while (!s2.isEmpty()) {
            System.out.println("s2.pop() = " + s2.pop());
        }
    }```

s2.isEmpty() = true
s1.pop() = drei
s1.pop() = zwei
s1.pop() = eins
s2.isEmpty() = false
s1.pop() = drei
s1.pop() = zwei
s1.pop() = eins
s2.pop() = drei
s2.pop() = zwei
s2.pop() = eins



d. h. weder push() noch pop() haben Einfluss bei Kopie der Referenz, wenngleich die Elemente schon kopiert werden.

Hä? Push erstellt ein neues Objekt, das betrifft die andere Liste nicht, Pop ändert den Zeiger weiter, auch das betrifft die andere Liste nicht.

Drei Elemente: Man würd erwarten, jede Änderung "des Stacks" betrifft auch den anderen Stack. So ist nicht.

So gibts "effektiv" keinen Unterschied. Oder?