14 - Ejemplo práctico 3: FastPass en una atracción

Este ejercicio recrea el acceso a una atracción popular con dos colas: general y FastPass. El sistema debe garantizar que por cada dos invitados con FastPass se atienda al menos uno de la cola general para mantener la percepción de justicia.

14.1 Planteo del problema

La operación diaria requiere:

  • Registrar visitantes, indicando nombre y tipo de pase.
  • Despachar visitantes según la regla 2:1 (dos FastPass, uno general) sin dejar colas bloqueadas.
  • Visualizar el estado de cada cola y cuántos visitantes ingresaron desde cada una.
Diagrama de cola general y FastPass con proporción 2 a 1

El tablero muestra en tiempo real la cantidad de visitantes por cola y los ingresados en cada turno.

14.2 Modelo de datos

Utilizamos una cola circular simple para cada tipo de pase.

CAP_VISITANTES = 40
MAX_NOMBRE = 40


class Visitante:
  def __init__(self, nombre=""):
    self.nombre = nombre[:MAX_NOMBRE]


class ColaVisitantes:
  def __init__(self, capacidad=CAP_VISITANTES):
    self.capacidad = capacidad
    self.datos = [None] * capacidad
    self.frente = 0
    self.final = 0
    self.cantidad = 0

  def push(self, visitante):
    if self.cantidad == self.capacidad:
      return False
    self.datos[self.final] = visitante
    self.final = (self.final + 1) % self.capacidad
    self.cantidad += 1
    return True

  def pop(self):
    if self.cantidad == 0:
      return None
    v = self.datos[self.frente]
    self.frente = (self.frente + 1) % self.capacidad
    self.cantidad -= 1
    return v

14.3 Reglas de despacho

La estructura principal controla ambas colas y lleva contadores para el reporte.

class ControlAtraccion:
  def __init__(self):
    self.fastpass = ColaVisitantes()
    self.general = ColaVisitantes()
    self.turno_fast = 0
    self.atendidos_fast = 0
    self.atendidos_general = 0

  def registrar(self, nombre, es_fastpass=False):
    visitante = Visitante(nombre)
    return self.fastpass.push(visitante) if es_fastpass else self.general.push(visitante)

  def despachar(self):
    if self.fastpass.cantidad > 0 and (self.turno_fast < 2 or self.general.cantidad == 0):
      v = self.fastpass.pop()
      if v:
        self.turno_fast += 1
        self.atendidos_fast += 1
        return v
    v = self.general.pop()
    if v:
      self.turno_fast = 0
      self.atendidos_general += 1
      return v
    v = self.fastpass.pop()
    if v:
      self.turno_fast = 1
      self.atendidos_fast += 1
      return v
    return None

  def mostrar(self):
    print(f"FastPass ({self.fastpass.cantidad}):")
    idx = self.fastpass.frente
    for _ in range(self.fastpass.cantidad):
      v = self.fastpass.datos[idx]
      print(f"  FP - {v.nombre}")
      idx = (idx + 1) % self.fastpass.capacidad
    print(f"General ({self.general.cantidad}):")
    idx = self.general.frente
    for _ in range(self.general.cantidad):
      v = self.general.datos[idx]
      print(f"  GEN - {v.nombre}")
      idx = (idx + 1) % self.general.capacidad

  def reporte(self):
    print("--- Resumen ---")
    print(f"Atendidos FastPass: {self.atendidos_fast}")
    print(f"Atendidos General: {self.atendidos_general}")

14.4 Interfaz de consola

El menú permite registrar invitados, despachar y consultar el estado.

def main():
  control = ControlAtraccion()

  while True:
    print("\n1) Registrar visitante general")
    print("2) Registrar visitante FastPass")
    print("3) Despachar siguiente")
    print("4) Mostrar colas")
    print("0) Cerrar atraccion")
    opcion = input("Opcion: ").strip()

    if opcion in ("1", "2"):
      nombre = input("Nombre: ").strip()
      if control.registrar(nombre, es_fastpass=opcion == "2"):
        print("Registrado.")
      else:
        print("Cola completa.")
    elif opcion == "3":
      visitante = control.despachar()
      if visitante:
        print(f"Ingresando: {visitante.nombre}")
      else:
        print("No hay visitantes en espera.")
    elif opcion == "4":
      control.mostrar()
      control.reporte()
    elif opcion == "0":
      print("Atraccion cerrada.")
      control.reporte()
      break
    else:
      print("Opcion no valida.")


if __name__ == "__main__":
  main()

14.5 Código completo en Python

El siguiente listado reúne todo el simulador en un solo archivo:

CAP_VISITANTES = 40
MAX_NOMBRE = 40


class Visitante:
  def __init__(self, nombre=""):
    self.nombre = nombre[:MAX_NOMBRE]


class ColaVisitantes:
  def __init__(self, capacidad=CAP_VISITANTES):
    self.capacidad = capacidad
    self.datos = [None] * capacidad
    self.frente = 0
    self.final = 0
    self.cantidad = 0

  def push(self, visitante):
    if self.cantidad == self.capacidad:
      return False
    self.datos[self.final] = visitante
    self.final = (self.final + 1) % self.capacidad
    self.cantidad += 1
    return True

  def pop(self):
    if self.cantidad == 0:
      return None
    v = self.datos[self.frente]
    self.frente = (self.frente + 1) % self.capacidad
    self.cantidad -= 1
    return v


class ControlAtraccion:
  def __init__(self):
    self.fastpass = ColaVisitantes()
    self.general = ColaVisitantes()
    self.turno_fast = 0
    self.atendidos_fast = 0
    self.atendidos_general = 0

  def registrar(self, nombre, es_fastpass=False):
    visitante = Visitante(nombre)
    return self.fastpass.push(visitante) if es_fastpass else self.general.push(visitante)

  def despachar(self):
    if self.fastpass.cantidad > 0 and (self.turno_fast < 2 or self.general.cantidad == 0):
      v = self.fastpass.pop()
      if v:
        self.turno_fast += 1
        self.atendidos_fast += 1
        return v
    v = self.general.pop()
    if v:
      self.turno_fast = 0
      self.atendidos_general += 1
      return v
    v = self.fastpass.pop()
    if v:
      self.turno_fast = 1
      self.atendidos_fast += 1
      return v
    return None

  def mostrar(self):
    print(f"FastPass ({self.fastpass.cantidad}):")
    idx = self.fastpass.frente
    for _ in range(self.fastpass.cantidad):
      v = self.fastpass.datos[idx]
      print(f"  FP - {v.nombre}")
      idx = (idx + 1) % self.fastpass.capacidad
    print(f"General ({self.general.cantidad}):")
    idx = self.general.frente
    for _ in range(self.general.cantidad):
      v = self.general.datos[idx]
      print(f"  GEN - {v.nombre}")
      idx = (idx + 1) % self.general.capacidad

  def reporte(self):
    print("--- Resumen ---")
    print(f"Atendidos FastPass: {self.atendidos_fast}")
    print(f"Atendidos General: {self.atendidos_general}")


def main():
  control = ControlAtraccion()

  while True:
    print("\n1) Registrar visitante general")
    print("2) Registrar visitante FastPass")
    print("3) Despachar siguiente")
    print("4) Mostrar colas")
    print("0) Cerrar atraccion")
    opcion = input("Opcion: ").strip()

    if opcion in ("1", "2"):
      nombre = input("Nombre: ").strip()
      if control.registrar(nombre, es_fastpass=opcion == "2"):
        print("Registrado.")
      else:
        print("Cola completa.")
    elif opcion == "3":
      visitante = control.despachar()
      if visitante:
        print(f"Ingresando: {visitante.nombre}")
      else:
        print("No hay visitantes en espera.")
    elif opcion == "4":
      control.mostrar()
      control.reporte()
    elif opcion == "0":
      print("Atraccion cerrada.")
      control.reporte()
      break
    else:
      print("Opcion no valida.")


if __name__ == "__main__":
  main()

14.6 Pruebas sugeridas

  • Simular una hora pico registrando muchos FastPass y verificar que la regla 2:1 nunca bloquea la cola general.
  • Ejecutar la atracción con la cola general llena para confirmar que el sistema sigue despachando FastPass cuando no hay espacio en general.
  • Modificar la proporción (por ejemplo 3:1) y observar cómo cambian los contadores.

En el siguiente ejemplo práctico integraremos sensores en tiempo real y almacenamiento persistente.