Cómo mezclar un array en Swift: optimización y rendimiento

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Cómo mezclar correctamente un array en Swift: de principiante a experto

\n

¿Alguna vez te has preguntado cómo crear una lista de reproducción aleatoria en tu aplicación? O quizás necesitabas mezclar las preguntas de un cuestionario para que cada vez aparecieran en un orden diferente. Si trabajas con Swift, tarde o temprano te enfrentarás a la tarea: cómo mezclar un array (shuffle array). Esta pregunta en Stack Overflow ha acumulado miles de visitas, y con razón: hay más dificultades de las que parecen a simple vista.

\n

En este artículo no solo mostraremos cómo llamar a shuffle(). Exploraremos a fondo: analizaremos algoritmos, compararemos el rendimiento, aprenderemos a hacer una mezcla optimizada y entenderemos cuándo el método estándar es malo y cuándo es lo mejor que la humanidad ha inventado. ¡Allá vamos!

\n\n

¿Por qué simplemente "random" es una mala idea?

\n

Supongamos que tienes un array:

\n
var cards = ["As", "Rey", "Reina", "Jota"]
\n

La forma más ingenua es crear un nuevo array e insertar elementos en posiciones aleatorias. Parece lógico, ¿verdad?

\n
func naiveShuffle(_ array: [String]) -> [String] {\n    var shuffled = [String]()\n    for element in array {\n        let randomIndex = Int.random(in: 0...shuffled.count)\n        shuffled.insert(element, at: randomIndex)\n    }\n    return shuffled\n}
\n

En arrays pequeños (4-10 elementos) esto funciona. Pero tan pronto como la cantidad de elementos supera el centenar, sentirás el dolor. ¿Por qué? Porque insert(at:) tiene una complejidad de O(n) — cada vez que insertas un elemento al principio o en medio, todos los elementos siguientes se desplazan. Complejidad final: O(n²). Para un array de 1000 elementos, esto son 500,000 operaciones. Para 10,000, ya son 50 millones. La aplicación comenzará a "ralentizarse", el usuario se irá, la calificación bajará. No hagas esto.

\n\n

El método integrado shuffle(): tu nuevo mejor amigo

\n

A partir de Swift 4.2, tenemos el método nativo shuffle() (para arrays mutables) y shuffled() (devuelve un nuevo array). Así es como funciona:

\n
var numbers = [1, 2, 3, 4, 5]\nnumbers.shuffle()  // modifica el array original\nprint(numbers)     // por ejemplo: [3, 1, 5, 2, 4]\n\nlet shuffledNumbers = numbers.shuffled()  // nuevo array\nprint(shuffledNumbers)                    // [4, 2, 1, 5, 3]
\n

Internamente, Apple utiliza el algoritmo de Fisher-Yates (también conocido como algoritmo de Knuth). Es el estándar de oro para mezclar. ¿Cómo funciona? Recorremos el array desde el último elemento hasta el primero, para cada uno elegimos un índice aleatorio desde 0 hasta la posición actual e intercambiamos los elementos. Complejidad: O(n). Para un array de 10,000 elementos — solo 10,000 operaciones. ¡5000 veces más rápido que el enfoque ingenuo!

\n\n

Algoritmo de Fisher-Yates: explicación sencilla

\n

Implementémoslo manualmente para entender la magia:

\n
extension Array {\n    mutating func customShuffle() {\n        for i in stride(from: count - 1, through: 1, by: -1) {\n            let j = Int.random(in: 0...i)\n            swapAt(i, j)\n        }\n    }\n}\n\nvar deck = Array(1...10)\ndeck.customShuffle()\nprint(deck) // [6, 2, 9, 1, 8, 4, 7, 3, 10, 5]
\n

¿Qué sucede aquí? Supongamos un array de 5 elementos. Empezamos con el índice 4 (el último). Elegimos un número aleatorio del 0 al 4, intercambiamos. Luego pasamos al índice 3, elegimos del 0 al 3, intercambiamos. Y así hasta el índice 1. Cada elemento está garantizado de estar en una posición aleatoria exactamente una vez. Sin desplazamientos, sin reinserciones — matemática pura.

\n

Un detalle importante: en el algoritmo de Fisher-Yates no se puede elegir un índice aleatorio de 0 a count-1 en cada paso. ¿Por qué? Porque entonces algunos elementos podrían permanecer en su lugar, mientras que otros se moverían varias veces, lo que rompería la uniformidad de la distribución. Confía en la implementación estándar de Apple — es óptima.

Blogs

Book Recommendations