Para ordenar un arreglo local de Swift por varios campos, compara cada clave en orden de prioridad y continúa solo cuando hay un empate. Resuelve por separado los valores de respaldo opcionales y luego define dónde deben ubicarse los valores faltantes. Este ejemplo ordena etiquetas sintéticas de formas para una lista de escritorio de Mac.
Define el criterio de ordenación antes de escribir el cierre
Supongamos que una lista de escritorio contiene etiquetas de formas con un número de grupo, una posición preferida opcional, una posición de respaldo opcional y un título. La tarea consiste en obtener un orden de visualización predecible a partir de estos valores en memoria.
Dos decisiones requieren reglas separadas. Un valor de respaldo elige qué valor representa a un elemento: se usa su posición preferida si está disponible; de lo contrario, su posición de respaldo. Un criterio de desempate compara otro campo solo después de que dos elementos tienen la misma posición efectiva. El operador nil-coalescing de Swift, ??, permite tomar la primera decisión al seleccionar el valor desempaquetado o el valor de respaldo. Operadores básicos — Operador nil-coalescing
Usa este criterio explícito para el ejemplo:
| Prioridad | Clave | Regla |
|---|---|---|
| 1 | Grupo | Primero los números menores |
| 2 | Posición efectiva | preferredPosition ?? backupPosition |
| 3 | Ubicación de las posiciones faltantes | Las posiciones faltantes van después de las presentes dentro de cada grupo |
| 4 | Título | Comparación ascendente de cadenas de Swift cuando las posiciones empatan |
| 5 | Identificador | Primero los identificadores menores cuando todas las claves anteriores empatan |
La ubicación de las posiciones faltantes forma parte de la comparación de posiciones, no es un campo independiente que prevalezca sobre el grupo. Un elemento del grupo cero sigue precediendo a uno del grupo uno aunque el primero no tenga posición.
La comparación del título usa deliberadamente el orden habitual de cadenas de Swift para este cálculo sintético. Mantén esa elección coherente si más adelante reemplazas el cierre por comparadores de Foundation.
Escribe una ordenación completa por varios campos con tratamiento explícito de nil
El siguiente programa completo usa la biblioteca estándar de Swift y no requiere importaciones. Ejecútalo en un entorno compatible con Swift en tu Mac. Todas las entradas están definidas en el programa; el cálculo no usa entradas externas ni almacenamiento persistente. La operación de ordenación devuelve un arreglo nuevo mediante sorted(by:). Documentación de sorted(by:) de Apple
struct ShapeItem {
let id: Int
let group: Int
let preferredPosition: Int?
let backupPosition: Int?
let title: String
var effectivePosition: Int? {
preferredPosition ?? backupPosition
}
}
func comesBefore(_ lhs: ShapeItem, _ rhs: ShapeItem) -> Bool {
if lhs.group != rhs.group {
return lhs.group < rhs.group
}
switch (lhs.effectivePosition, rhs.effectivePosition) {
case let (.some(left), .some(right)):
if left != right {
return left < right
}
case (.some(_), .none):
return true
case (.none, .some(_)):
return false
case (.none, .none):
break
}
if lhs.title != rhs.title {
return lhs.title < rhs.title
}
return lhs.id < rhs.id
}
let items = [
ShapeItem(id: 10, group: 1, preferredPosition: nil,
backupPosition: 2, title: "Circle"),
ShapeItem(id: 20, group: 1, preferredPosition: 1,
backupPosition: 9, title: "Square"),
ShapeItem(id: 30, group: 1, preferredPosition: nil,
backupPosition: nil, title: "Arc"),
ShapeItem(id: 40, group: 1, preferredPosition: 2,
backupPosition: 0, title: "Arc"),
ShapeItem(id: 50, group: 0, preferredPosition: nil,
backupPosition: nil, title: "Dot"),
ShapeItem(id: 60, group: 1, preferredPosition: 2,
backupPosition: nil, title: "Arc"),
ShapeItem(id: 70, group: 1, preferredPosition: 0,
backupPosition: 8, title: "Zero")
]
let ordered = items.sorted(by: comesBefore)
let expectedIDs = [50, 70, 20, 40, 60, 10, 30]
precondition(ordered.map(\.id) == expectedIDs)
precondition(ordered.count == items.count)
precondition(items.reversed().sorted(by: comesBefore).map(\.id)
== expectedIDs)
print(ordered.map(\.id))
Salida esperada:
[50, 70, 20, 40, 60, 10, 30]
El programa y el orden esperado se revisaron, pero no se ejecutaron aquí porque el entorno de investigación no cuenta con un compilador de Swift. Las precondiciones incluidas te permiten comprobar el resultado al ejecutarlo localmente.
Por qué cada ejemplo queda en esa posición
El elemento 50 va primero porque el grupo cero tiene prioridad antes de considerar la posición. El elemento 70 muestra entonces que cero es una posición preferida válida: no se usa su valor de respaldo, ocho. El elemento 20 usa su posición preferida de uno.
Los elementos 40, 60 y 10 tienen todos una posición efectiva de dos. Los dos títulos Arc preceden a Circle, y los identificadores resuelven el empate restante entre 40 y 60. El elemento 30 va al final porque sus dos campos de posición faltan dentro del grupo uno.
Esto también responde una pregunta común sobre los valores de respaldo: el elemento 40 no usa su posición de respaldo de cero cuando su posición preferida empata con la de otro elemento. Según este criterio, el valor de respaldo solo reemplaza una posición preferida ausente. Si el valor de respaldo también debiera resolver empates, habría que añadir otra regla de comparación.
Mantén válido el comparador cuando hay empates
Swift exige que el predicado de ordenación forme un orden débil estricto. En la práctica, un elemento nunca debe precederse a sí mismo, las decisiones de orden deben ser coherentes entre comparaciones y los elementos equivalentes deben formar grupos coherentes. Apple documenta estos requisitos para sorted(by:). Requisitos del predicado
El ejemplo comprueba si una clave anterior difiere antes de devolver el resultado de su comparación. Llega al título solo cuando el grupo y la posición efectiva empatan, y luego llega al identificador solo cuando también empata el título. Si los identificadores y todas las claves anteriores son iguales, la comparación estricta final devuelve false.
Evita combinar comparaciones de campos independientes con ||. Un elemento podría tener un grupo anterior, pero una posición posterior; otro podría tener la combinación opuesta. Un predicado basado en OR podría entonces indicar que cada elemento va antes que el otro. Resolver los empates de forma secuencial evita esa contradicción.
Evita también usar <= como comparación final: los valores iguales devolverían true, lo que infringiría la regla de que un elemento no puede precederse a sí mismo. Haz que cada comparación dependa únicamente de los valores proporcionados y de reglas de ordenación fijas.
Elige entre conservar el orden de entrada en los empates y usar un identificador final
La ordenación documentada de Swift es estable: los elementos que el predicado considera equivalentes conservan su orden relativo de entrada. Si ese es el comportamiento deseado, omite la comparación final del identificador y devuelve false cuando haya un empate en el título. Garantía de ordenación estable
Este ejemplo usa identificadores sintéticos únicos para resolver los empates restantes. Por ello, invertir la entrada de ejemplo sigue produciendo la misma secuencia de identificadores. Esa propiedad se deriva de este criterio y de los valores de ejemplo. Si los identificadores se repiten junto con todas las demás claves, esos elementos siguen siendo equivalentes y conservan su orden de entrada.
Expresa las mismas reglas con comparadores de rutas de claves de Foundation
Foundation proporciona KeyPathComparator para comparar propiedades y sorted(using:) para aplicar una secuencia de comparadores. El primer comparador establece el orden principal; los siguientes resuelven los empates. Estas API están disponibles desde macOS 12. KeyPathComparator, sorted(using:)
Para una versión equivalente con Foundation, expón dos claves de posición: una categoría para valores faltantes y una posición numérica. La categoría coloca las posiciones faltantes al final. Una vez que esa categoría empata, el sustituto numérico de las posiciones faltantes no puede coincidir con un cero presente, porque esos elementos ya tienen categorías distintas.
Añade la siguiente extensión y cálculo después del programa completo anterior. Este ejemplo depende de las definiciones existentes de ShapeItem, items y expectedIDs. Requiere macOS 14 o posterior porque construye explícitamente ComparableComparator con init(order:). Disponibilidad del inicializador
import Foundation
extension ShapeItem {
var missingPositionRank: Int {
effectivePosition == nil ? 1 : 0
}
var numericPositionKey: Int {
effectivePosition ?? 0
}
}
if #available(macOS 14.0, *) {
let comparators: [KeyPathComparator<ShapeItem>] = [
KeyPathComparator(\ShapeItem.group, order: .forward),
KeyPathComparator(\ShapeItem.missingPositionRank,
order: .forward),
KeyPathComparator(\ShapeItem.numericPositionKey,
order: .forward),
KeyPathComparator(
\ShapeItem.title,
comparator: ComparableComparator<String>(order: .forward)
),
KeyPathComparator(\ShapeItem.id, order: .forward)
]
let foundationOrdered = items.sorted(using: comparators)
precondition(foundationOrdered.map(\.id) == expectedIDs)
print(foundationOrdered.map(\.id))
} else {
print(items.sorted(by: comesBefore).map(\.id))
}
El comparador explícito del título conserva la elección anterior del orden de cadenas de Swift. Foundation documenta que ComparableComparator usa la implementación Comparable del tipo comparado, y que KeyPathComparator acepta un comparador de propiedades proporcionado. ComparableComparator, Inicializador de comparador de rutas de claves
Para una disposición fija, el cierre hace visible cada rama. Un arreglo de comparadores resulta útil cuando quieres representar como valores el orden de las claves admitidas. En cualquiera de los dos enfoques, mantén la selección del valor de respaldo en la propiedad calculada del modelo para que ambas implementaciones usen la misma definición.
Adapta las reglas sin cambiar su significado
Para ordenar las posiciones de forma descendente y mantener las faltantes al final, cambia solo la comparación entre dos posiciones presentes por left > right. En la versión de Foundation, invierte únicamente numericPositionKey; deja missingPositionRank en orden directo. Invertir todo el resultado también invertiría los grupos, títulos, identificadores y la ubicación de los valores faltantes.
Si una cadena opcional se convierte en una clave de respaldo, decide por separado si una cadena vacía cuenta como ausente. El nil-coalescing responde a nil; no reemplaza una cadena vacía presente. Resuelve cualquier regla de normalización adicional antes de ordenar y usa esa misma clave efectiva en todo momento. Comportamiento del nil-coalescing
Mantén este pequeño cálculo sincrónico. La guía de concurrencia de Swift distingue la ordenación sincrónica habitual de los puntos de suspensión, y el predicado documentado sorted(by:) es sincrónico. Si una aplicación necesita valores obtenidos mediante trabajo asíncrono, termina de obtenerlos antes de llamar a este ordenamiento. Añadir await dentro del cierre de comparación no es una extensión compatible de su firma. Concurrencia, Firma de sorted(by:)
Antes de adaptar el ejemplo a otra lista de escritorio, conserva las comprobaciones de una clave preferida ausente, dos claves de posición ausentes, un cero válido, posiciones efectivas iguales y campos de visualización completamente empatados. Decide si el último empate debe conservar el orden de entrada o usar un identificador. Esas elecciones dan a la lista un criterio de ordenación preciso y comprensible en ambas implementaciones.
Fuentes
- sorted(by:) — Apple
- Operadores básicos — Proyecto Swift
- KeyPathComparator — Apple
- sorted(using:) — Apple
- ComparableComparator — Apple
- init(order:) — Apple
- init(_:comparator:) — Apple
- Concurrencia — Proyecto Swift