Cómo ordenar un array de objetos por propiedad en Swift: mirando bajo el capó
¡Hola, colega! Si alguna vez has escrito una aplicación en Swift que muestra una lista de usuarios, productos o tareas, seguramente te has enfrentado a la necesidad de ordenar esos datos. La pregunta «How to sort an array of custom objects by property value?» es una de las más populares en Stack Overflow. Y no es casualidad: a primera vista todo parece simple, pero basta con profundizar un poco para descubrir todo un abanico de posibilidades.
Hoy no solo daremos una respuesta preparada. Analizaremos cómo funciona el ordenamiento bajo el capó de Swift, qué mecanismos están involucrados y cómo escribir no solo un código funcional, sino eficiente y elegante. ¡Allá vamos!
¿Qué es el ordenamiento de un array de objetos en Swift?
Imagina: tienes un array de objetos User. Cada usuario tiene nombre, edad y calificación. Necesitas mostrarlos en pantalla, ordenados por edad (de menor a mayor).
En Swift, esto se hace mediante los métodos incorporados sort() (ordena el array en el lugar) y sorted() (devuelve un nuevo array ordenado). Ambos aceptan una closure que define el orden.
struct User { let name: String let age: Int let rating: Double}
let users = [ User(name: "Ana", age: 25, rating: 4.5), User(name: "Boris", age: 30, rating: 3.8), User(name: "Vicky", age: 22, rating: 4.9)]
// Ordenamiento por edad (ascendente)let sortedByAge = users.sorted { $0.age < $1.age }print(sortedByAge.map { $0.name }) // ["Vicky", "Ana", "Boris"]Eso es todo. Pero analicemos qué está sucediendo realmente aquí.
Cómo funciona sort() bajo el capó: algoritmos y closures
El método sort() en Swift utiliza un algoritmo híbrido llamado TimSort (en honor a Tim Peters). Es un algoritmo de ordenamiento adaptativo y estable que combina ordenamiento por mezcla e inserción. Es especialmente eficiente en datos parcialmente ordenados: ¡un saludo a los feeds de noticias y listas de tareas!
La closure que pasas es un predicado de ordenamiento: una función que toma dos elementos y devuelve true si el primero debe ir antes que el segundo. Swift llama a esta closure repetidamente, comparando pares de elementos.
Importante: si tus objetos cumplen con el protocolo Comparable, puedes prescindir por completo de la closure:
struct User: Comparable { let name: String let age: Int static func < (lhs: User, rhs: User) -> Bool { return lhs.age < rhs.age }}
let sortedUsers = users.sorted() // ahora es simplePero, ¿qué pasa si necesitas ordenar por diferentes propiedades en tiempo de ejecución? Aquí es donde entran en juego los key paths.
Ordenamiento con KeyPath: elegancia y flexibilidad
A partir de Swift 4, tenemos KeyPath. Es una forma segura de referirse a una propiedad de un objeto. Combinando KeyPath con closures, se puede escribir una función de ordenamiento universal:
extension Array { mutating func sort<T: Comparable>(by keyPath: KeyPath<Element, T>, ascending: Bool = true) { sort { ascending ? $0[keyPath: keyPath] < $1[keyPath: keyPath] : $0[keyPath: keyPath] > $1[keyPath: keyPath] } } func sorted<T: Comparable>(by keyPath: KeyPath<Element, T>, ascending: Bool = true) -> [Element] { return sorted { ascending ? $0[keyPath: keyPath] < $1[keyPath: keyPath] : $0[keyPath: keyPath] > $1[keyPath: keyPath] } }}
// Uso:users.sort(by: \\.age) // por edad (ascendente)let sortedByRating = users.sorted(by: \\.rating, ascending: false) // por calificación (descendente)¿Hermoso, verdad? Seguridad de tipos