Как удалить дубликаты из массива в Swift? (С сохранением порядка)
Решение с сохранением порядка через расширение Array
Лучший способ удалить дубликаты из массива с сохранением порядка — использовать метод reduce(into:_:) в сочетании с Set для отслеживания уже встреченных элементов. Вот готовое расширение для Array:
extension Array where Element: Hashable {
func removingDuplicates() -> [Element] {
var seen = Set<Element>()
return filter { element in
guard !seen.contains(element) else { return false }
seen.insert(element)
return true
}
}
}
// Использование:
let names = ["Анна", "Борис", "Анна", "Виктор", "Борис", "Дмитрий"]
let uniqueNames = names.removingDuplicates()
print(uniqueNames) // ["Анна", "Борис", "Виктор", "Дмитрий"]
Как это работает
- Требование Hashable: Элементы массива должны соответствовать протоколу
Hashable, чтобы их можно было хранить вSet. Все стандартные типы (String, Int, Double и т.д.) уже соответствуют этому протоколу. - Метод filter: Проходит по массиву в порядке исходной последовательности. Для каждого элемента проверяет, был ли он уже добавлен в
seen. - Set для отслеживания:
seenхранит уже встреченные элементы. Если элемент уже есть — он пропускается (возвращаетсяfalse). Если нет — добавляется вseenи включается в результат. - Сложность O(n): Алгоритм работает за линейное время, так как операции вставки и проверки в
Setвыполняются за O(1) в среднем.
Альтернатива с reduce(into:)
Если вам не нравится filter, можно использовать reduce(into:_:) — результат будет тот же:
extension Array where Element: Hashable {
func removingDuplicates() -> [Element] {
var seen = Set<Element>()
return reduce(into: [Element]()) { partialResult, element in
guard !seen.contains(element) else { return }
seen.insert(element)
partialResult.append(element)
}
}
}
Если порядок не важен
Если порядок элементов не имеет значения, можно просто использовать инициализатор Set:
let uniqueNames = Array(Set(names))
print(uniqueNames) // ["Борис", "Дмитрий", "Анна", "Виктор"] — порядок случаен
Важные замечания
- Расширение работает только с
Hashableэлементами. Для пользовательских структур нужно либо добавить соответствиеHashable, либо использовать другой подход (например, сравнивать по id). - Если массив большой, а дубликатов мало —
filterсSetбудет эффективнее, чемreduce(into:_:), так как не создает промежуточный массив. - Для массивов, которые могут быть изменены, можно создать мутирующую версию:
mutating func removeDuplicates().
Это решение использует только стандартную библиотеку Swift, не требует Foundation и отлично справляется с задачей. Рекомендую добавить это расширение в ваш проект как utility-метод.
<<Решение через NSOrderedSet (с Foundation)
Если вы не против подключить Foundation (что в iOS/macOS проектах обычно уже сделано), можно использовать NSOrderedSet — это самый короткий способ:
import Foundation
let names = ["Анна", "Борис", "Анна", "Виктор", "Борис", "Дмитрий"]
let uniqueNames = NSOrderedSet(array: names).array as! [String]
print(uniqueNames) // ["Анна", "Борис", "Виктор", "Дмитрий"]
Плюсы и минусы
- Плюс: Очень коротко и понятно. Работает с любыми объектами, не требует Hashable.
- Минус: Возвращает
[Any], нужно явное приведение типа. Работает медленнее, чем чистое Swift-решение из-за моста к Objective-C. - Минус: Требует импорта Foundation, что не всегда удобно в серверном Swift или библиотеках.
Когда использовать
Этот подход отлично подходит для быстрых прототипов, скриптов или когда вы точно знаете, что Foundation уже подключен. Для production-кода на Swift я рекомендую первое решение — оно более производительное и типобезопасное.