Java - Enums

Karlter

Mitglied
Java:
public enum Direction {

    NORTH, SOUTH, EAST, WEST;

        public Direction opposite () {
            switch (this) {
                case NORTH:
                    return SOUTH;
                case SOUTH:
                    return NORTH;
                case EAST:
                    return WEST;
                case WEST:
                    return EAST;
                default:
                    return null;
            }
        }
    }


public class MzeSlv {

    public static SolutionStep findExit(Room currentRoom) {
        if (currentRoom.isExit()) {
            return new SolutionStep(null, null);
        }


        currentRoom.markVisited();

        for (Direction direction : Direction.values()) {
            Room nextRoom = currentRoom.getRoom(direction);
            if (nextRoom != null && !nextRoom.isVisited()) {
                SolutionStep result = findExit(nextRoom);
                if (result != null) {
                    return new SolutionStep(direction, result);
                }
            }
        }

        return null;
    }
}


public class Room {

    private final Room[] rooms = new Room[4];
    private boolean exit;
    private boolean visited = false;

    public Room(Room north, Room east, Room south, Room west, boolean exit) {
        this.exit = exit;
        rooms[Direction.NORTH.ordinal()] = north;
        rooms[Direction.EAST.ordinal()] = east;
        rooms[Direction.SOUTH.ordinal()] = south;
        rooms[Direction.WEST.ordinal()] = west;

        if (north != null) {
            assert north.rooms[Direction.SOUTH.ordinal()] == null;
            north.rooms[Direction.SOUTH.ordinal()] = this;
        }
        if (east != null) {
            assert east.rooms[Direction.WEST.ordinal()] == null;
            east.rooms[Direction.WEST.ordinal()] = this;
        }
        if (south != null) {
            assert south.rooms[Direction.NORTH.ordinal()] == null;
            south.rooms[Direction.NORTH.ordinal()] = this;
        }
        if (west != null) {
            assert west.rooms[Direction.EAST.ordinal()] == null;
            west.rooms[Direction.EAST.ordinal()] = this;
        }
    }

    public Room getRoom(Direction d) {
        return rooms[d.ordinal()];
    }

    public boolean isExit() {
        return exit;
    }

    public void markVisited() {
        this.visited = true;
    }

    public boolean isVisited() {
        return visited;
    }
}


public class SolutionStep {
    private final Direction direction;
    private final SolutionStep next;

    public SolutionStep(Direction direction, SolutionStep next) {
        this.direction = direction;
        this.next = next;
    }

    public SolutionStep next() {
        return next;
    }

    public Direction getDirection() {
        return direction;
    }


}

In dieser Aufgabe sollen Sie sich mit Enum befassen und Enum#ordinal() verwenden.

Schreiben Sie dazu ein Programm, das den Weg durch einen Irrgarten findet.

Das Enum Direction definiert Konstanten für die vier Richtungen (Direction.NORTH, Direction.EAST, Direction.SOUTH und Direction.WEST). Zusätzlich gibt es eine Methode Direction#opposite() die die gegensätzliche Richtung zurück gibt. (NORTH ↔↔ SOUTH bzw. EAST ↔↔ WEST).

Der Irrgarten wird durch eine Klasse Room repräsentiert, welcher bis zu vier Verbindungen zu anderen Räumen hat. Room hat einen Konstruktor Room(Room north, Room east, Room south, Room west, boolean exit). Dieser erzeugt einen neuen Raum mit angrenzenden Räumen in die Richtungen Direction.NORTH, Direction.EAST, Direction.SOUTH und Direction.WEST. null wird für (noch) keinen Raum verwendet. Nutzen Sie Enum#ordinal() von Direction um die Räume in einem Array (rooms) zu halten. Die übergebenen Räume werden so aktualisiert, dass am Ende des Konstruktoraufruf der Weg zurück wieder zu diesem Raum führt. Z.B. muss dieser Raum im Süden von dem Raum sein, der als north übergeben wurde. Stellen Sie mit assert sicher, das beim Aktualisieren keine vorhandenen Verbindungen überschrieben werden. Der boolean gibt an, ob der Raum ein Ausgang ist. Mit der Methode Room getRoom(Direction d) wird der Raum zurückgegeben, der in die angegebene Richtung liegt; sonst null. Die Methode boolean isExit() gibt zurück, ob der Raum ein Ausgang ist.

Die Funktion SolutionStep findExit(Room currentRoom) in MazeSolver bestimmt einen Weg aus dem Irrgarten heraus. Das Ergebnis wird in einzelnen Schritten angegeben. SolutionStep hat zwei Methoden (Direction getDirection() und SolutionStep next()) und wird wie folgt interpretiert: Wenn getDirection() null zurückgibt, ist der aktuelle Raum der Ausgang. Ansonsten ist dort angegebenen, in welche Richtung der aktuelle Raum verlassen werden muss. next() beinhaltet dann die Anweisung für den nächsten Raum. Beginn der Lösung ist in start. Z.B: Rückgabe (direction=null, next=null) heißt, der Startraum ist ein Ausgang. Rückgabe (direction=WEST, next=(direction=NORTH, next=(direction=null, next=null))) heißt, nach Westen, nach Norden und dort ist der Ausgang. Gibt es gar keinen Ausweg, wird null zurückgegeben. Jeder Irrgarten wird nur ein einziges mal gelöst.


Eventuell hat ja jemand Zeit und Lust sich hier mal einzulesen und mir gegebenenfalls einen Hinweis zu geben, wo hier noch Probleme auftreten könnten. In den vorgegebenen Testfällen sagt er mir, dass folgende Probleme auftreten:

Testfall · constructor_getRoomReturnsSameRoomAsPassed fehlgeschlagen​

Multiple Failures (2 failures) org.opentest4j.AssertionFailedError: Roome east was not found. org.opentest4j.AssertionFailedError: Roome south was not found.

Testfall · opposite fehlgeschlagen​

The opposite directions are checked. (4 failures) org.opentest4j.AssertionFailedError: south is opposite north org.opentest4j.AssertionFailedError: west is opposite east org.opentest4j.AssertionFailedError: north is opposite south org.opentest4j.AssertionFailedError: east is opposite west

Testfall · findExit_mazeSmall fehlgeschlagen​

expected: <Room@58d6cbe0> but was: <null>

Testfall · findExit_mazeManyExits fehlgeschlagen​

expected: <Room@51432332> but was: <Room@557cbdc2>

Testfall · findExit_mazeManyExits_endOnFirstExit fehlgeschlagen​

expected: <Room@4873b41> but was: <Room@216c8b5a>

Testfall · findExit_labyrinth fehlgeschlagen​

expected: <SOUTH> but was: <EAST>



Danke für Eure Mühe und Zeit!
 
Zuletzt bearbeitet:
Was kriege ich dafür wenn ich mich durch die Wall of text hangele? 👿

Ich glaube, dein Problem ist, dass du immer ordinal() aufrufst, anstatt einfach nur die Enum direkt zu verwenden. Dafür sind die Enums nicht gedacht.
 
Auf den ersten Blick sieht der Code grundsätzlich korrekt aus. Insbesondere der Failure bei opposite wundert mich, weil die Methode ist nun nicht wirklich komplex.

Evtl. mal probieren, ob du die Reihenfolge der Enum-Definition umstellst, anstelle von NORTH, SOUTH, EAST, WEST auf wie in der Aufgabe angegeben auf NORTH, EAST, SOUTH, WEST. Weil dadurch ändern sich die Ordinal Werte der Konstanten was mindestens auf die Reihenfolge, wie er durch das Labyrinth bei resolven läuft Einfluss hat. Und evtl. erwarten die Junit-Tests genau diese Ordinal Werte
 
PS: Sollte mein Vorschlag das lösen, ist das gleichzeitig ein gutes Beispiel warum das nutzen von #ordinal gefährlich sein kann - es ist von der Reihenfolge im Source-Code abhängig. Sobald man an dem Enum was ändert ändern sich die Werte, was damit z.B. gespeicherte Daten invalid machen kann.
 
Erstmal vielen Dank für Eure antworten. Überraschenderweise hast du Recht @LimDul, ich habe sie getauscht und jetzt erreiche ich eine Korrektheit von 97% bei den Testen. Allerdings frage mich ob das so korrekt ist, denn in der Aufgabe steht ja eigentlich in welcher Reihenfolge das ganze implementiert werden soll.... Sind die JunitTests dann eventuell einfach falsch? Nun erhalte ich nur noch folgende Fehlermeldung:

estfall · findExit_challenge fehlgeschlagen​

Could not invoke the method 'findExit' in the class MazeSolver because of an exception within the method: java.lang.StackOverflowError
 
Erstmal vielen Dank für Eure antworten. Überraschenderweise hast du Recht @LimDul, ich habe sie getauscht und jetzt erreiche ich eine Korrektheit von 97% bei den Testen. Allerdings frage mich ob das so korrekt ist, denn in der Aufgabe steht ja eigentlich in welcher Reihenfolge das ganze implementiert werden soll.... Sind die JunitTests dann eventuell einfach falsch? Nun erhalte ich nur noch folgende Fehlermeldung:

estfall · findExit_challenge fehlgeschlagen​

Could not invoke the method 'findExit' in the class MazeSolver because of an exception within the method: java.lang.StackOverflowError
In der Aufgabenstellung steht ja die Reihenfolge so wie ich sie aufgeschrieben habe. Wie beschrieben - ein super Beispiel warum ordinal gefährlich ist zu verwenden. 🙂

Warum der Fehler auftritt - keine Ahnung, Ich vermute mal du kannst dir das Challenge Maze nicht ansehen um es lokal zu testen? Der Fehler besagt, dass du zu viele rekursive Aufrufe hast. Ich sehe da jetzt keinen Fehler in deinem Code, so dass ich vermute, dass das Challenge Maze ein Labyrinth der folgenden Form ist

Code:
-> -> -> -> -> -> -> -> -> -> v
v  <- <- <- <- <- <- <- <- <- <-
-> -> -> -> -> -> -> -> -> -> v
...
Spricht eine Schlange, die sich windet. Das führt dazu dass die Rekursion tiefer wird, als Java das speichern kann. Evtl. wird bei dem Test die Stack Size auch bewusst niedrig gesetzt.

Das heißt, wenn du das vermeiden willst, müsstest du von einer rekursiven auf eine Iterative Lösung umstellen. Das geht grundsätzlich, macht es aber aufwendiger. Insbesondere weil die rekursive Lösung aus meiner Sicht die naheliegende und am besten lesbare Lösung ist.
 
Evtl. zeigst Du uns die aktuelle Version. Ggf. hast Du mehr geändert als nur die Reihenfolge in der Enum.

Da das Ziel der Aufgabe sein soll, das ordinal von enum kennen zu lernen, halte ich es für unwahrscheinlich, dass da jetzt aus der rekursiven Lösung eine Iterative gebaut werden soll.
 
Java:
public enum Direction {
    NORTH, EAST, SOUTH, WEST;

        public Direction opposite () {
            switch (this) {
                case NORTH:
                    return SOUTH;
                case SOUTH:
                    return NORTH;
                case EAST:
                    return WEST;
                case WEST:
                    return EAST;
                default:
                    return null;
            }
        }
    }

public class MazeSolver {

    public static SolutionStep findExit(Room currentRoom) {
        if (currentRoom.isExit()) {
            return new SolutionStep(null, null);
        }

        for (Direction direction : Direction.values()) {
            Room nextRoom = currentRoom.getRoom(direction);
            if (nextRoom != null && !nextRoom.isVisited()) {
                SolutionStep result = findExit(nextRoom);
                if (result != null) {
                    return new SolutionStep(direction, result);
                }
            }
        }
        return null;
    }
}

ublic class Room {

    private final Room[] rooms = new Room[4];
    private boolean exit;
    private boolean visited = false;

    public Room(Room north, Room east, Room south, Room west, boolean exit) {
        this.exit = exit;
        rooms[Direction.NORTH.ordinal()] = north;
        rooms[Direction.EAST.ordinal()] = east;
        rooms[Direction.SOUTH.ordinal()] = south;
        rooms[Direction.WEST.ordinal()] = west;

        if (north != null) {
            assert north.rooms[Direction.SOUTH.ordinal()] == null;
            north.rooms[Direction.SOUTH.ordinal()] = this;
        }
        if (east != null) {
            assert east.rooms[Direction.WEST.ordinal()] == null;
            east.rooms[Direction.WEST.ordinal()] = this;
        }
        if (south != null) {
            assert south.rooms[Direction.NORTH.ordinal()] == null;
            south.rooms[Direction.NORTH.ordinal()] = this;
        }
        if (west != null) {
            assert west.rooms[Direction.EAST.ordinal()] == null;
            west.rooms[Direction.EAST.ordinal()] = this;
        }
    }

    public Room getRoom(Direction d) {
        return rooms[d.ordinal()];
    }

    public boolean isExit() {
        return exit;
    }

    public void markVisited() {
        this.visited = true;
    }

    public boolean isVisited() {
        return visited;
    }
}

public class SolutionStep {
    private final Direction direction;
    private final SolutionStep next;

    public SolutionStep(Direction direction, SolutionStep next) {
        this.direction = direction;
        this.next = next;
    }

    public SolutionStep next() {
        return next;
    }

    public Direction getDirection() {
        return direction;
    }


}



Das ist jetzt der aktuelle Code, welcher ein Testergebnis von 97% erreicht. Ich habe also wirklich lediglich die Anordnung von den Enums verändert. Den einzigen Fehler, welchen ich noch erhalte ist ein java.lang.StackOverflowError. Das irritiert mich, da ich die rekursive Lösung auch für den optimaleren Lösungsweg halt und ich darüber hinaus doch die besuchten Räume mit visit kennzeichne...
 
Danke für den Hinweis @KonradN, habe ich jetzt wieder eingefügt:

Java:
public class MazeSolver {

    public static SolutionStep findExit(Room currentRoom) {
        if (currentRoom.isExit()) {
            return new SolutionStep(null, null);
        }

        currentRoom.markVisited();

        for (Direction direction : Direction.values()) {
            Room nextRoom = currentRoom.getRoom(direction);
            if (nextRoom != null && !nextRoom.isVisited()) {
                SolutionStep result = findExit(nextRoom);
                if (result != null) {
                    return new SolutionStep(direction, result);
                }
            }
        }
        return null;
    }
}


Leider tritt die gleiche Fehlermeldung immer noch auf. Ich denke dann liegt es wohl an den vorgegebenen JunitTests (auf welche ich leider keinen lokalen Zugriff habe). Verstehe trotzdem nicht warum die Lösung dann anscheinend iterativ implementiert werden soll... Und wieso ich jetzt dennoch einen StackOverFlow erhalte.
 
Vorschlag meinerseits wäre da mit deinem Betreuer/Tutor zu sprechen. Aus meiner Sicht sehe ich da keinen echten Fehler. Und ich bin bei @KonradN - ich würde auch nicht verstehen, warum die Lösung jetzt zwangsweise iterativ bauen muss.
 
Sicher, dass du die Version mit dem markVisited testest? Denn du hattest ja dieses Problem vorher nicht und die reine Fehlerbehebung sollte nur die letzten Probleme beheben und nicht zu so einer Exception führen.

Und du kannst den Code ja auch selbst testen. Mach einfach eine main Methode, in der du paar Räume erstellst und dann den Algorithmus von dir selbst aufrufst.
 
Danke für den Hinweis @KonradN, habe ich jetzt wieder eingefügt:

Java:
public class MazeSolver {

    public static SolutionStep findExit(Room currentRoom) {
        if (currentRoom.isExit()) {
            return new SolutionStep(null, null);
        }

        currentRoom.markVisited();

        for (Direction direction : Direction.values()) {
            Room nextRoom = currentRoom.getRoom(direction);
            if (nextRoom != null && !nextRoom.isVisited()) {
                SolutionStep result = findExit(nextRoom);
                if (result != null) {
                    return new SolutionStep(direction, result);
                }
            }
        }
        return null;
    }
}


Leider tritt die gleiche Fehlermeldung immer noch auf. Ich denke dann liegt es wohl an den vorgegebenen JunitTests (auf welche ich leider keinen lokalen Zugriff habe). Verstehe trotzdem nicht warum die Lösung dann anscheinend iterativ implementiert werden soll... Und wieso ich jetzt dennoch einen StackOverFlow erhalte.
Ich würde mal folgendes sagen, du bist via direction von a nach b gelangt. b stellt den Ausgang dar. Dann müsste es SolutionStep(currentRoom, null) in der Abbruchbedingung sein, wenn du da einen Weg aufbaust. Gegebenenfalls auch -direction (also die umgekehrte Seite), wenn rückwärts.
 
Ich würde mal folgendes sagen, du bist via direction von a nach b gelangt. b stellt den Ausgang dar. Dann müsste es SolutionStep(currentRoom, null) in der Abbruchbedingung sein, wenn du da einen Weg aufbaust. Gegebenenfalls auch -direction (also die umgekehrte Seite), wenn rückwärts.
Nein, in der Beschreibung ist das genau so wie im Code beschrieben. Der Raum, der ein Exit hat, der hat da das (null, null) und dann ist das immer der innere Knoten und der äußere Knoten ist die Richtung + dem SolutionStep, die es bis zum Exit hatte.

Das sieht also korrekt aus.
 
Durch die Markierung besuchter Räume sind Zyklen und so egal.

Was mir noch durch den Kopf geht: siehst Du Ausgaben der Unit Tests? Kannst Du mit System.out oder System.err etwas ausgeben und bekommst das auch als Ergebnis? Dann könntest Du Details ausgeben um zu sehen, was genau passiert.
 
Muss man beim Backtracking vor dem Verlassen einer frustran verlaufenen Rekursion nicht den ursprünglichen Zustand wieder herstellen? Ich vermisse ein
currentRoom.markUnvisited();
nach der for-each-Schleife.
 
Muss man beim Backtracking vor dem Verlassen einer frustran verlaufenen Rekursion nicht den ursprünglichen Zustand wieder herstellen? Ich vermisse ein
currentRoom.markUnvisited();
nach der for-each-Schleife.
Das ist nicht notwendig, da Du ja nur irgend eine Lösung haben willst. Du gehst ja alle Möglichkeiten durch von dem Raum. Wenn Du also bei einem Raum r1 bist und du alle Möglichkeiten durchgegangen bist von diesem Raum: Warum solltest Du ihn noch einmal anpacken? Von r1 aus gibt es keinen Weg zu einem Exit. Da ist es doch egal, ob Du von r2 oder r3 zu r1 gekommen bist.
 
Muss man beim Backtracking vor dem Verlassen einer frustran verlaufenen Rekursion nicht den ursprünglichen Zustand wieder herstellen? Ich vermisse ein
currentRoom.markUnvisited();
nach der for-each-Schleife.

Das ist notwendig. Prinzipiell kann man machen was man will, man kann es auch nennen wie man will (obwohl es dann Probleme mit der Kommunikation geben könnte, also kann man nennen wie man will für sich allein).

Aber es gibt best practices Lösungen, man macht sich das Leben einfacher, wenn man von anderen lernt.

Als Vergleich bietet es sich an, den flood fill algorithmus anzusehen.
 
Das ist notwendig.
Der wiederholte Hinweis ändert aber eben nichts daran, dass es eben nicht notwendig ist. Im Gegensatz dazu ist es sogar kontraproduktiv, da falsche Wege wiederholt geprüft werden.

Und wenn Du den Flood Fill Algorithmus anschaust, dann findest Du dort auch nicht das zurücksetzen in den ursprünglichen Zustand. Wäre ja auch blöd - Du willst z.B. alle Pixel umfärben - wieso solltest Du da einen umgefärbten Pixel wieder zur ursprünglichen Farbe zurück setzen?

Also einfach einmal selbst schauen:
 
Mal ein Beispiel:

1729166814447.png

Der Exit ist unten links. Es wäre ja cool, wenn der Ausgang direkt gefunden werden könnte. Aber ach ne, das geht ja nicht, weil Konrad vorschreibt, dass Markierungen nicht wieder rückgängig zu machen seien. Also durchlaufen wir halt alle Räume, um den schlechtmöglichsten Weg zu erhalten und die Unit-Tests fehlschlagen zu lassen. 🤣 Konrad und Logik und so. 😉
 
Moin Leute, tut mir leid @richtig, ich bin gestern tatsächlich eingeschlafen 😀 Also die 100% erreiche ich immer noch nicht. Aber verstehe ich das jetzt richtig? Ich soll nochmal versuchen markUnvisited() zu implementieren? Hier spalten sich ja anscheinend die Meinungen… Danke Euch allen!
 
Mal ein Beispiel:

Anhang anzeigen 23157

Der Exit ist unten links. Es wäre ja cool, wenn der Ausgang direkt gefunden werden könnte. Aber ach ne, das geht ja nicht, weil Konrad vorschreibt, dass Markierungen nicht wieder rückgängig zu machen seien. Also durchlaufen wir halt alle Räume, um den schlechtmöglichsten Weg zu erhalten und die Unit-Tests fehlschlagen zu lassen. 🤣 Konrad und Logik und so. 😉
Tobias: Du solltest die Aufgabe lesen und verstehen. Es geht eben nicht darum, den kürzesten Weg zu finden sondern es geht nur darum irgend einen Weg zu finden ...

Moin Leute, tut mir leid @richtig, ich bin gestern tatsächlich eingeschlafen 😀 Also die 100% erreiche ich immer noch nicht. Aber verstehe ich das jetzt richtig? Ich soll nochmal versuchen markUnvisited() zu implementieren? Hier spalten sich ja anscheinend die Meinungen… Danke Euch allen!
Nein, das ist weder notwendig noch sinnvoll bei dieser Aufgabe. Das würde nur mit deutlich mehr Änderungen Sinn machen (Wie die Länge des Weges zu merken und nach finden eines Weges nicht abzubrechen sondern weiter zu suchen ...)

Ansonsten ist @richtig und @erWeissEs der Forentroll "Tobias", der ständig gebannt wird und dann dennoch immer wieder mit neuen Accounts daher kommt. Es ist in 99% der Fälle immer am besten, ihn zu ignorieren...
 
Moin Leute, tut mir leid @richtig, ich bin gestern tatsächlich eingeschlafen 😀 Also die 100% erreiche ich immer noch nicht. Aber verstehe ich das jetzt richtig? Ich soll nochmal versuchen markUnvisited() zu implementieren? Hier spalten sich ja anscheinend die Meinungen… Danke Euch allen!
Nein, brauchst du nicht. @richtig kannst du ignorieren, das ist im sinnvollsten. Bzw. das Gegenteil machen, dann ist die Trefferquote > 50% 😉
 

Zurück
Oben