Optimización de Plantillas de Fútbol en Python: POO vs. Backtracking
Enviado por Chuletator online y clasificado en Deporte y Educación Física
Escrito el en español con un tamaño de 12,24 KB
Este documento explora dos enfoques de programación en Python para abordar un desafío común en la gestión deportiva: la optimización de la plantilla de un equipo de fútbol bajo un presupuesto limitado. Presentaremos una implementación basada en la Programación Orientada a Objetos (POO) y una solución algorítmica utilizando el principio de backtracking, destacando cómo cada paradigma puede ser aplicado para seleccionar la mejor combinación de jugadores y maximizar el rendimiento del equipo.
Implementación Orientada a Objetos para la Gestión de Equipos
La primera sección detalla un modelo de clases en Python diseñado para representar jugadores y equipos. Este enfoque POO permite una gestión estructurada y modular de los recursos, facilitando la toma de decisiones sobre la contratación de deportistas.
Clase Jugador
La clase Jugador
encapsula las propiedades fundamentales de cada deportista: su posición en el campo, el costo asociado a su contratación y el aporte o valor que suma al rendimiento general del equipo.
class Jugador:
def __init__(self, posicion, costo, aporte):
self.posicion = posicion
self.costo = costo
self.aporte = aporte
def __repr__(self):
# Representación legible del objeto Jugador
return self.posicion
Clase Equipo
La clase Equipo
es responsable de gestionar el presupuesto disponible, las opciones de jugadores en el mercado y la lógica para evaluar y seleccionar la plantilla óptima. Incluye métodos para calcular los costos y el valor del equipo, así como para decidir si un jugador candidato debe ser aceptado o rechazado según criterios predefinidos.
class Equipo:
def __init__(self):
self.presupuesto = 700000
self.opciones = []
# Inicialización de jugadores disponibles en el mercado
jugador = Jugador("Defensa", 100000, 2)
self.opciones.append(jugador)
jugador = Jugador("Lateral", 200000, 4)
self.opciones.append(jugador)
jugador = Jugador("Mediocampista", 200000, 5)
self.opciones.append(jugador)
jugador = Jugador("Delantero", 400000, 7)
self.opciones.append(jugador)
self.equipo = [] # Plantilla actual del equipo
def evaluar_costo_equipo(self, equipo_candidato):
"""Calcula el costo total de un equipo candidato."""
costo = 0
if len(equipo_candidato) == 0:
# Si el equipo está vacío, retorna un costo que excede el presupuesto
# para que no sea considerado una opción válida por sí mismo.
return self.presupuesto + 1
for jugador in equipo_candidato:
costo += jugador.costo
return costo
def evaluar_aporte_equipo(self, equipo_candidato):
"""Calcula el aporte total (valor) de un equipo candidato."""
valor = 0
if len(equipo_candidato) == 0:
# Si el equipo está vacío, retorna -1 para indicar que no tiene aporte.
return -1
for jugador in equipo_candidato:
valor += jugador.aporte
return valor
def evaluar_aporte_actual(self):
"""Evalúa el aporte del equipo actualmente seleccionado."""
return self.evaluar_aporte_equipo(self.equipo)
def evaluar_costo_actual(self):
"""Evalúa el costo del equipo actualmente seleccionado."""
return self.evaluar_costo_equipo(self.equipo)
def generar_equipos_candidatos(self, equipo_base):
"""Genera nuevas combinaciones de equipos añadiendo un jugador de las opciones disponibles."""
alternativas = []
for jugador in self.opciones:
opcion = equipo_base.copy()
opcion.append(jugador)
alternativas.append(opcion)
return alternativas
def aceptar_candidato(self, equipo_candidato):
"""Determina si un equipo candidato es mejor que el actual, considerando presupuesto y valor.
La lógica original prioriza la aceptación si el presupuesto restante es bajo y el valor es superior,
o si el valor es igual pero el costo es menor.
"""
costo = self.evaluar_costo_equipo(equipo_candidato)
valor = self.evaluar_aporte_equipo(equipo_candidato)
if costo > self.presupuesto:
return False # Excede el presupuesto, no se puede aceptar
diferencia_presupuesto = self.presupuesto - costo
# Si el presupuesto restante es bajo (menos de 100,000), se prioriza el valor
if diferencia_presupuesto < 100000:
costo_actual = self.evaluar_costo_actual()
valor_actual = self.evaluar_aporte_actual()
if valor > valor_actual:
return True # Mejor valor, se acepta
elif valor == valor_actual:
if costo < costo_actual:
return True # Mismo valor, pero más barato, se acepta
# En otros casos, la lógica original no especifica una aceptación por defecto
# si hay mucho presupuesto restante y el equipo no es 'casi completo'.
# Se mantiene el comportamiento original de retornar False.
return False
def rechazar_candidato(self, equipo_candidato):
"""Determina si un equipo candidato debe ser rechazado (principalmente por exceder el presupuesto)."""
costo = self.evaluar_costo_equipo(equipo_candidato)
return costo > self.presupuesto
def contratar(self, equipo_candidato):
"""Proceso recursivo para construir el equipo óptimo, evaluando candidatos.
Este método explora combinaciones y actualiza el equipo si encuentra una mejor opción.
"""
if len(equipo_candidato) != 0:
if self.rechazar_candidato(equipo_candidato):
return # Si el candidato excede el presupuesto, se descarta
if self.aceptar_candidato(equipo_candidato):
self.equipo = equipo_candidato # Si el candidato es aceptado, se convierte en el equipo actual
return
# Generar y evaluar nuevas alternativas recursivamente
alternativas = self.generar_equipos_candidatos(equipo_candidato)
for equipo in alternativas:
# print(equipo) # Descomentar para visualizar el proceso de construcción del equipo
self.contratar(equipo)
def planificar_temporada(self):
"""Inicia el proceso de planificación y contratación del equipo para la temporada."""
equipo_candidato = []
self.contratar(equipo_candidato)
# --- Ejecución del ejemplo con Programación Orientada a Objetos ---
colocolo = Equipo()
colocolo.planificar_temporada()
print("\n--- Resultados de la Implementación POO ---")
print(f"Equipo final seleccionado: {colocolo.equipo}")
print(f"Aporte total del equipo: {colocolo.evaluar_aporte_actual()}")
print(f"Costo total del equipo: {colocolo.evaluar_costo_actual()}")
Optimización de Equipo con Backtracking
Esta sección presenta una solución alternativa al problema de selección de jugadores utilizando el algoritmo de backtracking. Este enfoque explora sistemáticamente todas las combinaciones posibles de jugadores, podando las ramas que exceden el presupuesto, para encontrar la configuración que maximice el aporte total.
Funciones Auxiliares para Backtracking
Se definen funciones simples para calcular el costo y el aporte de una lista de jugadores, que serán utilizadas por el algoritmo de backtracking para evaluar las combinaciones.
def costo(lista_jugadores):
"""Calcula el costo total de una lista de jugadores. Cada jugador es un sub-array [posición, costo, aporte]."""
suma = 0
for jug in lista_jugadores:
suma += jug[1] # El costo se encuentra en la posición 1 del sub-array del jugador
return suma
def aporte(lista_jugadores):
"""Calcula el aporte total de una lista de jugadores. Cada jugador es un sub-array [posición, costo, aporte]."""
suma = 0
for jug in lista_jugadores:
suma += jug[2] # El aporte se encuentra en la posición 2 del sub-array del jugador
return suma
Algoritmo de Backtracking: Función combinacion
La función combinacion
implementa el algoritmo de backtracking para encontrar la mejor combinación de jugadores. Utiliza variables globales (maximoGlobal
y solucionGlobal
) para mantener el seguimiento del máximo aporte encontrado y la solución de equipo correspondiente a ese aporte.
def combinacion(jugadores_disponibles, lista_combinacion_actual, presupuesto_maximo):
"""
Algoritmo de backtracking para encontrar la combinación óptima de jugadores.
Args:
jugadores_disponibles (list): Lista de todos los jugadores disponibles (formato: [posición, costo, aporte]).
lista_combinacion_actual (list): La combinación de jugadores que se está construyendo en la recursión actual.
presupuesto_maximo (int): El presupuesto máximo permitido para el equipo.
"""
global maximoGlobal
global solucionGlobal
# Caso base: si el costo de la combinación actual excede el presupuesto, se poda esta rama de búsqueda.
if costo(lista_combinacion_actual) > presupuesto_maximo:
return
# Si no excede el presupuesto, es una solución válida. Se verifica si es la mejor encontrada hasta ahora.
else:
if aporte(lista_combinacion_actual) > maximoGlobal: # Si es el mejor aporte que he encontrado
maximoGlobal = aporte(lista_combinacion_actual) # Guardo el aporte
solucionGlobal.clear() # Borro la solución anterior
for elem in lista_combinacion_actual: # Y guardo la solución actual
solucionGlobal.append(elem)
# Caso recursivo: se intenta agregar cada jugador disponible a la lista de combinaciones.
# La implementación original itera sobre la lista completa de jugadores disponibles en cada paso.
# Esto puede generar combinaciones con jugadores repetidos si no se maneja el índice de inicio
# para asegurar que cada jugador se considere solo una vez en una combinación única.
# Para mantener la fidelidad al código original, se conserva esta iteración.
for jugador in jugadores_disponibles:
lista_combinacion_actual.append(jugador) # Se agrega el jugador a la combinación actual
if costo(lista_combinacion_actual) <= presupuesto_maximo:
# Si la combinación con el nuevo jugador no excede el presupuesto, se llama recursivamente.
combinacion(jugadores_disponibles, lista_combinacion_actual, presupuesto_maximo)
lista_combinacion_actual.remove(jugador) # Se hace backtracking: se remueve el jugador para probar otras combinaciones
Ejecución del Algoritmo de Backtracking
Se inicializan las variables globales necesarias para el algoritmo de backtracking y se ejecuta la función principal combinacion
para encontrar la solución óptima de la plantilla.
maximoGlobal = 0
solucionGlobal = []
presupuesto = 700000
# Definición de jugadores disponibles en formato: [posición, costo, aporte]
jugadores = [["defensa", 100000, 2], ["lateral", 200000, 4], ["mediocampista", 200000, 5], ["delantero", 400000, 7]]
print("\n--- Resultados de la Implementación con Backtracking ---")
combinacion(jugadores, [], presupuesto)
print(f"Máximo aporte global encontrado: {maximoGlobal}")
print(f"Solución de equipo óptima: {solucionGlobal}")
Conclusión
Ambos enfoques presentados, la programación orientada a objetos y el algoritmo de backtracking, ofrecen soluciones válidas para el problema de optimización de la plantilla de un equipo de fútbol. Mientras que el modelo POO proporciona una estructura más modular y extensible para la gestión de entidades como jugadores y equipos, el backtracking es una técnica algorítmica poderosa para explorar espacios de búsqueda y encontrar soluciones óptimas en problemas combinatorios, como la selección de subconjuntos bajo restricciones.
La elección entre uno y otro dependerá de la complejidad específica del problema, la necesidad de modularidad en el diseño del software y la eficiencia requerida para el tamaño de los datos y el espacio de búsqueda.