Java Ziegenproblem simulieren

CyborgBeta

Banned
Registriert
Jan. 2021
Beiträge
3.958
Moin,

ich bin mir nicht sicher, ob mein Simulationscode richtig ist, aber müsste statistisch gesehen die Wahrscheinlichkeit, die Ziege zu ziehen, nicht immer höher sein, wenn der Spieler nach der Frage des Quizmasters nicht die Tür wechselt?

Java:
import java.util.Random;

public class Goat {
    public static void main(String[] args) {
        int switchDoor = 0;
        int noSwitchDoor = 0;
        for (int i = 0; i < 1_000_000; i++) {
            switchDoor += simulate(true);
            noSwitchDoor += simulate(false);
        }
        System.out.printf("Switch door: %f%%%n", switchDoor / 10_000.);
        System.out.printf("No switch door: %f%%%n", noSwitchDoor / 10_000.);
    }

    private static int simulate(boolean switchDoor) {
        Random random = new Random();
        final int goatAt = random.nextInt(3);
        final int playerChoose1 = random.nextInt(3);
        if (goatAt == playerChoose1) {
            // The player has chosen the door with the goat behind it.
            return 0;
        }
        int playerChoose2;
        do {
            playerChoose2 = random.nextInt(3);
        } while (playerChoose2 == playerChoose1);
        // Ask the player to switch the chosen door:
        if (switchDoor) {
            playerChoose2 = playerChoose1 ^ playerChoose2 ^ 3;
        }
        if (playerChoose2 == goatAt) {
            // The player has chosen the door with the goat behind it.
            return 1;
        }
        // The player has chosen the door with the car behind it.
        return 2;
    }
}

Edit: Glaube, sehe den Fehler schon, der Quizmaster öffnet ja zunächst eine der beiden nicht gewählten Türen ... 😬

Edit 2: Das sollte jetzt auch richtig sein ... die Wahrscheinlichkeiten stimmen mit denen auf Wikipedia überein:

Java:
import java.util.Random;

public class Goat {
    private static final Random random = new Random();

    public static void main(String[] args) {
        int switchDoor = 0;
        int noSwitchDoor = 0;
        for (int i = 0; i < 1_000; i++) {
            switchDoor += simulate(true);
            noSwitchDoor += simulate(false);
        }
        System.out.printf("Switch door: %f%%%n", switchDoor / 10.);
        System.out.printf("No switch door: %f%%%n", noSwitchDoor / 10.);
    }

    private static int simulate(boolean switchDoor) {
        final int winAt = random.nextInt(3);
        final int playerChoose1 = random.nextInt(3);
        int quizMasterChoose;
        // The quiz master chooses a door that is not the one the player has chosen, and not the one with the car behind it:
        do {
            quizMasterChoose = random.nextInt(3);
        } while (quizMasterChoose == playerChoose1 || quizMasterChoose == winAt);
        // Ask the player to switch the chosen door:
        final int playerChoose2 = switchDoor ? playerChoose1 ^ quizMasterChoose ^ 3 : playerChoose1;
        if (playerChoose2 == winAt) {
            // The player has chosen the door with the car behind it.
            return 1;
        }
        // The player has chosen the door with a goat behind it.
        return 0;
    }
}
 
Zuletzt bearbeitet:
Klassischer Fall von Rubber-Duck-Debugging 😉
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: CyborgBeta, Innensechskant, Der Lord und 3 andere
Solange am Ende rauskommt dass bei Türwechsel die Gewinnchance 2/3 bzw. 66,66% beträgt hast alles richtig gemacht.
 
  • Gefällt mir
Reaktionen: CyborgBeta
SaxnPaule schrieb:
Klassischer Fall von Rubber-Duck-Debugging 😉
Ey! Ich mag meine Ente :D

duck-spinning.gif

Ergänzung ()

Edit: Das Problem war eher ... dass ich anfangs die Spielregeln nicht richtig verstanden hatte. 😬 Wenn man das Problem erst versteht, ist man auch schon einen Schritt weiter. :D
Ergänzung ()

Der gedankliche Trick ist aber, dass die Wahrscheinlichkeiten für die verbleibenden zwei Türchen nicht mehr gleichverteilt sind, nachdem der Quizmaster ein Ziegentürchen geöffnet hat, und sich der Spieler so einen Vorteil verschaffen kann, wenn er wechselt ...

Fragt mich aber jetzt bitte nicht (nach einer logischen Begründung), weshalb das so ist.
 
Zuletzt bearbeitet:
Der grundlegendste Punkt dabei ist - was du ja selbst gemerkt hast als du es vergessen hast zu implementieren - dass der Quizmaster eine falsche Tuer oeffnet. Er oeffnet eben keine zufaellige Tuer, sondern immer eine hinter der eine Ziege ist.

Die Erklaerung auf der englischen Wikipedia dazu ist recht gut:
https://en.wikipedia.org/wiki/Monty_Hall_problem

An intuitive explanation is that, if the contestant initially picks a goat (2 of 3 doors), the contestant will win the car by switching because the other goat can no longer be picked – the host had to reveal its location – whereas if the contestant initially picks the car (1 of 3 doors), the contestant will not win the car by switching.
Using the switching strategy, winning or losing thus only depends on whether the contestant has initially chosen a goat (⁠2/3⁠ probability) or the car (⁠1/3⁠ probability). The fact that the host subsequently reveals a goat in one of the unchosen doors changes nothing about the initial probability.
 
  • Gefällt mir
Reaktionen: CyborgBeta
By the way ...

int quizMasterChoose = winAt ^ playerChoose1 ^ 3;

geht übrigens nicht, weil der Spieler ja auch schon bereits das Auto-Türchen gewählt haben könnte ...

Also eine do-while-Schleife.
 
CyborgBeta schrieb:
Fragt mich aber jetzt bitte nicht (nach einer logischen Begründung), weshalb das so ist.

Die Begründung ist einfach: Am Anfang hast du drei Türen, 2x Ziege, 1x Gewinn.
Du wählst random eine Tür, hast also 1/3 Chance auf Gewinn.
DANN wählt der Moderator eine Tür aus hinter der eine Ziege ist und öffnet die und DANN darfst du wählen ob du Tür wechselst oder nicht.

Wie sind am Anfang die Chancen verteilt:
1/3 auf die gewählte Tür und 2/3 auf die nicht gewählten Türen.
Jetzt fällt eine der nicht gewählten Türen weg und die 2/3 Wahrscheinlichkeit verteilt sich nicht mehr auf zwei sondern nurnoch auf eine Tür.
Daher hat man bei Wechsel eine Gewinnwahrscheinlichkeit von 2/3 und wenn man bei der Anfangstür bleibt bei 1/3

Du wählst am Ende quasi die addierte Wahrscheinlichkeit der beiden anderen Türen, verteilt auf die verbleibende eine andere Tür.

Oder man spielt es durch:

Sagen wir mal Tür 1 ist immer Gewinn und Tür 2 und 3 immer Ziege. Der Moderator öffnet immer nur Ziegentüren die man nicht gewählt hat, das ist wichtig.

Gewählte Tür mit Wechsel:
Tür 1: Gewinn, nach Wechsel: Ziege
Tür 2: Ziege, nach Wechsel und andere Ziegentür öffnen bleibt nurnoch Tür mit Gewinn über
Tür 3: Ziege, nach Wechsel und andere Ziegentür öffnen bleibt nurnoch Tür mit Gewinn über

Wechselt man hingegen nicht siehts so aus
Tür 1: Gewinn, nach Ziegentür öffnen: Gewinn
Tür 2: Ziege, nach andere Ziegentür öffnen: Verloren
Tür 3: Ziege, nach andere Ziegentür öffnen: Verloren

Heisst, egal welche Tür man anfangs wählt, in 2 von 3 Fällen gewinnt man wenn man die Tür wechselt nachdem eine Ziegentür geöffnet wurde. Bleibt man hingegen bei der Anfangstür und wechselt nicht, gewinnt man nur in 1/3 Fällen.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: Arc Angeling, BeBur und CyborgBeta
Ich finde übrigens die Zeilen mit bitweisem Oder nicht unbedingt super lesbar.
Auch, weil dieser Ansatz der Erklärung, die ein intuitives Verständnis schafft, entgegenläuft.

Die intuitive Erklärung für das Ziegenproblem ist nämlich ganz einfach, wenn man das Problem auf viele Türen erweitert und dann gerade die eine Variante wählt, die das ganze intuitv logisch werden lässt:

Ich habe n=1000 Türen.
Hinter n-1=999 sind Ziegen, hinter einer ein Auto.
Nun wählt der Spieler eine Tür.
Anschließend öffnet der Moderator n-2=998 Türen mit Ziege. Ganz offensichtlich sind diese Türen nicht zufällig gewählt. Bei n=3 öffnet der Moderator somit eine Türe mit Ziege.
Nun fragt der Moderator, ob der Spieler bei seiner ersten Wahl bleiben will, oder ob er auf die andere der zwei noch offenen Türen wechseln möchte.

Man könnte das Spiel auch auf andere Art und Weise auf mehr Türen verallgemeinern. Würde der Moderator statt n-2 Türen mit Ziege nur genau eine Türe mit Ziege öffnen, so würden danach noch n-2 Türen übrigbleiben, die verschlossen sind und nicht vom Spieler gewählt wurden.
Würde man nun fragen, ob der Spieler (zufällig) eine dieser noch verschlossenen Türen wählen möchte, dann würde das ebenfalls seine Gewinnwahrscheinlichkeit erhöhen. Aber im Fall n=1000 nur marginal (und zwar von 1/n auf (n-1)/n * 1/(n-2) bzw. auf 999/998000 oder etwa 1,001‰.)

Für den Fall n=3 sind die beiden Szenarien identisch und führen dazu, dass die Gewinnwahrscheinlichkeit durch Wechsel sich auf 2/3 erhöht.
 
  • Gefällt mir
Reaktionen: Arc Angeling, BeBur und CyborgBeta
simpsonsfan schrieb:
Ich finde übrigens die Zeilen mit bitweisem Oder nicht unbedingt super lesbar.
Dann nenne eine kompaktere Alternative.
Ergänzung ()

Ich denke, ich hab's verstanden. Auch wenn es der natürlichen Intuition zuwiderläuft, dass meine Wahl durch die Wahl eines anderen beeinflusst wird. Aber es ist eben nur oberflächlich eine Wahl, denn eigentlich ist es ein (versteckter) "Hinweis" vom Quizmaster.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: BeBur
Ich bin aber gar nicht der Meinung, dass Kompaktheit mit Lesbarkeit gleichzusetzen ist. Daher werde ich auch gar nicht erst versuchen, eine kompaktere, lesbarere Schreibweise zu nennen.
Ich schließe nicht aus, dass es die geben kann, aber man sollte mMn nicht versuchen, mit so wenig Zeichen wie möglich zu programmieren. Speicherlpatz für Code ist billig und reichtlich vorhanden. Und Java wird ohnehin kompiliert.
 
simpsonsfan schrieb:
lesbarere Schreibweise
Das trifft aber in dem Fall nicht zu. Die Schreibweise ist vielleicht ungewöhnlich, aber nicht unlesbar.

Aber egal. Ich will mich wegen so etwas nicht fetzen.
 
Auch wenn niemand gefragt hat, hier mal was von mir.
Man sieht die Prämisse, dass der Kandidat die Wahl hat, die Türe zu wechseln und dann entweder durch Wechsel oder durch NichtWechsel gewinnt. Und wer möchte kann natürlich auch das Array an Türen ausgeben oder im Debugger betrachten.

Java:
import java.util.Random;

public class Main
{
    private static final int N_DOORS = 3;
    private static final short CAR = 1;  // represents a car behind a door in the door array
    private static final short GOAT = 0; // represents a goat behind a door in the door array
    public static void main(String[] args) {
        simulateSpecifiedNumberOfRuns(1_000);
    }
    
    /**
     * Simulate n runs of the Monty Hall goat game
     * and print statistics.
     * @param n Number of runs to simulate.
     */
    public static void simulateSpecifiedNumberOfRuns(int n) {
        System.out.println("Simulating Monty Hall problem with " +
            N_DOORS + " doors for " + n + " runs.");
        
        int nWinsWithoutSwitch = 0;
        int nWinsWithSwitch = 0;
        for (int i=0; i<n; i++) {
            if (simulateRunWithSpecifiedNumberOfDoors(N_DOORS)) {
                nWinsWithoutSwitch++;
            } else {
                nWinsWithSwitch++;
            }
        }
        System.out.printf("Number of times, the player would have won by switching: %d (%.1f%%)\n",
            nWinsWithSwitch, 100.*nWinsWithSwitch/n);
        System.out.printf("Number of times, the player would have won by not switching: %d (%.1f%%)\n",
            nWinsWithoutSwitch, 100.*nWinsWithoutSwitch/n);
    }
    
    /**
     * Simulate one run of the Monty Hall problem.
     *
     * The car is put behind a random door. Player chooses a door at random.
     * Gamemaster then opens all doors except for the players' choice and one other.
     * The gamemaster will never open the door with the car behind it.
     * Player can either switch to the other unopened door or keep the first choice.
     * This method will return true if the first choice would have won, false otherwise.
     *
     * @param nDoors total number of doors in the game (standard is 3)
     * @return true if the player would have won with the initial choice, false
     *   if the player would have won with switching.
     */
    public static boolean simulateRunWithSpecifiedNumberOfDoors(int nDoors) {
        Random random = new Random();
        // Array representing what's behind each door. Mark goat with 0, car with 1. i-th array element is i-th door.
        short[] doorArray = new short[nDoors];  // is initialized to all zero / goats.
        final int winningDoor = random.nextInt(nDoors);
        doorArray[winningDoor] = CAR;
        final int playerInitialChoice = random.nextInt(nDoors);
        // at this point, the result is clear already. But we go on to simulate outcomes.
        
        /** gamemaster chooses one door, other than the players' choice, to keep closed.
          * If player has chosen the door with the car, gamemaster chooses one of the
          * goat doors at random, otherwise, gamemaster chooses the door with the car to keep closed. */
        int secondDoorToKeepClosed;
        if (CAR == doorArray[playerInitialChoice]) {
            do {
                secondDoorToKeepClosed = random.nextInt(nDoors);
            } while (secondDoorToKeepClosed == playerInitialChoice);  // need a door different from the player's
        } else {
            secondDoorToKeepClosed = winningDoor;  // do not open the car door
        }
        
        // player now has the option to switch to the other closed door or keep the initial choice
        if (CAR == doorArray[playerInitialChoice]) return true; // player wins by not switching
        if (CAR == doorArray[secondDoorToKeepClosed]) return false; // player wins by switching
        
        throw new RuntimeException("There is no car! This line should never be reached. Play around with the contents of doorArray.");
    }
}
 
@simpsonsfan Sorry, aber schwer zu lesen i-wie ... auch aufgrund der Kommentare.

Du hast das jetzt zu stark vereinfacht. Zudem gewinnt der Spieler immer, was in der Realität nicht so ist.

Zudem auch ein technischer Fehler in Zeile 74. Wenn Zeile 72 immer zutrifft, dann entfällt das vorherige if-Konstrukt.
 
Logischerweise trifft das if in Zeile 72 nicht immer zu.
Und der Spieler kann entweder durch Wechsel oder durch Nicht-Wechsel gewinnen, es gibt keine andere Möglichkeit. Es kann nicht sein, dass weder Wechsel noch Nicht-Wechsel gewinnt.
 
simpsonsfan schrieb:
Logischerweise trifft das if in Zeile 72 nicht immer zu.
simpsonsfan schrieb:
Es kann nicht sein, dass weder Wechsel noch Nicht-Wechsel gewinnt.
Lies noch mal. Du widersprichst dir gerade selbst.

Und wenn das nicht der Fall wäre, würde dich der Compiler darauf hinweisen. Der ist in Java nämlich sehr stark.

Ich habe jetzt keine Lust, ein formales Code-Review zu machen ... aber du hättest von mir kein pauschales Approval bekommen.
 
Zurück
Oben