WhatsApp

  

Algoritmo de Combinaciones en Python: Guía Completa, Ejemplos y Mejores Prácticas

Aprende a generar combinaciones con Python, descubre implementaciones desde cero, compara con itertools, optimiza rendimiento y aplica casos de uso reales.

Algoritmo de Combinaciones en Python

Introducción

Las combinaciones son una estructura fundamental de la teoría de la combinatoria: dado un conjunto n de elementos, una combinación de tamaño k es cualquier subconjunto de k elementos sin importar el orden. En programación, generar estas combinaciones es esencial para problemas de búsqueda, pruebas de software, análisis de datos y algoritmos de IA.

En este artículo veremos:

  • Conceptos teóricos y complejidad.
  • Implementación paso a paso sin dependencias externas.
  • Comparación con itertools.combinations (la solución estándar de la biblioteca).
  • Ejemplos prácticos, benchmarks y técnicas de optimización.
  • Buenas prácticas, troubleshooting y consideraciones de rendimiento.

Implementación del algoritmo de combinaciones (sin itertools)

El algoritmo recursivo más clásico utiliza backtracking: se construye la combinación elemento a elemento y se retrocede cuando la longitud requerida se alcanza.

def combinaciones(arr, k):
    """Genera todas las combinaciones de arr de tamaño k.
    Parámetros:
        arr (list): Lista de elementos origen.
        k   (int) : Tamaño de la combinación.
    Retorna:
        generator: Cada combinación como una tupla.
    """
    def backtrack(start, path):
        # Caso base: combinación completa
        if len(path) == k:
            yield tuple(path)
            return
        # Explorar siguientes candidatos
        for i in range(start, len(arr)):
            # Añadimos arr[i] y continuamos
            path.append(arr[i])
            yield from backtrack(i + 1, path)
            # Deshacemos la elección (backtrack)
            path.pop()
    return backtrack(0, [])
# Ejemplo de uso
if __name__ == "__main__":
    lista = ["A", "B", "C", "D"]
    for c in combinaciones(lista, 2):
        print(c)

Salida:

("A", "B")
("A", "C")
("A", "D")
("B", "C")
("B", "D")
("C", "D")

Complejidad temporal: O(C(n,k) * k), donde C(n,k) es el número de combinaciones (n! / (k! (n‑k)!)). La complejidad espacial es O(k) por la pila de recursión.

Comparación con itertools.combinations

Ventajas de la implementación propia

  • Control total del proceso (p. ej., filtrado en tiempo real).
  • No depende de módulos externos, útil en entornos restringidos.
  • Facilita la inserción de lógica adicional (corte temprano, generación parcial).

Ventajas de itertools.combinations

  • Implementación en C, por lo que es ≈10‑30× más rápida en la mayoría de los casos.
  • Menor consumo de memoria gracias a un generador altamente optimizado.
  • Garantiza compatibilidad con cualquier iterable (no solo listas).

Benchmark rápido (Python 3.11, Intel i7‑12700K):

import time, itertools
arr = list(range(20))
K = 10
# Implementación propia
start = time.perf_counter()
list(combinaciones(arr, K))
print('propia:', time.perf_counter() - start)
# itertools
start = time.perf_counter()
list(itertools.combinations(arr, K))
print('itertools:', time.perf_counter() - start)

Resultado típico:

propia: 0.084 s
itertools: 0.004 s

En casos donde k es pequeño respecto a n, la diferencia se reduce, pero itertools sigue liderando.

Casos de uso del mundo real

  • Testing combinatorio: generar todos los pares de parámetros para pruebas de integración.
  • Feature selection en Machine Learning: evaluar combinaciones de variables para modelos de regresión.
  • Problemas de asignación: combinar recursos y tareas en escenarios de planificación.
  • Generación de palabras clave: combinar términos para SEO y marketing de contenidos.

Optimización, seguridad y buenas prácticas

1. Corte temprano (pruning)

Si sabes que ciertas combinaciones son inválidas, puedes filtrarlas antes de generar todas:

def combinaciones_filtradas(arr, k, pred):
    def backtrack(start, path):
        if len(path) == k:
            if pred(path):
                yield tuple(path)
            return
        for i in range(start, len(arr)):
            path.append(arr[i])
            # Prune: si la predicción ya falla, no seguir
            if pred(path):
                yield from backtrack(i + 1, path)
            path.pop()
    return backtrack(0, [])

2. Uso de generadores en lugar de listas

Siempre que sea posible, consume la salida con un bucle for o itertools.islice para evitar cargar todas las combinaciones en memoria.

3. Parallelismo

Para n grande, puedes dividir el rango inicial entre procesos con multiprocessing.Pool:

from multiprocessing import Pool
def worker(start_idx):
    return list(combinaciones(arr[start_idx:], k))
with Pool() as p:
    results = p.map(worker, range(0, len(arr), chunk))
    # Combine resultados

4. Seguridad de datos

El algoritmo no ejecuta código arbitrario, por lo que no representa riesgo directo. Sin embargo, si los elementos provienen de fuentes no confiables, evita imprimirlos sin sanitizarlos (p. ej., al generar rutas de archivo).

Resolución de problemas habituales

  • MemoryError: ocurre cuando intentas convertir el generador a list() para n muy grande. Solución: procesa por lotes o usa itertools.islice.
  • RecursionError: maximum recursion depth exceeded: la recursión supera el límite de Python (≈1000). Usa una versión iterativa o incrementa sys.setrecursionlimit() con cautela.
  • Resultados inesperados: verifica que el índice i + 1 en la llamada recursiva sea correcto; un error común es usar i y generar combinaciones duplicadas.

Conclusión

El algoritmo de combinaciones es una herramienta esencial para cualquier ingeniero de software o científico de datos. Python ofrece una solución lista para producción con itertools.combinations, pero comprender la lógica subyacente permite personalizar, optimizar y adaptar la generación de combinaciones a requisitos específicos como filtrado en tiempo real o ejecución paralela.

Aplica las mejores prácticas mostradas, mide siempre el rendimiento con timeit o perf_counter, y elige la variante que mejor equilibre velocidad, consumo de memoria y flexibilidad para tu caso de uso.



Algoritmo de Combinaciones en Python: Guía Completa, Ejemplos y Mejores Prácticas
ASIMOV Ingeniería S. de R.L. de C.V., Emiliano Nava 15 noviembre, 2025
Compartir
Iniciar sesión dejar un comentario

  
Algoritmo de Permutaciones en Python: Guía Completa, Implementaciones y Mejores Prácticas
Aprende todo sobre el algoritmo de permutaciones, su teoría, complejidad y múltiples implementaciones en Python, con ejemplos, comparativas, benchmarking y consejos de optimización.