Avatar ·

Удаление дубликатов из массива в Swift: лучшие способы

Всем привет! Я новичок в Swift и столкнулся с проблемой: нужно удалить дубликаты из массива строк. У меня есть массив: ```swift let fruits = ["apple", "banana", "apple", "orange", "banana", "kiwi"] ``` Я хочу получить на выходе: `["apple", "banana", "orange", "kiwi"]` — то есть без повторений. Пробовал использовать `Set`, но он не сохраняет порядок элементов: ```swift let uniqueFruits = Array(Set(fruits)) // Результат может быть: ["banana", "kiwi", "orange", "apple"] ``` Порядок для меня важен, так как массив отображается в таблице. Также видел решение с `filter` и `contains`, но оно кажется медленным для больших массивов. Подскажите, как правильно удалить дубликаты из массива в Swift, сохранив порядок элементов? Есть ли встроенные методы или лучшие практики для этой задачи? Заранее спасибо! <<>>

Использование NSOrderedSet (простой способ)

Самый простой способ сохранить порядок — использовать NSOrderedSet из Foundation. Он автоматически удаляет дубликаты, сохраняя первый порядок вхождения:

import Foundation

let fruits = ["apple", "banana", "apple", "orange", "banana", "kiwi"]
let uniqueFruits = Array(NSOrderedSet(array: fruits)) as! [String]
print(uniqueFruits) // ["apple", "banana", "orange", "kiwi"]

Плюсы: коротко и читаемо.
Минусы: работает только с элементами, поддерживающими Hashable (строки, числа — подходят).

Расширение Array с Dictionary (Swift 5.0+)

Более производительный способ для больших массивов — использовать словарь для отслеживания уникальности:

extension Array where Element: Hashable {
    func removingDuplicates() -> [Element] {
        var seen = [Element: Bool]()
        return filter { seen.updateValue(true, forKey: $0) == nil }
    }
}

let uniqueFruits = fruits.removingDuplicates()
print(uniqueFruits) // ["apple", "banana", "orange", "kiwi"]

Метод updateValue(_:forKey:) возвращает nil, если ключа ещё не было — так мы оставляем только первое вхождение.

Сравнение производительности

  • NSOrderedSet — O(n) по времени, но требует приведения типов (cast).
  • Расширение с Dictionary — O(n), оптимально для больших массивов (1000+ элементов).
  • filter + contains — O(n²), избегайте для больших данных.

Универсальное решение для любых типов (не только Hashable)

Если элементы не поддерживают Hashable (например, кастомные структуры без Hashable), используйте замыкание для сравнения:

extension Array {
    func removingDuplicates(by predicate: (Element, Element) -> Bool) -> [Element] {
        var result: [Element] = []
        for element in self {
            if !result.contains(where: { predicate($0, element) }) {
                result.append(element)
            }
        }
        return result
    }
}

// Пример для кастомных структур
struct Person {
    let id: Int
    let name: String
}
let people = [Person(id: 1, name: "Alice"), Person(id: 2, name: "Bob"), Person(id: 1, name: "Alice")]
let uniquePeople = people.removingDuplicates(by: { $0.id == $1.id })
print(uniquePeople.count) // 2

Внимание: этот метод — O(n²), используйте только для небольших массивов (до 100 элементов).

Рекомендация

Для 90% случаев используйте расширение с Dictionary — оно быстрое, сохраняет порядок и легко читается. Если массив маленький (до 50 элементов), можно использовать NSOrderedSet для краткости.

<<>>

Альтернативный подход: использование OrderedSet из Collections (Swift 5.7+)

Начиная с Swift 5.7, в пакете Collections (от Apple) появился OrderedSet — структура данных, которая сочетает свойства Set и Array:

import Collections

let fruits = ["apple", "banana", "apple", "orange", "banana", "kiwi"]
let uniqueFruits = Array(OrderedSet(fruits))
print(uniqueFruits) // ["apple", "banana", "orange", "kiwi"]

Плюсы:

  • Сохраняет порядок добавления.
  • Работает быстро — O(1) для проверки уникальности.
  • Не требует приведения типов (как NSOrderedSet).

Минусы: нужно добавить зависимость swift-collections в Package.swift или через SPM.

Этот вариант предпочтителен, если вы уже используете другие коллекции из этого пакета (например, Deque или Heap).