Testing the Monty Hall problem using a Python program to run game simulations

If a search brought you here then you probably already know what the Monty Hall problem is. If not, and in case you are interested, the Wikipedia page explains it in detail.

In a nutshell: the game comes from a gameshow called Let’s Make a Deal, in which the contestant is presented with three doors, behind one of which is a car which will be won if the contestant chooses that door. Once the contestant has made their initial choice from the three doors, the gameshow host Monty Hall will open one of the other doors to show that the car is not behind that door. The contestant is then given a choice of staying with their original choice of door, or switching to the other remaining door which has not yet been opened.

I recently encountered the Monty Hall problem for the first time and succumbed to the logic failure that most people succumb to, i.e. feeling certain that staying on my original choice had exactly the same probability of winning as switching to the other available door.

Due to my initial difficulty in comprehending that switching doors actually gives approximately double the chance of winning, I decided to take the scientific approach of writing a Python program to test this out. The results of running this clearly demonstrated that my 50/50 belief was wrong, and that in fact one should always switch because doing so approximately doubles the chance of winning.

There are plenty of other similar programs out there that people have written to test this, but they generally seem unnecessarily complicated. To test this satisfactorily it’s only necessary to choose Door 1 initially, then compare the results of staying with Door 1 vs switching to whichever one of Doors 2 and 3 is not already open.

To that end, here is my code:

import random

runs = 10 # total runs
games = 1000 # number of games per run for each choice

# Function to run the game simulations based on 
# choice of "stay" or "switch"
def play_games(choice):
  wins = 0

  # When running the game simulations we can assume 
  # an initial choice of door 1 each time. 
  # There's no need to make it any more complex than that
  for i in range(games):
    # Randomly select one of the three doors for the car to be behind
    car_door = random.randrange(1,4)
    # If we stay on door 1 we only need to know if the car is behind
    # door 1 (in which case we win) or not
    if choice == 'stay' and car_door == 1:
      wins += 1
    # If we switch to the other door (2 or 3, whichever of those two is
    # not already open) then we only need to know if the car is not
    # behind door 1 (in which case we win)
    elif choice == 'switch' and car_door != 1:
      wins += 1

  return(wins)

for j in range(runs):
  print('\nrun:\t' + str(j + 1))

  game_wins = {}

  # Run the game simulations for both choices, and print the results
  for choice in ('stay', 'switch'):
    game_wins[choice] = play_games(choice)
    print(choice + ':\t' + str(game_wins[choice]) + ' wins out of ' + str(games) + ' games')

  # Determine the win ratio of switches to stays and print the result
  win_ratio = game_wins['switch'] / game_wins['stay']
  print('result:\t"switch" won ' + str(round(win_ratio, 2)) + ' times as often as "stay"')

print('')

The results of the game simulations are very clear:

$ python3 ./montyhall.py 

run:	1
stay:	310 wins out of 1000 games
switch:	674 wins out of 1000 games
result:	"switch" won 2.17 times as often as "stay"

run:	2
stay:	350 wins out of 1000 games
switch:	667 wins out of 1000 games
result:	"switch" won 1.91 times as often as "stay"

run:	3
stay:	307 wins out of 1000 games
switch:	651 wins out of 1000 games
result:	"switch" won 2.12 times as often as "stay"

run:	4
stay:	336 wins out of 1000 games
switch:	670 wins out of 1000 games
result:	"switch" won 1.99 times as often as "stay"

run:	5
stay:	341 wins out of 1000 games
switch:	638 wins out of 1000 games
result:	"switch" won 1.87 times as often as "stay"

run:	6
stay:	322 wins out of 1000 games
switch:	679 wins out of 1000 games
result:	"switch" won 2.11 times as often as "stay"

run:	7
stay:	355 wins out of 1000 games
switch:	668 wins out of 1000 games
result:	"switch" won 1.88 times as often as "stay"

run:	8
stay:	311 wins out of 1000 games
switch:	665 wins out of 1000 games
result:	"switch" won 2.14 times as often as "stay"

run:	9
stay:	338 wins out of 1000 games
switch:	668 wins out of 1000 games
result:	"switch" won 1.98 times as often as "stay"

run:	10
stay:	358 wins out of 1000 games
switch:	646 wins out of 1000 games
result:	"switch" won 1.8 times as often as "stay"

It took me some time to comprehend why switching always roughly doubles your chances of winning. If you’re also having some difficulty getting your head around it, this video of Derren Brown explaining how his version of the game works is helpful for grasping the logic. Enjoy.

How to set up a Kubernetes cluster with minikube and then with Amazon EKS

Purpose of this tutorial project

Our goal is to create a Kubernetes cluster serving the output of simple-webapp via nginx. simple-webapp is a simple Python app I wrote for these kinds of projects, which outputs a basic web page as proof of concept. In a real production environment, this would be a full-blown web application of some kind.

The Kubernetes cluster will consist of the following:

  • Two cluster Nodes.
  • A simple-webapp Deployment consisting of four Pods, each running the simple-webapp container, exposed internally to nginx via a ClusterIP Service.
  • An nginx Deployment consisting of four Pods, each running an nginx container with a modified nginx.conf file made available via a ConfigMap which allows nginx to reverse-proxy traffic to the simple-webapp service, exposed externally via a LoadBalancer Service.
Continue reading “How to set up a Kubernetes cluster with minikube and then with Amazon EKS”

Genrify: Python app to filter Spotify library based on genre

Taking a temporary departure from my usual Infrastructure/DevOps articles, this article is about “Genrify”, a Python app I’ve written to select tracks from Saved Albums or Playlists on Spotify based on Artist Genre and add them to Queue or to a new Playlist.

I got frustrated at having a Spotify library of added albums and created playlists, but not being able to query my library by genre. Basically, I wanted functionality like Smart Playlists in Apple’s Music app (previously iTunes), where it’s possible to say something like “select all songs from recently added albums where the genre is darkwave”. Therefore, with the goal in mind of being able to do something similar with my Spotify library, I created this Python 3 app called Genrify which uses Spotify’s API via the Spotipy Python library to achieve this functionality.

Continue reading “Genrify: Python app to filter Spotify library based on genre”

How to use Ansible to provision an EC2 instance with an app running in a Docker container

I created this suite of Ansible playbooks to provision a basic AWS (Amazon Web Services) infrastructure on EC2 with a Staging instance, and to deploy a webapp on the Staging instance which runs in a Docker container, pulled from Docker Hub.

Firstly a Docker image is built locally and pushed to a private Docker Hub repository, then the EC2 SSH key and Security Groups are created, then a Staging instance is provisioned. Next, the Docker image is pulled on the Staging instance, then a Docker container is started from the image, with nginx set up on the Staging instance to proxy web requests to the container. Finally, a DNS entry is added for the Staging instance in Route 53.

This is a simple Ansible framework to serve as a basis for building Docker images for your webapp and deploying them as containers on Amazon EC2. It can be expanded in multiple ways, the most obvious being to add an auto-scaled Production environment with Docker containers and a load balancer. (For Ansible playbooks suitable for provisioning an auto-scaled Production environment, check out my previous article and associated files “How to use Ansible for automated AWS provisioning”.) More complex apps could be split across multiple Docker containers for handling front-end and back-end components, so this could also be added as needed.

Continue reading “How to use Ansible to provision an EC2 instance with an app running in a Docker container”

How to use Ansible for automated AWS provisioning

I’ve recently produced a series of articles aimed at startups, entrepreneurial solo developers, etc. wanting to take their first steps into Amazon Web Services (AWS) setups for app deployment:

I then wanted to move on from discussing manual setup via the GUI interface of the AWS web console, to DevOps-style command-line programmatic setup for automated provisioning of an AWS infrastructure for app deployment, i.e. infrastructure as code (IaC). I have therefore created a suite of Ansible playbooks to provision an entire AWS infrastructure with a Staging instance and an auto-scaled load-balanced Production environment, and to deploy a webapp thereon. The resulting set of Ansible AWS provisioning playbooks and associated files can be found in a repository on my GitHub, so go ahead and grab it from there if you want to try them out. Keep reading for information on how to set up and use the playbooks (and you can also refer to the README in the repo folder, which contains much of the same information).

With these playbooks, firstly the EC2 SSH key and Security Groups are created, then a Staging instance is provisioned, then the webapp is deployed on Staging from GitHub, then an image is taken from which to provision the Production environment. The Production environment is set up with auto-scaled EC2 instances running behind a load balancer. Finally, DNS entries are added for the Production and Staging environments.

Continue reading “How to use Ansible for automated AWS provisioning”

Building a Postfix-based mail system for incoming and outgoing email, capable of successfully sending one million emails per day

It was necessary to build an updated mail system for a client which would handle all incoming and outgoing email, and which could handle successfully sending out an average of one million emails per day. This was based on Postfix, since Postfix is known for reliability, robustness, security, and relative ease of administration. Building a Postfix mail system capable of handling so many emails is quite a significant aim at a time when establishing a positive reputation for independent mail servers delivering high volumes of email is quite a challenging goal.

Continue reading “Building a Postfix-based mail system for incoming and outgoing email, capable of successfully sending one million emails per day”