2
\$\begingroup\$

I'm trying to simulate the Monty Hall problem, to statistically determine if there is any benefit to changing my choice after a door containing a goat is open (testing this guy's Monty Hall theory on YouTube).

So, does this manage to simulate it properly? (Just for the curious: Changing doors doesn't seem to have any benefit, but I want to make sure my simulator isn't flawed). I also want style help, if possible!

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define DOOR_COUNT 3
#define TEST_COUNT 1000000

void InitRandom();
int RunTest(char change);
void RandomizeDoors();
int GetRandom(int floor, int roof);
void OpenGoatDoor (int choice);
int OtherDoor (int choice);
int ChoiceWasCar(int choice);

char doors[DOOR_COUNT+1];

int main (const char argc, const char **argv)
{
    int run[2], yes[2];
    InitRandom();

    run[0] = 0;
    run[1] = 0;
    yes[0] = 0;
    yes[1] = 0;
    // Control Group
    for (int i = 0; i < TEST_COUNT; i++ ) {
        run[0]++;
        yes[0] += RunTest(0);
    }
    for (int i = 0; i < TEST_COUNT; i++ ) {
        run[1]++;
        yes[1] += RunTest(1);
    }
    printf("Control Group:\n\t%d tests run\n\t%d yes.\n\n", run[0], yes[0]);
    printf("Other Group:\n\t%d tests run\n\t%d yes.\n\n", run[1], yes[1]);
}

void InitRandom()
{
    unsigned int iseed = (unsigned int)time(NULL);
    srand (iseed);
}

int RunTest(char change)
{
    RandomizeDoors();
    int choice = GetRandom(1, DOOR_COUNT);
    //printf("Choice is: %d\n", choice);
    OpenGoatDoor(choice);
    if (change) {
        choice = OtherDoor(choice);
    }
    return ChoiceWasCar(choice);
}

void RandomizeDoors()
{
    char n = GetRandom(1,DOOR_COUNT);
    for (int i = 0; i <= DOOR_COUNT; i++) {
        doors[i] = n == i ? 1 : 0;
        //printf("Door %d is %d\n", i, doors[i]);
    }
//  printf("\n");
}

int GetRandom(int floor, int roof)
{
    int n;
    n = (rand()%(roof-floor));
    n += floor;
    return n;
}

void OpenGoatDoor (int choice)
{
    int doorCur = GetRandom(1, DOOR_COUNT);
    while ( doors[doorCur] == 1 || doorCur == choice ) {
        if (doorCur > DOOR_COUNT) {
            doorCur = GetRandom(1, DOOR_COUNT);
        } else {
            doorCur++;
        }
    }
    //printf("Opened Door is %d\n", doorCur);
    doors[doorCur] = -1;
}

int OtherDoor (int choice)
{
    int doorCur = 1;
    while ( doorCur == choice || doors[doorCur] == -1 ) {
        doorCur++;
    }
    return doorCur;
}

int ChoiceWasCar(int choice)
{
    return doors[choice] == 1;
}
\$\endgroup\$
  • \$\begingroup\$ this question might be better suited for programmers.stackexchange.com rather than here. how have you tested this code to make sure that it works? \$\endgroup\$ – Malachi Oct 25 '13 at 20:52
  • \$\begingroup\$ will post an answer this evening (or delete this comment!). would be frustrating if you moved it while i was working on it... \$\endgroup\$ – andrew cooke Oct 25 '13 at 21:27
  • 1
    \$\begingroup\$ Your GetRandom() is a tiny bit biased: see Generating a uniform distribution of integers in C. \$\endgroup\$ – 200_success Oct 27 '13 at 8:33
  • \$\begingroup\$ void InitRandom(); should be void InitRandom(void);. void InitRandom(); will accept any arguments. \$\endgroup\$ – Banthar Oct 27 '13 at 10:35
2
\$\begingroup\$

ok, so i ended up changing more than was strictly necessary, sorry. as you probably know, it does pay to change doors, so your code did have a bug. i didn't try to trace it down exactly, but i assume it was related to you having 4 doors instead of 3, which i guess was because you had

for (int i = 0; i <= DOOR_COUNT; i++) {

instead of

for (int i = 0; i < DOOR_COUNT; i++) {

in C you always start from 0 and use < in loops. it's the law.

[in general your code is super neat and pretty good. you had a muddle with array indexing, which is very common, and didn't make use of enum, but apart from that, it was nice code. edit: also, your general approach was fine. this is a good way to test the problem.]

some other issues were:

  • the random number generation was strangely general, yet always picked a door. it also started from 1, which i guess is related to the confusion over door array indexing.

  • it wasn't clear to me what the doors array contained at first. when i understood i clarified it with an enum.

  • it wasn't clear to me what the two groups were ("control" didn't explain things). when i understood i clarified things with another enum (note the GROUP_COUNT trick which gives the number of entries in the enum, excluding that).

  • given the group enum, there's no need to have two print statements - we can use an array of group names. and there's no need for arrays of results, since we have everything in a loop now.

  • i clarified the logic in OpenGoatDoor - the code initialises the door to choice, which we know is "bad" so that the while is triggered and so we only need a single call to RandomDoor.

  • i tried to clarify the logic in OtherDoor but i am not sure i helped.

  • i don't think ChoiceWasCar is necessary - with the enum it's pretty clear what return doors[choice] == car means.

here's the modified code. thanks for posting - i was bored and this was interesting to look at.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#undef DEBUG
#define DOOR_COUNT 3
#define TEST_COUNT 1000000

typedef enum {goat, car, opened} door;
door doors[DOOR_COUNT];

typedef enum {nochange, change, GROUP_COUNT} group;
char *groupName[GROUP_COUNT] = {"No Change", "Change"};

void InitRandom();
int RunTest(group g);
void RandomizeDoors();
int RandomDoor();
void OpenGoatDoor(int choice);
int OtherDoor(int choice);

int main (void)
{
  InitRandom();

  for (group g = 0; g < GROUP_COUNT; ++g) {
    int wins = 0;
    for (int i = 0; i < TEST_COUNT; ++i) {
      wins += RunTest(g);
    }
    printf("%s:\n\t%d tests run\n\t%d win car.\n\n", 
           groupName[g], TEST_COUNT, wins);
  }
  return 0;
}

void InitRandom()
{
  unsigned int iseed = (unsigned int)time(NULL);
  srand (iseed);
}

int RunTest(group g)
{
  RandomizeDoors();
  int choice = RandomDoor();
  #ifdef DEBUG
  printf("Choice is: %d\n", choice);
  #endif
  OpenGoatDoor(choice);
  if (g == change) {
    choice = OtherDoor(choice);
  }
  return doors[choice] == car;
}

void RandomizeDoors()
{
  char carDoor = RandomDoor();
  for (int i = 0; i < DOOR_COUNT; i++) {
    doors[i] = carDoor == i ? car : goat;
    #ifdef DEBUG
    printf("Door %d is %d\n", i, doors[i]);
    #endif
  }
  #ifdef DEBUG
  printf("\n");
  #endif
}

int RandomDoor()
{
  // this is very slightly biased, but we don't care.
  return rand() % DOOR_COUNT;
}

void OpenGoatDoor(int choice)
{
  int randomDoor = choice;
  while ( randomDoor == choice || doors[randomDoor] != goat) {
    randomDoor = RandomDoor();
  }
  #ifdef DEBUG
  printf("Opened Door is %d\n", randomDoor);
  #endif
  doors[randomDoor] = opened;
}

int OtherDoor(int choice)
{
  int otherDoor = choice;
  while ( otherDoor == choice || doors[otherDoor] == opened ) {
    otherDoor = (otherDoor + 1) % DOOR_COUNT;
  }
  return otherDoor;
}

for anyone following along at home, gcc needs -std=c99.

output:

: gcc -std=c99 mhall.c; ./a.out
No Change:
        1000000 tests run
        333875 win car.

Change:
        1000000 tests run
        667098 win car.
\$\endgroup\$
  • \$\begingroup\$ I got monty.c:22:5: error: first parameter of 'main' (argument count) must be of type 'int', and edited your code to fix it. \$\endgroup\$ – 200_success Oct 27 '13 at 8:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.