Acciones

Relación 25 Sol

De Informática de 1º de Matemáticas [Curso 2021-22, Grupo 3]

-- 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%