-- I1M 2021-22: Relación 25
-- Funciones de entrada/salida: El dilema de Monty Hall
-- Departamento de Ciencias de la Computación e Inteligencia Artificial
-- Universidad de Sevilla
-- ============================================================================
-- ============================================================================
-- Librerías auxiliares
-- ============================================================================
import System.Random
import Data.List
import Data.Char
-- ============================================================================
-- Monty Hall es el nombre de un conocido presentador de televisión en
-- Estados Unidos. En uno de sus concursos, se presenta el siguiente dilema:
--
-- El concursante debe elegir una de entre tres puertas cerradas y el premio
-- consiste en llevarse lo que se encuentra detrás de la puerta elegida.
-- Se sabe con certeza que una de las puertas encierra un coche y que las otras
-- dos tienen sendas cabras (se entiende que el concursante prefiere llevarse
-- el coche antes que una cabra).
--
-- Una vez que el concursante haya elegido una puerta, el presentador, que sabe
-- lo que encierra cada puerta, abrirá una de las otras dos, mostrando siempre
-- una cabra.
--
-- A continuación, le da la opción al concursante de cambiar, si lo desea, de
-- puerta o quedarse con su elección original.
--
-- Vamos a plantear un programa interactivo que permita recrear la
-- participación del concursante en el dilema de Monty Hall
-- ============================================================================
-- ----------------------------------------------------------------------------
-- Representación
-- ----------------------------------------------------------------------------
-- Denominaremos a las puertas por los enteros 1, 2 y 3.
-- ----------------------------------------------------------------------------
-- Ejercicio 1. Definir la acción de E/S
-- escojaPuerta :: IO Int
-- que interactúe con el jugador para que decida qué puerta escoger, dando los
-- correspondientes mensajes (se debe devolver un entero entre 1 y 3),
-- por ejemplo:
-- escojaPuerta =>
-- Que puerta elige? c
-- ERROR: Entrada incorrecta
-- Que puerta elige? 4
-- ERROR: Entrada incorrecta
-- Que puerta elige? 2
-- 2
-- ----------------------------------------------------------------------------
escojaPuerta :: IO Int
escojaPuerta = do
putStr "Que puerta elige? "
c <- getChar
getChar
let puerta = read [c]
if isDigit c && puerta >= 1 && puerta <= 3 then
return puerta
else do
putStrLn "ERROR: Entrada incorrecta"
escojaPuerta
-- ----------------------------------------------------------------------------
-- Ejercicio 2. Definir la acción
-- ponPremio :: IO Int
-- tal que devuelva la puerta que contiene el premio del concurso.
-- Para ello se debe seleccionar de forma aleatoria un valor entero entre 1 y 3:
-- ponPremio =>
-- 3
-- ----------------------------------------------------------------------------
ponPremio :: IO Int
ponPremio = randomRIO(1::Int,3)
-- ----------------------------------------------------------------------------
-- Ejercicio 3. Definir la función
-- abrePuerta :: Int -> Int -> IO Int
-- tal que (abrePuerta concursante premio) devuelve el número de una puerta que
-- abre el presentador. La puerta devuelta nunca puede ser la que contiene el
-- premio ni la que ha escogido el concursante, y, en caso de que el jugador
-- haya elegido la puerta con premio, se debe escoger aleatoriamente entre las
-- otras dos.
-- abrePuerta 1 1 =>
-- 2 (pero podía haber sido la 3)
-- abrePuerta 1 1 =>
-- 3 (pero podía haber sido la 2)
-- abrePuerta 1 2 =>
-- 3 (obligada)
-- abrePuerta 1 3 =>
-- 2 (obligada)
-- ----------------------------------------------------------------------------
abrePuerta :: Int -> Int -> IO Int
abrePuerta concursante premio = do
x <- randomRIO(1::Int,3)
if x == concursante || x == premio then
abrePuerta concursante premio
else
return x
-- ----------------------------------------------------------------------------
-- Ejercicio 4. Definir la acción
-- otraPuerta :: Int -> Int -> Int
-- tal que (otraPuerta concursante abierta) devuelve el número de la puerta que
-- no es igual a la del concursante ni a la que ya está abierta (se supone
-- que "concursante" es distinto de "abierta").
-- otraPuerta 1 2 =>
-- 3
-- otraPuerta 2 1 =>
-- 3
-- otraPuerta 3 1 =>
-- 2
-- ----------------------------------------------------------------------------
otraPuerta :: Int -> Int -> Int
otraPuerta concursante abierta = 6 - concursante - abierta
-- ----------------------------------------------------------------------------
-- Ejercicio 5. Definir la acción
-- cambiaPuerta :: Int -> Int -> IO Int
-- tal que (cambiaPuerta concursante cerrada) devuelve el número de la puerta
-- que el concursante al final termina escogiendo después de haberle dado a
-- elegir entre "mantener" la elección original "concursante" o "cambiar" a
-- la "cerrada".
-- cambiaPuerta 1 3 =>
-- Concursante, digame, prefiere mantener su decision original, la puerta 1 [mantener]
-- o quiere cambiar a la puerta 3 [cambiar]? mantener
-- 1
-- Concursante, digame, prefiere mantener su decision original, la puerta 1 [mantener]
-- o quiere cambiar a la puerta 3 [cambiar]? cambiar
-- 3
-- ----------------------------------------------------------------------------
cambiaPuerta :: Int -> Int -> IO Int
cambiaPuerta concursante cerrada = do
putStr "Concursante, digame, prefiere mantener su decision original, la puerta "
putStr (show concursante)
putStrLn " [mantener]"
putStr "o quiere cambiar a la puerta "
putStr (show cerrada)
putStr " [cambiar]? "
x <- getLine
if x == "mantener" then
return concursante
else if x == "cambiar" then
return cerrada
else do
putStrLn "ERROR: Entrada erronea"
cambiaPuerta concursante cerrada
-- ----------------------------------------------------------------------------
-- Ejercicio 6. Definir la acción
-- concurso :: IO ()
-- tal que se describa una ronda completa del concurso de Monty Hall.
-- concurso =>
-- Muy bien, concursante, aqui tiene tres puertas, una de ellas esconde un
-- coche y las otras dos, sendas cabras.
-- Que puerta elige? 1
-- Pues ahora abramos la puerta 2:
-- Vaya, hay una CABRA!
-- Concursante, digame, prefiere mantener su decision original, la puerta 1 [mantener]
-- o quiere cambiar a la puerta 3 [cambiar]? mantener
-- Bien, pues abramos la puerta 1: Y hay un COCHE, enhorabuena!
-- ó
-- Bien, pues abramos la puerta 1: Y hay una CABRA, lo sentimos mucho!
-- Juegue varias veces. ¿Qué estrategia es mejor, mantener o cambiar?
-- ¿Con qué probabilidad nos llevamos el coche si escogemos "mantener"?
-- ¿Con qué probabilidad nos llevamos el coche si escogemos "cambiar"?
-- ----------------------------------------------------------------------------
concurso :: IO ()
concurso = do
putStrLn "Muy bien, concursante, aqui tiene tres puertas, una de ellas esconde un"
putStrLn "coche y las otras dos, sendas cabras."
concursante <- escojaPuerta
premio <- ponPremio
abierta <- abrePuerta concursante premio
let cerrada = otraPuerta concursante abierta
putStr "Pues ahora abramos la puerta "
putStr (show abierta)
putStrLn ":"
putStrLn "Vaya, hay una CABRA!"
concursante <- cambiaPuerta concursante cerrada
putStr "Bien, pues abramos la puerta "
putStr (show concursante)
if concursante == premio then
putStrLn ": Y hay un COCHE, enhorabuena!"
else
putStrLn ": Y hay una CABRA, lo sentimos mucho!"
-- ----------------------------------------------------------------------------
-- Ejercicio 7. Definir la acción
-- estrategiaMantener :: IO Int
-- tal que devuelva un 1 si en una ronda del concurso, el jugador se llevó
-- el premio o 0 en caso contrario. Suponga que el jugador siempre escoge
-- mantener su decisión original.
--
-- Para ello:
-- 1. Seleccione de forma aleatoria una puerta para el premio.
-- 2. Seleccione de forma aleatoria una puerta para el concursante.
-- 3. Abra una de las puertas que contiene un cabra sin que sea la del concursante.
-- 4. Mantenga la decisión del concursante.
-- 5. Devuelva el valor adecuado dependiendo del resultado.
-- ----------------------------------------------------------------------------
estrategiaMantener :: IO Int
estrategiaMantener = do
premio <- randomRIO(1::Int,3)
concursante <- randomRIO(1::Int,3)
abierta <- abrePuerta concursante premio
let concursanteFinal = concursante
if concursanteFinal == premio then return 1 else return 0
-- ----------------------------------------------------------------------------
-- Ejercicio 8. Definir la acción
-- estrategiaCambiar :: IO Int
-- tal que devuelva un 1 si en una ronda del concurso, el jugador se llevó
-- el premio o 0 en caso contrario. Suponga que el jugador siempre escoge
-- cambiar su decisión original.
--
-- Para ello:
-- 1. Seleccione de forma aleatoria una puerta para el premio.
-- 2. Seleccione de forma aleatoria una puerta para el concursante.
-- 3. Abra una de las puertas que contiene un cabra sin que sea la del concursante.
-- 4. Cambie la decisión del concursante a la puerta que queda sin abrir.
-- 5. Devuelva el valor adecuado dependiendo del resultado.
-- ----------------------------------------------------------------------------
estrategiaCambiar :: IO Int
estrategiaCambiar = do
premio <- randomRIO(1::Int,3)
concursante <- randomRIO(1::Int,3)
abierta <- abrePuerta concursante premio
let concursanteFinal = otraPuerta concursante abierta
if concursanteFinal == premio then return 1 else return 0
-- ----------------------------------------------------------------------------
-- Ejercicio 9. Definir la acción
-- ejecutaEstrategia :: Int -> IO Int -> IO Int
-- tal que (ejecutaEstrategia n estrategia) ejecuta de forma consecutiva n veces
-- la estrategia pasada como segundo argumento. Devuelve el número de veces que
-- el concursante se llevó el premio. Por ejemplo:
-- ejecuta 100 estrategiaMantener =>
-- 30
-- ejecuta 100 estrategiaCambiar =>
-- 68
-- ----------------------------------------------------------------------------
ejecutaEstrategia :: Int -> IO Int -> IO Int
ejecutaEstrategia 0 estrategia = return 0
ejecutaEstrategia n estrategia = do
resultado1 <- estrategia
resultado2 <- ejecutaEstrategia (n-1) estrategia
return (resultado1 + resultado2)
-- Alternativas para las dos estrategias que permite resolver el dilema:
estrategiaMantener' :: IO Int
estrategiaMantener' = do
premio <- randomRIO(1::Int,3)
concursante <- randomRIO(1::Int,3)
if concursante == premio then return 1 else return 0
estrategiaCambiar' :: IO Int
estrategiaCambiar' = do
premio <- randomRIO(1::Int,3)
concursante <- randomRIO(1::Int,3)
if concursante /= premio then return 1 else return 0
-- ----------------------------------------------------------------------------
-- Ejercicio 10.
-- ¿Cuál es número mínimo de personas que debe haber en una fiesta para
-- tener una probabilidad del 50% de que al menos dos de ellas tengan el mismo
-- cumpleaños?
-- ----------------------------------------------------------------------------
nacimiento :: IO Int
nacimiento = randomRIO(1::Int,365)
fiesta :: Int -> IO [Int]
fiesta 0 = return []
fiesta n = do
nace <- nacimiento
resto <- fiesta (n-1)
return (nace:resto)
compruebaCumples :: Int -> IO Int
compruebaCumples n = do
f <- fiesta n
if f == nub f then return 0 else return 1
compruebaMuchosCumples :: Int -> Int -> IO Int
compruebaMuchosCumples 0 personas = return 0
compruebaMuchosCumples fiestas personas = do
resultado1 <- compruebaCumples personas
resultado2 <- compruebaMuchosCumples (fiestas-1) personas
return (resultado1 + resultado2)
-- Ir ejecutando "compruebaMuchosCumples" con 100000 fiestas y un número cada
-- vez mayor de personas hasta que el resultado sea superior al 50%