Удаление дублирующихся элементов из массива в Swift


252

В настоящее время в Swift вы просто печатаете, Set( yourArray )чтобы сделать массив уникальным. (Или заказанный набор при необходимости.)

До того, как это было возможно, как это было сделано?


Я мог бы иметь массив, который выглядит следующим образом:

[1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]

Или, на самом деле, любая последовательность однотипных частей данных. Что я хочу сделать, это убедиться, что есть только один из каждого идентичного элемента. Например, приведенный выше массив станет:

[1, 4, 2, 6, 24, 15, 60]

Обратите внимание, что дубликаты 2, 6 и 15 были удалены, чтобы гарантировать, что был только один из каждого идентичного элемента. Предоставляет ли Swift способ сделать это легко, или мне придется делать это самому?


11
Самый простой способ - преобразовать массив в NSSetNSSet - это неупорядоченная коллекция объектов, если необходимо сохранить порядок NSOrderedSet.
Андреа

Вы можете использовать функцию пересечения, как вы можете найти в этом классе с функциями для массивов: github.com/pNre/ExSwift/blob/master/ExSwift/Array.swift
Эдвин Вермеер,

Не является частью Swift, но я использую доллар. $.uniq(array) github.com/ankurp/Dollar#uniq---uniq
Энди

Вероятно, самый элегантный, самый умный и быстрый ответ дает ответ mxcl ниже. Что также помогает поддерживать порядок
мед

1
Почему бы вам просто не использовать Setот Swift? Вы сможете предоставить список неупорядоченных и уникальных элементов.
TibiaZ

Ответы:


133

Вы можете бросить свой собственный, например, так ( обновлено для Swift 1.2 с Set ):

func uniq<S : SequenceType, T : Hashable where S.Generator.Element == T>(source: S) -> [T] {
    var buffer = [T]()
    var added = Set<T>()
    for elem in source {
        if !added.contains(elem) {
            buffer.append(elem)
            added.insert(elem)
        }
    }
    return buffer
}

let vals = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
let uniqueVals = uniq(vals) // [1, 4, 2, 6, 24, 15, 60]

Версия Swift 3:

func uniq<S : Sequence, T : Hashable>(source: S) -> [T] where S.Iterator.Element == T {
    var buffer = [T]()
    var added = Set<T>()
    for elem in source {
        if !added.contains(elem) {
            buffer.append(elem)
            added.insert(elem)
        }
    }
    return buffer
}

И как расширение для Array:

extension Array where Element: Hashable {
    var uniques: Array {
        var buffer = Array()
        var added = Set<Element>()
        for elem in self {
            if !added.contains(elem) {
                buffer.append(elem)
                added.insert(elem)
            }
        }
        return buffer
    }
}

12
Вы также можете реализовать тело этой функции какvar addedDict = [T:Bool](); return filter(source) { addedDict(true, forKey: $0) == nil }
Airspeed Velocity

1
@AirspeedVelocity: Вы имели в виду updateValue(true, forKey: $0)...вместоaddedDict(true, forKey: $0)...
Jawwad

1
Ой да извините я случайно метод! Должно быть, return filter(source) { addedDict.updateValue(true, forKey: $0) == nil }как вы говорите.
Скорость

21
Просто предостережение: не обсуждайте производительность для простых функций, подобных этой, пока вы не будете явно зависеть от их производительности, и в этот момент единственное, что вам нужно сделать, - это тест. Слишком часто я видел не поддерживаемый код или даже менее производительный код из-за допущений. :) Кроме того, это, вероятно, легче понять:let uniques = Array(Set(vals))
Blixt

11
@Blixt Согласен. Еще раз, здесь преимущество заключается в соблюдении порядка элементов исходного массива.
Жан-Филипп Пелле

493

Вы можете легко преобразовать в набор и снова вернуться в массив:

let unique = Array(Set(originals))

Это не гарантирует сохранение исходного порядка массива.


37
Есть ли способ использовать набор при сохранении исходного порядка массива?
Crashalot

6
@Crashalot Смотри мой ответ.
Жан-Филипп Пелле

5
Если вам нужно сохранить уникальность объектов по определенному свойству, тогда также реализуйте протокол Hashable и Equatable для этого класса, вместо того, чтобы просто использовать преобразование Array-> Set-> Array
Fawkes

2
Ницца!! Какова временная сложность этого решения, пожалуйста?
JW.ZG

2
Сбой, если элементы в originalsне являются Hashable; Hashableв набор могут быть добавлены только типы данных, но в массив может быть добавлен любой тип данных.
Меки

69

Здесь доступно много ответов, но я пропустил это простое расширение, подходящее для Swift 2 и выше:

extension Array where Element:Equatable {
    func removeDuplicates() -> [Element] {
        var result = [Element]()

        for value in self {
            if result.contains(value) == false {
                result.append(value)
            }
        }

        return result
    }
}

Делает это очень просто. Можно назвать так:

let arrayOfInts = [2, 2, 4, 4]
print(arrayOfInts.removeDuplicates()) // Prints: [2, 4]

Фильтрация по свойствам

Чтобы отфильтровать массив на основе свойств, вы можете использовать этот метод:

extension Array {

    func filterDuplicates(@noescape includeElement: (lhs:Element, rhs:Element) -> Bool) -> [Element]{
        var results = [Element]()

        forEach { (element) in
            let existingElements = results.filter {
                return includeElement(lhs: element, rhs: $0)
            }
            if existingElements.count == 0 {
                results.append(element)
            }
        }

        return results
    }
}

Который вы можете позвонить следующим образом:

let filteredElements = myElements.filterDuplicates { $0.PropertyOne == $1.PropertyOne && $0.PropertyTwo == $1.PropertyTwo }

@ Antoine Спасибо за фильтрацию по расширению свойств. Это действительно полезно. Но не могли бы вы объяснить, как это работает. Это слишком сложно понять для меня. Спасибо
Мостафа Мохамед Раафат

Обновления для swift 3: func filterDuplicates (_ includeElement: (_ lhs: Element, _ rhs: Element) -> Bool) -> [Element] {
cbartel

Первая часть этого ответа ( extension Array where Element: Equatable) заменяется stackoverflow.com/a/36048862/1033581, который предлагает более мощное решение ( extension Sequence where Iterator.Element: Equatable).
Cœur

7
Это будет иметь O(n²)временную производительность, что очень плохо для больших массивов.
Дункан C

Вы должны использовать набор, чтобы отслеживать элементы, видимые до сих пор, чтобы вернуть эту ужасную O(n²)сложность кO(n)
Александр - Восстановить Монику

63

Swift 3.0

let uniqueUnordered = Array(Set(array))
let uniqueOrdered = Array(NSOrderedSet(array: array))

1
пусть uniqueOrderedNames = Array (NSOrderedSet (array: userNames)) как! [String] если у вас есть массив String, а не Any
Запорожченко Александр

Сбой, если элементы в arrayне являются Hashable; Hashableв набор могут быть добавлены только типы данных, но в массив может быть добавлен любой тип данных.
Меки

Протестировано в Swift 5.1b5, учитывая, что элементы являются Hashable и желание сохранить порядок, NSOrderedSet (array: array) .array немного быстрее, чем чистый swift-функция uniqued (), использующий набор с фильтром. Я протестировал 5100 строк, что привело к 13 уникальным значениям.
Dlemex

62

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

public extension Sequence where Element: Hashable {
  var firstUniqueElements: [Element] {
    var set: Set<Element> = []
    return filter { set.insert($0).inserted }
  }
}

public extension Sequence where Element: Equatable {
  var firstUniqueElements: [Element] {
    reduce(into: []) { uniqueElements, element in
      if !uniqueElements.contains(element) {
        uniqueElements.append(element)
      }
    }
  }
}

Если порядок не важен, то вы всегда можете просто использовать этот инициализатор Set .


хорошо, понял. я не смог вызвать его, потому что мой массив - это массив структур ... как бы я справился с этим в моем случае? структура из 20 различных переменных, строка и [строка]
Дэвид Сик

@ Давид Seek Похоже, вы не сделали свой строгий хэшируемый или уравновешенный. Это правильно?
Джесси

1
@DavidSeek вот так: uniqueArray = nonUniqueArray.uniqueElements
Мерт Челик,

да не волнуйся получил это работает сразу после. Прошло почти 2 года: P
David Seek

Это будет иметь O(n²)временную производительность, что очень плохо для больших массивов.
Дункан C

44

редактировать / обновлять Swift 4 или новее

Мы также можем расширить RangeReplaceableCollectionпротокол, чтобы использовать его с StringProtocolтипами:

extension RangeReplaceableCollection where Element: Hashable {
    var orderedSet: Self {
        var set = Set<Element>()
        return filter { set.insert($0).inserted }
    }
    mutating func removeDuplicates() {
        var set = Set<Element>()
        removeAll { !set.insert($0).inserted }
    }
}

let integers = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
let integersOrderedSet = integers.orderedSet // [1, 4, 2, 6, 24, 15, 60]

"abcdefabcghi".orderedSet  // "abcdefghi"
"abcdefabcghi".dropFirst(3).orderedSet // "defabcghi"

Метод мутации:

var string = "abcdefabcghi"
string.removeDuplicates() 
string  //  "abcdefghi"

var substring = "abcdefabcdefghi".dropFirst(3)  // "defabcdefghi"
substring.removeDuplicates()
substring   // "defabcghi"

Для Swift 3 нажмите здесь


1
Мне нравится это, это работает с массивом словарей тоже!
DeyaEldeen


1
@ Александр Лео Дабус заменил reduceреализацию, так что теперь сложность другая.
Cœur

1
Результаты интересные. И для 1 миллиона уникальных предметов, и для 8 миллионов версия фильтра работает быстрее. Тем не менее, версия на основе фильтра занимает в 8,38 раза больше для 8 миллионов уникальных элементов (с течением O(n)времени), тогда как версия для плоской карты занимает в 7,47 раза больше для 8 миллионов уникальных записей, чем для 1 миллиона, предполагая, что версия на основе плоской карты масштабируется лучше , Каким-то образом версия на основе плоской карты работает немного лучше, чем O(n)время!
Дункан C

1
Фактически, когда я запускаю тест с 64-кратным количеством элементов в массиве, версия на основе плоской карты оказывается быстрее.
Дункан C

43

Swift 4

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

каждая попытка insertтакже вернет кортеж (inserted: Bool, memberAfterInsert: Set.Element). Смотрите документацию .

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


7
После простого профилирования этот метод действительно быстрый. Это в сотни раз быстрее, чем при использовании Reduce (_: _ :) или даже Reduce (into: _ :)
Кельвин,

3
@Kelvin Потому что все эти другие алгоритмы были O(n^2), и никто не заметил.
Александр - Восстановить Монику

@ Кельвин, этот ответ идентичен ответу Энеко Алонсо + мой комментарий (16 июня 17 года).
Cœur

27

Swift 4

Гарантированно сохранить заказ.

extension Array where Element: Equatable {
    func removingDuplicates() -> Array {
        return reduce(into: []) { result, element in
            if !result.contains(element) {
                result.append(element)
            }
        }
    }
}

Я использую это сейчас, только изменил имя метода для удаления дубликатов :)
J. Doe

Я предполагаю , что это решение является компактным, но я считаю , что решение deanWombourne отправил годом ранее , может быть немного более эффективным , чем reduce: в целом, это просто еще одна линия в целом ваш проект , чтобы написать вашу функцию: var unique: [Iterator.Element] = []; for element in self where !unique.contains(element) { unique.append(element) }; return unique. Я признаю, что еще не тестировал относительные характеристики.
Cœur

3
Это будет иметь O(n²)временную производительность, что очень плохо для больших массивов.
Дункан C

@NickGaens Нет, это не так O(n²). В этом нет ничего быстрого.
Александр - Восстановить Монику

@ Cœur reduceили reduce(into:)не будет иметь решающее значение. Переписав это, чтобы не повторять вызов, containsбудет ОЧЕНЬ большая разница.
Александр - Восстановить Монику

16

Вот категория по SequenceTypeкоторой сохраняет первоначальный порядок массива, но использует , Setчтобы сделать containsLookups , чтобы избежать O(n)затрат на массив contains(_:)метода.

public extension Sequence where Element: Hashable {

    /// Return the sequence with all duplicates removed.
    ///
    /// i.e. `[ 1, 2, 3, 1, 2 ].uniqued() == [ 1, 2, 3 ]`
    ///
    /// - note: Taken from stackoverflow.com/a/46354989/3141234, as 
    ///         per @Alexander's comment.
    func uniqued() -> [Element] {
        var seen = Set<Element>()
        return self.filter { seen.insert($0).inserted }
    }
}

Если вы не Hashable или Equatable, вы можете передать предикат для проверки равенства:

extension Sequence {

    /// Return the sequence with all duplicates removed.
    ///
    /// Duplicate, in this case, is defined as returning `true` from `comparator`.
    ///
    /// - note: Taken from stackoverflow.com/a/46354989/3141234
    func uniqued(comparator: @escaping (Element, Element) throws -> Bool) rethrows -> [Element] {
        var buffer: [Element] = []

        for element in self {
            // If element is already in buffer, skip to the next element
            if try buffer.contains(where: { try comparator(element, $0) }) {
                continue
            }

            buffer.append(element)
        }

        return buffer
    }
}

Теперь, если у вас нет Hashable, но являются Equatable, вы можете использовать этот метод:

extension Sequence where Element: Equatable {

    /// Return the sequence with all duplicates removed.
    ///
    /// i.e. `[ 1, 2, 3, 1, 2 ].uniqued() == [ 1, 2, 3 ]`
    ///
    /// - note: Taken from stackoverflow.com/a/46354989/3141234
    func uniqued() -> [Element] {
        return self.uniqued(comparator: ==)
    }
}

Наконец, вы можете добавить ключевую версию uniqued следующим образом:

extension Sequence {

    /// Returns the sequence with duplicate elements removed, performing the comparison usinig the property at
    /// the supplied keypath.
    ///
    /// i.e.
    ///
    /// ```
    /// [
    ///   MyStruct(value: "Hello"),
    ///   MyStruct(value: "Hello"),
    ///   MyStruct(value: "World")
    ///  ].uniqued(\.value)
    /// ```
    /// would result in
    ///
    /// ```
    /// [
    ///   MyStruct(value: "Hello"),
    ///   MyStruct(value: "World")
    /// ]
    /// ```
    ///
    /// - note: Taken from stackoverflow.com/a/46354989/3141234
    ///
    func uniqued<T: Equatable>(_ keyPath: KeyPath<Element, T>) -> [Element] {
        self.uniqued { $0[keyPath: keyPath] == $1[keyPath: keyPath] }
    }
}

Вы можете вставить оба из них в свое приложение, Swift выберет правильный в зависимости от типа вашей последовательности Iterator.Element.


Эй, наконец, кто-то с O(n)решением. Кстати, вы можете объединить операции set проверки и вставки в одну. См. Stackoverflow.com/a/46354989/3141234
Александр - Восстановить Монику

О, это умно :)
DeanWombourne

15

Вдохновленный https://www.swiftbysundell.com/posts/the-power-of-key-paths-in-swift , мы можем объявить более мощный инструмент, способный фильтровать уникальность для любого keyPath. Благодаря комментариям Александра к различным ответам относительно сложности, приведенные ниже решения должны быть почти оптимальными.

Не мутирующее решение

Мы расширили функцию, которая способна фильтровать уникальность для любого keyPath:

extension RangeReplaceableCollection {
    /// Returns a collection containing, in order, the first instances of
    /// elements of the sequence that compare equally for the keyPath.
    func unique<T: Hashable>(for keyPath: KeyPath<Element, T>) -> Self {
        var unique = Set<T>()
        return filter { unique.insert($0[keyPath: keyPath]).inserted }
    }
}

Примечание: в случае, если ваш объект не соответствует RangeReplaceableCollection, но соответствует Sequence, вы можете иметь это дополнительное расширение, но тип возвращаемого значения всегда будет Array:

extension Sequence {
    /// Returns an array containing, in order, the first instances of
    /// elements of the sequence that compare equally for the keyPath.
    func unique<T: Hashable>(for keyPath: KeyPath<Element, T>) -> [Element] {
        var unique = Set<T>()
        return filter { unique.insert($0[keyPath: keyPath]).inserted }
    }
}

использование

Если мы хотим уникальности для самих элементов, как в вопросе, мы используем keyPath \.self:

let a = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
let b = a.unique(for: \.self)
/* b is [1, 4, 2, 6, 24, 15, 60] */

Если мы хотим уникальности для чего-то другого (например, для idколлекции объектов), тогда мы используем keyPath по нашему выбору:

let a = [CGPoint(x: 1, y: 1), CGPoint(x: 2, y: 1), CGPoint(x: 1, y: 2)]
let b = a.unique(for: \.y)
/* b is [{x 1 y 1}, {x 1 y 2}] */

Мутирующий раствор

Мы расширяем его мутирующей функцией, которая способна фильтровать уникальность для любого keyPath:

extension RangeReplaceableCollection {
    /// Keeps only, in order, the first instances of
    /// elements of the collection that compare equally for the keyPath.
    mutating func uniqueInPlace<T: Hashable>(for keyPath: KeyPath<Element, T>) {
        var unique = Set<T>()
        removeAll { !unique.insert($0[keyPath: keyPath]).inserted }
    }
}

использование

Если мы хотим уникальности для самих элементов, как в вопросе, мы используем keyPath \.self:

var a = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
a.uniqueInPlace(for: \.self)
/* a is [1, 4, 2, 6, 24, 15, 60] */

Если мы хотим уникальности для чего-то другого (например, для idколлекции объектов), тогда мы используем keyPath по нашему выбору:

var a = [CGPoint(x: 1, y: 1), CGPoint(x: 2, y: 1), CGPoint(x: 1, y: 2)]
a.uniqueInPlace(for: \.y)
/* a is [{x 1 y 1}, {x 1 y 2}] */

1
Теперь это хорошая реализация! Я только с тем, что ключевые пути были конвертируемыми в замыкания, так что вы можете использовать аргумент замыкания для поддержки как произвольного кода (в замыканиях), так и простого поиска свойств (через ключевые пути). Единственное изменение, которое я бы сделал, - это сделать по keyPathумолчанию \.self, потому что это, вероятно, большинство вариантов использования.
Александр - Восстановить Монику

1
@ Александр Я пытался по умолчанию на себя, но тогда мне нужно будет сделать Elementвсегда Hashable. Альтернативой значению по умолчанию является добавление простой перегрузки без параметров:extension Sequence where Element: Hashable { func unique() { ... } }
Cœur


1
Блестящий ... простой, а лучше всего «гибкий». Спасибо.
BonanzaDriver

12

Отсюда альтернативное (если не оптимальное) решение с использованием неизменяемых типов, а не переменных:

func deleteDuplicates<S: ExtensibleCollectionType where S.Generator.Element: Equatable>(seq:S)-> S {
    let s = reduce(seq, S()){
        ac, x in contains(ac,x) ? ac : ac + [x]
    }
    return s
}

Включенный, чтобы противопоставить императивный подход Жана-Пилиппа с функциональным подходом.

В качестве бонуса эта функция работает как со строками, так и с массивами!

Изменить: Этот ответ был написан в 2014 году для Swift 1.0 (до того, как Setбыл доступен в Swift). Не требует соответствия Hashable и работает в квадратичном времени.


8
Остерегайтесь, есть не один, а два способа, которыми это выполняется за квадратичное время - оба, containsи добавление массива, запускаются в O (n). Хотя он имеет преимущество только в том, что требует равных, а не хэш.
Скорость

это действительно сложный способ написания filter. Это O (n ^ 2) (что необходимо, если вы не хотите требовать Hashableсоответствия), но вы должны по крайней мере явно заявить об этом
Александр - Восстановить Монику

10

Свифт 2

с uniq функцией ответа:

func uniq<S: SequenceType, E: Hashable where E==S.Generator.Element>(source: S) -> [E] {
    var seen: [E:Bool] = [:]
    return source.filter({ (v) -> Bool in
        return seen.updateValue(true, forKey: v) == nil
    })
}

использовать:

var test = [1,2,3,4,5,6,7,8,9,9,9,9,9,9]
print(uniq(test)) //1,2,3,4,5,6,7,8,9

BoolЗначение , очевидно , является излишним, так как ваш код никогда не читает. Используйте Setвместо a, Dictionaryи вы получите мой upvote.
Николай Рюэ,


9

Еще одно решение Swift 3.0 для удаления дубликатов из массива. Это решение улучшает многие другие решения, уже предложенные:

  • Сохранение порядка элементов во входном массиве
  • Линейная сложность O (n): однопроходной фильтр O (n) + установка вставки O (1)

Учитывая целочисленный массив:

let numberArray = [10, 1, 2, 3, 2, 1, 15, 4, 5, 6, 7, 3, 2, 12, 2, 5, 5, 6, 10, 7, 8, 3, 3, 45, 5, 15, 6, 7, 8, 7]

Функциональный код:

func orderedSet<T: Hashable>(array: Array<T>) -> Array<T> {
    var unique = Set<T>()
    return array.filter { element in
        return unique.insert(element).inserted
    }
}

orderedSet(array: numberArray)  // [10, 1, 2, 3, 15, 4, 5, 6, 7, 12, 8, 45]

Код расширения массива:

extension Array where Element:Hashable {
    var orderedSet: Array {
        var unique = Set<Element>()
        return filter { element in
            return unique.insert(element).inserted
        }
    }
}

numberArray.orderedSet // [10, 1, 2, 3, 15, 4, 5, 6, 7, 12, 8, 45]

Этот код использует в своих интересах результат, возвращаемый insertоперацией on Set, которая выполняется O(1)и возвращает кортеж, указывающий, был ли элемент вставлен или уже существовал в наборе.

Если предмет был в наборе, filterисключим его из окончательного результата.


1
Не будьте разборчивы, но вы будете выполнять вставку и проверку членства столько раз, сколько есть элементов, поэтому вы должны также учитывать их стоимость как O (n). Однако это не означает 3xO (n), потому что эти O и стоимость фильтра не равны, поэтому добавление O (n) - это яблоки к апельсинам. Если мы считаем, что операции над множествами являются O (1) частью стоимости фильтра, сложность просто O (n), хотя и с большим «O». Нажимая на это до предела, вы также можете избежать вставки, когда элемент уже находится в наборе.
Алена Т.

Вы правы, с deferпомощью кода дважды выполнялась бы заданная тестовая операция: одна с, containsа другая с insert. Продолжая читать документацию по Swift, я обнаружил, что insertвозвращает кортеж, указывающий, был ли элемент вставлен или нет, поэтому я упростил код, удалив containsпроверку.
Энеко Алонсо

2
Ницца. Ваше продление может быть оптимальным, если вы extension Sequence where Iterator.Element: Hashable { ... }
Cœur

@AlainT. Нет. Оба insertи containsимеют O(1)сложность. O(1) + O(1) = O(1), Затем эти две операции выполняются nраз (один раз на вызов переданного замыкания filter, который вызывается один раз на элемент). Т.е., если операция занимает постоянное количество времени независимо от размера ввода, то выполнение ее дважды все равно заставляет ее занимать постоянное время. это независимо от размера ввода. Общая сложность этого есть O(n).
Александр - Восстановить Монику

9

Swift 4.x:

extension Sequence where Iterator.Element: Hashable {
  func unique() -> [Iterator.Element] {
    return Array(Set<Iterator.Element>(self))
  }

  func uniqueOrdered() -> [Iterator.Element] {
    return reduce([Iterator.Element]()) { $0.contains($1) ? $0 : $0 + [$1] }
  }
}

использование:

["Ljubljana", "London", "Los Angeles", "Ljubljana"].unique()

или

["Ljubljana", "London", "Los Angeles", "Ljubljana"].uniqueOrdered()

Это O(n^2). Не делай этого.
Александр - Восстановить Монику

8

Swift 5

extension Sequence where Element: Hashable {
    func unique() -> [Element] {
        NSOrderedSet(array: self as! [Any]).array as! [Element]
    }
}

Я сделал несколько вариантов, чтобы выбрать ключ для сравнения. extension Sequence { // Returns distinct elements based on a key value. func distinct<key: Hashable>(by: ((_ el: Iterator.Element) -> key)) -> [Iterator.Element] { var existing = Set<key>() return self.filter { existing.insert(by($0)).inserted } } }
Марсело де Агиар

Там нет необходимости использовать Bool, когда единственное значение, которое вы используете true. Вы достигаете "тип единицы" (тип только с одним возможным значением). Тип модуля Свифта - это Voidединственное значение ()(или пустой кортеж). Так что вы можете просто использовать [T: Void]. Хотя вы не должны этого делать, потому что вы в основном только что изобрели Set. Используйте Setвместо этого. См. Stackoverflow.com/a/55684308/3141234 Пожалуйста, удалите этот ответ.
Александр - Восстановить Монику

8

Думай как функциональный программист :)

Чтобы отфильтровать список на основе того, был ли элемент уже найден, вам нужен индекс. Вы можете использовать, enumeratedчтобы получить индекс и mapвернуться к списку значений.

let unique = myArray
    .enumerated()
    .filter{ myArray.firstIndex(of: $0.1) == $0.0 }
    .map{ $0.1 }

Это гарантирует порядок. Если вы не возражаете против порядка, то существующий ответ Array(Set(myArray))проще и, вероятно, более эффективен.


ОБНОВЛЕНИЕ: некоторые примечания по эффективности и правильности

Несколько человек прокомментировали эффективность. Я определенно в школе сначала пишу правильный и простой код, а потом выясняю узкие места, хотя я ценю спор о том, яснее ли это Array(Set(array)).

Этот метод намного медленнее, чем Array(Set(array)). Как отмечается в комментариях, он сохраняет порядок и работает с элементами, которые не являются Hashable.

Тем не менее, метод @Alain T также сохраняет порядок и работает намного быстрее. Так что, если ваш тип элемента не является хэшируемым, или вам просто нужен быстрый однострочник, я бы посоветовал перейти к их решению.

Вот несколько тестов на MacBook Pro (2014) на Xcode 11.3.1 (Swift 5.1) в режиме выпуска.

Функция профилирования и два метода для сравнения:

func printTimeElapsed(title:String, operation:()->()) {
    var totalTime = 0.0
    for _ in (0..<1000) {
        let startTime = CFAbsoluteTimeGetCurrent()
        operation()
        let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
        totalTime += timeElapsed
    }
    let meanTime = totalTime / 1000
    print("Mean time for \(title): \(meanTime) s")
}

func method1<T: Hashable>(_ array: Array<T>) -> Array<T> {
    return Array(Set(array))
}

func method2<T: Equatable>(_ array: Array<T>) -> Array<T>{
    return array
    .enumerated()
    .filter{ array.firstIndex(of: $0.1) == $0.0 }
    .map{ $0.1 }
}

// Alain T.'s answer (adapted)
func method3<T: Hashable>(_ array: Array<T>) -> Array<T> {
    var uniqueKeys = Set<T>()
    return array.filter{uniqueKeys.insert($0).inserted}
}

И небольшое разнообразие тестовых входов:

func randomString(_ length: Int) -> String {
  let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
  return String((0..<length).map{ _ in letters.randomElement()! })
}

let shortIntList = (0..<100).map{_ in Int.random(in: 0..<100) }
let longIntList = (0..<10000).map{_ in Int.random(in: 0..<10000) }
let longIntListManyRepetitions = (0..<10000).map{_ in Int.random(in: 0..<100) }
let longStringList = (0..<10000).map{_ in randomString(1000)}
let longMegaStringList = (0..<10000).map{_ in randomString(10000)}

Дает в качестве вывода:

Mean time for method1 on shortIntList: 2.7358531951904296e-06 s
Mean time for method2 on shortIntList: 4.910230636596679e-06 s
Mean time for method3 on shortIntList: 6.417632102966309e-06 s
Mean time for method1 on longIntList: 0.0002518167495727539 s
Mean time for method2 on longIntList: 0.021718120217323302 s
Mean time for method3 on longIntList: 0.0005312927961349487 s
Mean time for method1 on longIntListManyRepetitions: 0.00014377200603485108 s
Mean time for method2 on longIntListManyRepetitions: 0.0007293639183044434 s
Mean time for method3 on longIntListManyRepetitions: 0.0001843773126602173 s
Mean time for method1 on longStringList: 0.007168249964714051 s
Mean time for method2 on longStringList: 0.9114790915250778 s
Mean time for method3 on longStringList: 0.015888616919517515 s
Mean time for method1 on longMegaStringList: 0.0525397013425827 s
Mean time for method2 on longMegaStringList: 1.111266262292862 s
Mean time for method3 on longMegaStringList: 0.11214958941936493 s

1
в отличие от Array(Set(myArray))этого, это работает для вещей, которые не являютсяHashable
Портер Чайлд

1
... и в отличие Array(Set(myArray))от порядка вашего массива поддерживается.
Сандер Саелманс

Мне кажется, это лучший ответ, по крайней мере, в настоящее время, когда Swift 5 уже является текущей версией.
Орадыв

Это очень элегантное решение; к сожалению, это также довольно медленно.
Колин Старк

1
@TimMB О, я неправильно прочитал ваш пост. Я видел чью-то адаптацию, которая использовала lastIndex(of:). Я полностью не согласен по поводу ясности и оптимизации в этом случае. Я не думаю, что эта реализация особенно ясна, особенно по сравнению с простым решением на основе множеств. В любом случае, такой код должен быть извлечен в функцию расширения. Этот алгоритм становится практически непригодным даже при небольшом входном размере, например, от тысяч до десятков тысяч. Нетрудно найти такие наборы данных, у людей могут быть тысячи песен, файлов, контактов и т. Д.
Александр - Восстановите Монику

6

Для массивов, в которых элементы не являются ни Hashable, ни Comparable (например, сложные объекты, словари или структуры), это расширение предоставляет обобщенный способ удаления дубликатов:

extension Array
{
   func filterDuplicate<T:Hashable>(_ keyValue:(Element)->T) -> [Element]
   {
      var uniqueKeys = Set<T>()
      return filter{uniqueKeys.insert(keyValue($0)).inserted}
   }

   func filterDuplicate<T>(_ keyValue:(Element)->T) -> [Element]
   { 
      return filterDuplicate{"\(keyValue($0))"}
   }
}

// example usage: (for a unique combination of attributes):

peopleArray = peopleArray.filterDuplicate{ ($0.name, $0.age, $0.sex) }

or...

peopleArray = peopleArray.filterDuplicate{ "\(($0.name, $0.age, $0.sex))" }

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

Примечание: для более надежного подхода см. Решение, предложенное Coeur, в комментариях ниже.

stackoverflow.com/a/55684308/1033581

[Редактировать] Swift 4 альтернативы

В Swift 4.2 вы можете использовать класс Hasher для создания хешей. Вышеупомянутое расширение может быть изменено, чтобы использовать это:

extension Array
{
    func filterDuplicate(_ keyValue:((AnyHashable...)->AnyHashable,Element)->AnyHashable) -> [Element]
    {
        func makeHash(_ params:AnyHashable ...) -> AnyHashable
        { 
           var hash = Hasher()
           params.forEach{ hash.combine($0) }
           return hash.finalize()
        }  
        var uniqueKeys = Set<AnyHashable>()
        return filter{uniqueKeys.insert(keyValue(makeHash,$0)).inserted}     
    }
}

Синтаксис вызова немного отличается, потому что замыкание получает дополнительный параметр, содержащий функцию для хэширования переменного числа значений (которое должно быть по отдельности Hashable)

peopleArray = peopleArray.filterDuplicate{ $0($1.name, $1.age, $1.sex) } 

Он также будет работать с одним значением уникальности (используя $ 1 и игнорируя $ 0).

peopleArray = peopleArray.filterDuplicate{ $1.name } 

Это может дать случайные результаты в зависимости от поведения "\()", так как это может не дать вам уникальные значения, такие как соответствие Hashableследует. Например, если ваши элементы соответствуют Printableвсем, возвращающим одно descriptionи то же , тогда ваша фильтрация завершается ошибкой.
Cœur

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

2
@AlainT. Не делай этого, правда. Цель String - не быть механизмом генерации ключей в гетто. Просто ограничивайся Tбытием Hashable.
Александр - Восстановить Монику

@ Александр Я применил эту идею в новом ответе: stackoverflow.com/a/55684308/1033581
Cœur

Идеальный ответ, как я хочу. Огромное спасибо.
Хардик Тхаккар

4

Вы можете напрямую использовать коллекцию наборов для удаления дубликатов, а затем преобразовать ее обратно в массив

var myArray = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
var mySet = Set<Int>(myArray)

myArray = Array(mySet) // [2, 4, 60, 6, 15, 24, 1]

Тогда вы можете заказать свой массив, как вы хотите

myArray.sort{$0 < $1} // [1, 2, 4, 6, 15, 24, 60]

«Тогда вы можете упорядочить свой массив так, как хотите». Что если я захочу установить тот же порядок, что и в исходном массиве? Это не так просто.
Александр - Восстановить Монику

3

Чуть более краткая синтаксическая версия ответа Даниэля Крома Swift 2 с использованием завершающего замыкания и сокращенного имени аргумента, которое, похоже, основано на первоначальном ответе Airspeed Velocity :

func uniq<S: SequenceType, E: Hashable where E == S.Generator.Element>(source: S) -> [E] {
  var seen = [E: Bool]()
  return source.filter { seen.updateValue(true, forKey: $0) == nil }
}

Пример реализации пользовательского типа, который может использоваться с uniq(_:)(который должен соответствовать Hashableи, следовательно Equatable, потому что Hashableрасширяется Equatable):

func ==(lhs: SomeCustomType, rhs: SomeCustomType) -> Bool {
  return lhs.id == rhs.id // && lhs.someOtherEquatableProperty == rhs.someOtherEquatableProperty
}

struct SomeCustomType {

  let id: Int

  // ...

}

extension SomeCustomType: Hashable {

  var hashValue: Int {
    return id
  }

}

В приведенном выше коде ...

id, как используется в перегрузке ==, может быть любого Equatableтипа (или метода, который возвращает Equatableтип, например, someMethodThatReturnsAnEquatableType()). Закомментированный код демонстрирует расширение проверки на равенство, где someOtherEquatablePropertyесть другое свойство Equatableтипа (но также может быть методом, который возвращает Equatableтип).

idкак используется в hashValueвычисляемом свойстве (требуется для соответствия Hashable), может быть любым Hashable(и, следовательно, Equatable) свойством (или методом, возвращающим Hashableтип).

Пример использования uniq(_:):

var someCustomTypes = [SomeCustomType(id: 1), SomeCustomType(id: 2), SomeCustomType(id: 3), SomeCustomType(id: 1)]

print(someCustomTypes.count) // 4

someCustomTypes = uniq(someCustomTypes)

print(someCustomTypes.count) // 3

Там нет необходимости использовать Bool, когда единственное значение, которое вы используете true. Вы достигаете "тип единицы" (тип только с одним возможным значением). Тип модуля Свифта - это Voidединственное значение ()(или пустой кортеж). Так что вы можете просто использовать [T: Void]. Хотя вы не должны этого делать, потому что вы в основном только что изобрели Set. Используйте Setвместо этого. См. Stackoverflow.com/a/55684308/3141234
Александр - Восстановить Монику

3

Если вам нужны отсортированные значения, это работает (Swift 4)

let sortedValues = Array(Set(array)).sorted()


2
Вы теряете порядок элементов в этом случае.
Шмидт

Вовсе нет, вот для чего .sorted()конец. С уважением.
Маурисио Чирино

@MauricioChirino А если ваш оригинальный массив был [2, 1, 1]? Это вышло бы [1, 2], это не заказано: p
Александр - Восстановите Монику

2
@MauricioChirino Нет, я не. Если цель состоит в том, чтобы удалить повторяющиеся значения из последовательности, сохраняя порядок, в котором элементы уникальным образом появляются, это не будет сделано. Очень четкий встречный пример [2, 1, 1]. Первое появление уникальных элементов в порядке [2, 1]. Это правильный ответ. Но с помощью (неправильный) алгоритм, вы получите [1, 2], который будет отсортирован, но не в правильном, оригинал, заказ.
Александр - Восстановить Монику

2
Сбой, если элементы в arrayне являются Hashable; Hashableв набор могут быть добавлены только типы данных, но в массив может быть добавлен любой тип данных.
Меки

3

Вот решение, которое

  • Не использует устаревшие NSтипы
  • Достаточно быстро с O(n)
  • Сжат
  • Сохраняет порядок элементов
extension Array where Element: Hashable {

    var uniqueValues: [Element] {
        var allowed = Set(self)
        return compactMap { allowed.remove($0) }
    }
}

2

здесь я сделал O (n) решение для объектов. Решение не в несколько строк, но ...

struct DistinctWrapper <T>: Hashable {
    var underlyingObject: T
    var distinctAttribute: String
    var hashValue: Int {
        return distinctAttribute.hashValue
    }
}
func distinct<S : SequenceType, T where S.Generator.Element == T>(source: S,
                                                                distinctAttribute: (T) -> String,
                                                                resolution: (T, T) -> T) -> [T] {
    let wrappers: [DistinctWrapper<T>] = source.map({
        return DistinctWrapper(underlyingObject: $0, distinctAttribute: distinctAttribute($0))
    })
    var added = Set<DistinctWrapper<T>>()
    for wrapper in wrappers {
        if let indexOfExisting = added.indexOf(wrapper) {
            let old = added[indexOfExisting]
            let winner = resolution(old.underlyingObject, wrapper.underlyingObject)
            added.insert(DistinctWrapper(underlyingObject: winner, distinctAttribute: distinctAttribute(winner)))
        } else {
            added.insert(wrapper)
        }
    }
    return Array(added).map( { return $0.underlyingObject } )
}
func == <T>(lhs: DistinctWrapper<T>, rhs: DistinctWrapper<T>) -> Bool {
    return lhs.hashValue == rhs.hashValue
}

// tests
// case : perhaps we want to get distinct addressbook list which may contain duplicated contacts like Irma and Irma Burgess with same phone numbers
// solution : definitely we want to exclude Irma and keep Irma Burgess
class Person {
    var name: String
    var phoneNumber: String
    init(_ name: String, _ phoneNumber: String) {
        self.name = name
        self.phoneNumber = phoneNumber
    }
}

let persons: [Person] = [Person("Irma Burgess", "11-22-33"), Person("Lester Davidson", "44-66-22"), Person("Irma", "11-22-33")]
let distinctPersons = distinct(persons,
    distinctAttribute: { (person: Person) -> String in
        return person.phoneNumber
    },
    resolution:
    { (p1, p2) -> Person in
        return p1.name.characters.count > p2.name.characters.count ? p1 : p2
    }
)
// distinctPersons contains ("Irma Burgess", "11-22-33") and ("Lester Davidson", "44-66-22")

1
Вместо того, чтобы использовать Setс пользовательским DistinctWrapper, вы должны использовать Dictionaryот DifferentAttributes для объектов. Когда вы будете следовать этой логике, вы в конечном итоге будете реализовывать [ Dictionary.init(_:uniquingKeysWith:)] pastebin.com/w90pVe0p(https://developer.apple.com/documentation/… , который теперь встроен в стандартную библиотеку. Проверьте, насколько это просто pastebin.com/w90pVe0p
Александр - Восстановить Монику

2

Я использовал ответ @ Jean-Philippe Pellet и сделал расширение Array, которое выполняет операции над массивами, аналогичные множествам, сохраняя порядок элементов.

/// Extensions for performing set-like operations on lists, maintaining order
extension Array where Element: Hashable {
  func unique() -> [Element] {
    var seen: [Element:Bool] = [:]
    return self.filter({ seen.updateValue(true, forKey: $0) == nil })
  }

  func subtract(takeAway: [Element]) -> [Element] {
    let set = Set(takeAway)
    return self.filter({ !set.contains($0) })
  }

  func intersect(with: [Element]) -> [Element] {
    let set = Set(with)
    return self.filter({ set.contains($0) })
  }
}

Там нет необходимости использовать Bool, когда единственное значение, которое вы используете true. Вы достигаете "тип единицы" (тип только с одним возможным значением). Тип модуля Свифта - это Voidединственное значение ()(или пустой кортеж). Так что вы можете просто использовать [T: Void]. Хотя вы не должны этого делать, потому что вы в основном только что изобрели Set. Используйте Setвместо этого. См. Stackoverflow.com/a/55684308/3141234
Александр - Восстановить Монику

2

Это просто очень простая и удобная реализация. Вычисляемое свойство в расширении массива, которое имеет экватируемые элементы.

extension Array where Element: Equatable {
    /// Array containing only _unique_ elements.
    var unique: [Element] {
        var result: [Element] = []
        for element in self {
            if !result.contains(element) {
                result.append(element)
            }
        }

        return result
    }
}


2
func removeDublicate (ab: [Int]) -> [Int] {
var answer1:[Int] = []
for i in ab {
    if !answer1.contains(i) {
        answer1.append(i)
    }}
return answer1
}

Использование:

let f = removeDublicate(ab: [1,2,2])
print(f)

Я думаю, что это самое простое
Джек Рус

он сохраняет порядок и дает вам массив, который вы хотите
Джек Рус


2
  1. Сначала добавьте все элементы массива в NSOrderedSet.
  2. Это удалит все дубликаты в вашем массиве.
  3. Снова преобразуйте этот упорядоченный набор в массив.

Готово....

пример

let array = [1,1,1,1,2,2,2,2,4,6,8]

let orderedSet : NSOrderedSet = NSOrderedSet(array: array)

let arrayWithoutDuplicates : NSArray = orderedSet.array as NSArray

вывод массива без дубликатов - [1,2,4,6,8]


2

Слегка укороченная версия, основанная на ответе расширения массива @ Jean-Philippe Pellet:

extension Array where Element: Hashable {

    var uniques: Array {
        var added = Set<Element>()
        return filter { element in
            defer { added.insert(element) }
            return !added.contains(element)
        }
    }
}

Это делает две операции хеширования на элемент, что не нужно. insertвозвращает кортеж, который сообщает вам, был ли элемент уже там или был добавлен в первый раз. stackoverflow.com/a/55684308/3141234 Пожалуйста, удалите этот ответ.
Александр - Восстановить Монику

1

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

var arrayOfDates: NSArray = ["15/04/01","15/04/01","15/04/02","15/04/02","15/04/03","15/04/03","15/04/03"]

var datesOnlyDict = NSMutableDictionary()
var x = Int()

for (x=0;x<(arrayOfDates.count);x++) {
    let date = arrayOfDates[x] as String
    datesOnlyDict.setValue("foo", forKey: date)
}

let uniqueDatesArray: NSArray = datesOnlyDict.allKeys // uniqueDatesArray = ["15/04/01", "15/04/03", "15/04/02"]

println(uniqueDatesArray.count)  // = 3

Как видите, результирующий массив не всегда будет в «порядке». Если вы хотите отсортировать / заказать массив, добавьте это:

var sortedArray = sorted(datesOnlyArray) {
(obj1, obj2) in

    let p1 = obj1 as String
    let p2 = obj2 as String
    return p1 < p2
}

println(sortedArray) // = ["15/04/01", "15/04/02", "15/04/03"]

,


1

Самый простой способ - использовать NSOrderedSet, который хранит уникальные элементы и сохраняет порядок элементов. Подобно:

func removeDuplicates(from items: [Int]) -> [Int] {
    let uniqueItems = NSOrderedSet(array: items)
    return (uniqueItems.array as? [Int]) ?? []
}

let arr = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
removeDuplicates(from: arr)

Интересно, как эта производительность сравнивается с лучшими ответами здесь? Вы сравнивали?
Александр - Восстановить Монику
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.