Connect with us

Программирование

Уникальные значения — удаление дубликатов из массива в Swift

Существует много способов достичь того же результата, и у каждого из них есть свои плюсы и минусы. Давайте рассмотрим их и посмотрим, какие методы лучше всего подходят для вашего случая.

Опубликовано

/

     
     

Удаление дубликатов для получения уникальных значений из массива может быть распространенной задачей. В таких языках, как Ruby, есть встроенные методы, например uniq, но в Swift нам приходится создавать такие методы самостоятельно. Стандартная библиотека не предоставляет простого способа для этого.

Существует много способов достичь того же результата, и у каждого из них есть свои плюсы и минусы. Давайте рассмотрим их и посмотрим, какие методы лучше всего подходят для вашего случая.

Использование Set для удаления дубликатов

Прежде чем приступить к рассмотрению расширений для удаления дубликатов из массива, стоит ознакомиться с наборами в Swift. Set по умолчанию содержит уникальные значения и может помочь вам достичь желаемых результатов.

Выбирайте набор, если:

  • Порядок не имеет значения
  • Вы хотите, чтобы элементы были уникальными по умолчанию
let arrayOfElements: [Int] = [1, 1, 1, 2, 2, 2, 3, 3, 3]
let setOfElements: Set<Int> = [1, 1, 1, 2, 2, 2, 3, 3, 3]

print(arrayOfElements) // prints: [1, 1, 1, 2, 2, 2, 3, 3, 3]
print(setOfElements)   // prints: [2, 3, 1]

Это скорее принципиальное решение для кода, когда вы выбираете между набором и массивом. Наборы имеют преимущество в виде более высокой производительности, что делает их хорошим кандидатом для решения проблемы уникальности. Вы можете узнать больше о различиях между набором и массивом в статье «Массив против набора: основы Swift«.

Не вдаваясь в подробности различий, стоит знать, что набор не сохраняет порядок. Если в вашем случае сохранение порядка имеет важное значение, вам, возможно, не стоит выбирать набор. Вы можете выбрать NSOrderedSet, но этот класс не предоставляет дополнение типов, и вам придется работать с экземплярами Any.

Удаление дубликатов из массива с помощью расширения

Когда порядок элементов имеет важное значение, мы можем продолжить работу с массивом и извлечь уникальные значения, используя кастомное расширение.

Выбирайте массив, если:

  • Порядок имеет важное значение
  • Вы не можете легко переключиться на набор

Нам нужно создать расширение для фильтрации дубликатов элементов. При разработке этого расширения важно учитывать производительность, так как мы можем быстро получить квадратичную временную сложность (N²). Если вкратце, то это означает, что чем больше элементов, тем больше снижается производительность.

Следующий фрагмент кода использует протокол Hashable для сопоставления элементов и имеет линейную временную сложность o(N). Это означает, что для 3 элементов требуется 3 итерации, для 10 элементов — 10 итераций и так далее.

extension Sequence where Iterator.Element: Hashable {
    func unique() -> [Iterator.Element] {
        var seen: Set<Iterator.Element> = []
        return filter { seen.insert($0).inserted }
    }
}

print(array.unique()) // prints: [1, 2, 3]

Давайте разберем этот метод unique():

  • Мы создаем Set для отслеживания просмотренных объектов
  • Фильтр используется для итерации по всем объектам
  • Метод insert(_:) возвращает кортеж, включающий вставленный булевый параметр, который устанавливается в true, если объект был вставлен, и в false, если нет
  • Вставленный булевый параметр используется для фильтрации дубликатов из нашего массива

Конечным результатом является массив с тем же порядком, но без дубликатов элементов. Единственным недостатком является то, что ваши элементы должны соответствовать протоколу Hashable, но это не должно быть большой проблемой. Многие типы в стандартной библиотеке уже соответствуют этому протоколу, например строки, целые числа и булевы значения. Протокол Hashable используется для определения, равен ли элемент существующему объекту, и поэтому он необходим для получения уникальных значений. Однако есть способ отфильтровать элементы, не следуя этому протоколу.

Удаление дубликатов элементов по любому хэшируемому значению

Есть сценарии, в которых вы хотите удалить дубликаты элементов на основе значения объекта. Часто это значение уже соответствует Hashable, что избавляет от необходимости приводить ваш кастомный тип в соответствие с протоколом.

Расширение для этого выглядит следующим образом:

extension Sequence {
    func unique<T: Hashable>(by keyForValue: (Iterator.Element) throws -> T) rethrows -> [Iterator.Element] {
        var seen: Set<T> = []
        return try filter { try seen.insert(keyForValue($0)).inserted }
    }
}

Чтобы лучше объяснить этот случай использования, я хотел бы представить следующие примеры с данными:

struct Employee: CustomDebugStringConvertible {
    let name: String
    let title: String
    
    var debugDescription: String {
        "\(name) - \(title)"
    }
}

let employees: [Employee] = [
    Employee(name: "Antoine", title: "Swift Developer"), // duplicate title
    Employee(name: "Dorian", title: "Swift Developer"), // duplicate title
    Employee(name: "Ralph", title: "Head of Sales"),
    Employee(name: "Niek", title: "Frontend Developer"),
    Employee(name: "Kristy", title: "Head of Marketing")
]

Если бы мы использовали наш оригинальный метод unique, результат был бы следующим:

let regularUnique = employees.unique()
print(regularUnique)

/// Prints:
/// [
///     Antoine - Swift Developer,
///     Dorian - Swift Developer,
///     Ralph - Head of Sales,
///     Niek - Frontend Developer,
///     Kristy - Head of Marketing
/// ]

Все значения являются уникальными, если рассматривать свойства всех членов вместе. Однако, если мы хотим отфильтровать уникальные названия должностей, мы можем воспользоваться новым расширением:

let uniqueByTitle = employees.unique(by: { $0.title })
print(uniqueByTitle)

/// Prints:
/// [
///     Antoine - Swift Developer,
///     Ralph - Head of Sales,
///     Niek - Frontend Developer,
///     Kristy - Head of Marketing
/// ]

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

Использование пакета Swift Algorithms

Хотя эта статья направлена на предоставление простого расширения для удаления дубликатов, возможно, вы уже используете пакет алгоритмов Swift. Этот пакет с открытым исходным кодом от Apple предоставляет метод uniqued(), который можно посмотреть здесь.

Для работы необходимо добавить весь пакет, что не всегда может быть желательным. Однако это хорошо поддерживаемый пакет, разработанный Apple, который содержит много других полезных функций.

Вывод

Если вам нужны уникальные элементы коллекции, вы можете использовать Set или расширение Array.

  • Если порядок не важен, используйте набор
  • Сохраните порядок и извлеките уникальные элементы с помощью расширения массива

Источник

Если вы нашли опечатку - выделите ее и нажмите Ctrl + Enter! Для связи с нами вы можете использовать info@apptractor.ru.
Telegram

Популярное

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: