Avatar ·

Как отсортировать массив кастомных объектов по значению свойства в Swift?

Всем привет! Я только начал изучать Swift и столкнулся с проблемой при работе с массивами. У меня есть структура `User` с несколькими свойствами: ```swift struct User { let name: String let age: Int let score: Double } ``` Я создаю массив пользователей: ```swift let users = [ User(name: "Анна", age: 25, score: 85.5), User(name: "Борис", age: 30, score: 92.3), User(name: "Виктор", age: 22, score: 78.1) ] ``` Мне нужно отсортировать этот массив по возрасту (от младшего к старшему) и по имени (в алфавитном порядке). Я пробовал использовать `sort()` и `sorted()`, но компилятор ругается, что `User` не соответствует протоколу `Comparable`. Как правильно отсортировать массив кастомных объектов в Swift? Желательно с примерами кода. Заранее спасибо за помощь! <<>>

Основные способы сортировки кастомных объектов в Swift

В Swift есть несколько подходов для сортировки массивов с пользовательскими типами. Рассмотрим самые популярные и эффективные.

1. Использование замыкания (Closure) с sorted(by:)

Самый простой и гибкий способ — передать замыкание, которое сравнивает два элемента:

// Сортировка по возрасту (по возрастанию)
let sortedByAge = users.sorted { $0.age < $1.age }

// Сортировка по имени (в алфавитном порядке)
let sortedByName = users.sorted { $0.name < $1.name }

// Сортировка по убыванию счета
let sortedByScoreDesc = users.sorted { $0.score > $1.score }

Если нужно отсортировать исходный массив (изменяемый), используйте sort(by:):

var mutableUsers = users
mutableUsers.sort { $0.age < $1.age }

2. Множественная сортировка (по нескольким свойствам)

Когда нужно отсортировать сначала по одному полю, а при равенстве — по другому:

// Сначала по возрасту, затем по имени (если возраст одинаковый)
let sortedMulti = users.sorted {
    if $0.age != $1.age {
        return $0.age < $1.age
    } else {
        return $0.name < $1.name
    }
}

Более лаконичный вариант с кортежами:

let sortedMultiTuple = users.sorted {
    ($0.age, $0.name) < ($1.age, $1.name)
}

3. Реализация протокола Comparable

Если вы часто сортируете объекты одного типа, можно сделать тип сравниваемым:

struct User: Comparable {
    let name: String
    let age: Int
    let score: Double
    
    static func < (lhs: User, rhs: User) -> Bool {
        // Определяем логику сравнения
        return lhs.age < rhs.age
    }
    
    static func == (lhs: User, rhs: User) -> Bool {
        return lhs.age == rhs.age && lhs.name == rhs.name
    }
}

// Теперь можно использовать просто sorted()
let sortedUsers = users.sorted()

4. Использование NSSortDescriptor (для Obj-C совместимости)

Если вы работаете с NSArray или Core Data, можно использовать классический подход:

import Foundation

let ageDescriptor = NSSortDescriptor(key: "age", ascending: true)
let nameDescriptor = NSSortDescriptor(key: "name", ascending: true)
let sortedNSArray = (users as NSArray).sortedArray(using: [ageDescriptor, nameDescriptor])

Важные моменты

  • sorted() возвращает новый отсортированный массив, не изменяя исходный.
  • sort() изменяет исходный массив (должен быть var).
  • Для обратной сортировки используйте оператор > или вызовите .reversed().
  • Сортировка по строкам по умолчанию чувствительна к регистру. Для case-insensitive используйте $0.name.localizedCaseInsensitiveCompare($1.name) == .orderedAscending.

Рекомендую начинать с замыканий — это самый гибкий и понятный способ. Переходите к протоколу Comparable, когда один тип сортируется одинаково в разных местах кода.

<<>>

Альтернативный подход: сортировка через map и словари

Иногда удобно сгруппировать данные перед сортировкой. Например, если нужно отсортировать по категориям:

// Группировка по возрасту
let groupedByAge = Dictionary(grouping: users) { $0.age }
// Сортировка ключей
let sortedKeys = groupedByAge.keys.sorted()
// Восстановление массива
let sortedGrouped = sortedKeys.flatMap { groupedByAge[$0]! }

Сортировка с учетом локали

Для русских имён и текста важно использовать локализованное сравнение:

let sortedLocalized = users.sorted {
    $0.name.localizedStandardCompare($1.name) == .orderedAscending
}

Этот метод корректно обрабатывает букву "ё", регистр и диакритические знаки. Для большинства реальных приложений с пользовательским интерфейсом стоит использовать именно его, а не просто < для строк.

Производительность

Все методы сортировки в Swift используют алгоритм Timsort (гибрид сортировки слиянием и вставками) со сложностью O(n log n). Для массивов до 10 000 элементов разница между подходами незаметна. Выбирайте тот, который делает код читаемее.