Видалення дублікатів елементів із масиву в 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
Найпростіший спосіб - перетворити масив в NSSet, NSSet - це не упорядкована колекція об'єктів, якщо потрібно підтримувати порядок NSOrderedSet.
Андреа

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

Не є частиною Свіфта, але я використовую долар. $.uniq(array) github.com/ankurp/Dollar#uniq---uniq
Енді

Напевно, найелегантніша, найрозумніша і найшвидша відповідь надана нижче відповіді mxcl. Що також допомагає підтримувати порядок
мед

1
Чому ви просто не користуєтесь SetSwift? Ви зможете надати список не упорядкованих та унікальних елементів.
TibiaZ

Відповіді:


133

Ви можете скочувати свою власну, наприклад, як це ( оновлено для Swift 1.2 із набором ):

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 }
Швидкість

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до набору можуть бути додані лише типи даних, але будь-який тип даних може бути доданий до масиву.
Mecki

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 (масив: userNames)) як! [String] якщо у вас є масив String, а не Any
Запоріжченко Олександр

Виходить з ладу, якщо елементів в ньому arrayнемає Hashable; Hashableдо набору можуть бути додані лише типи даних, але будь-який тип даних може бути доданий до масиву.
Mecki

Тестований у Swift 5.1b5, враховуючи, що елементи є "Газбіл" та бажання зберегти впорядкування, NSOrderedSet (масив: масив). Я протестував 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 різних змінних, string та [string]
Девід Зек

@David Seek Здається, що ви не зробили ваші суворі хешируемие або зрівняльні. Це правильно?
Джессі

1
@DavidSeek як це, uniqueArray = nonUniqueArray.uniqueElements
Мерт Челік

так, не хвилюйтеся. змусив це працювати відразу після. вже майже 2 роки: P
David Seek

Це матиме O(n²)ефективність у часі, що справді погано для великих масивів.
Дункан С

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
@Alexander Лео Дабус замінив reduceреалізацію, тому зараз складність інша.
Cœur

1
Результати цікаві. І для 1 мільйона унікальних предметів, і для 8 мільйонів версія фільтра швидша. Однак версія на основі фільтрів займає 8,38x довше для 8 мільйонів унікальних елементів (волосся з O(n)часом), де версія на основі плоскої карти займає 7,47x довше на 8 мільйонів унікальних записів, ніж на 1 мільйон, що дозволяє припустити, що версія на основі плоских карт масштабується краще . Якась версія на основі плоскої карти трохи краще, ніж O(n)час!
Дункан C

1
Насправді, коли я запускаю тест із 64-кратними елементами в масиві, версія на основі плоскої карти швидша.
Duncan C

43

Швидкий 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
Після простого профілювання цей метод дійсно швидкий. Його в сотні разів швидше, ніж використання скорочення (_: _ :), або навіть зменшення (у: _ :)
Келвін

3
@Kelvin Тому що всі ці інші алгоритми були O(n^2), і ніхто не помітив.
Олександр - Відновіть Моніку

@Kelvin ця відповідь ідентична відповіді Енеко Алонсо + мій коментар (16 червня 17 року).
Cœur

27

Швидкий 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²)ефективність у часі, що справді погано для великих масивів.
Duncan C

@NickGaens Ні, це не так, це O(n²). У цьому немає нічого швидкого.
Олександр - Відновіть Моніку

@ Cœur reduceабо reduce(into:)не призвело б до критичного значення. Переписуйте це, щоб не повторювати дзвінкиcontains зробило б набагато більшу різницю.
Олександр - Відновіть Моніку

16

Ось категорія, в SequenceTypeякій зберігається початковий порядок масиву, але використовується a, Setщоб зробити containsпошук, щоб уникнути O(n)витрат на contains(_:)метод Array .

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: ==)
    }
}

Нарешті, ви можете додати версію ключового контуру унікальної так:

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)рішенням. Ви можете поєднати операції набору "перевірити" та "вставити" в одну, до речі. Дивіться stackoverflow.com/a/46354989/3141234
Олександр - Відновіть Моніку

О, це розумно :)
deanWombourne

14

Натхненний 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, але відповідає послідовності, ви можете мати це додаткове розширення, але типом повернення завжди буде масив:

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
@Alexander Я намагався встановити за замовчуванням Self, але тоді мені потрібно було б зробити це 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). Він не вимагає відповідності сумішшю і працює в квадратичний час.


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замість цього, Dictionaryі ви отримаєте мою нагороду.
Микола Рухе


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

Швидкий 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

Швидкий 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 } } }
Марсело де Агуяр

Немає потреби використовувати a 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)). Як зазначається в коментарях, він зберігає порядок і працює над елементами, які не підлягають миттю.

Однак метод @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 вже є поточною версією.
oradyvan

Це дуже елегантне рішення; на жаль, це також досить повільно.
Колін Старк

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

6

Для масивів, де елементи не є ні Hashable, ні порівнянні (наприклад, складні об'єкти, словники чи структури), це розширення забезпечує узагальнений спосіб видалення дублікатів:

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, і це дозволяє використовувати різні комбінації полів для унікальності.

Примітка: для більш надійного підходу див. Рішення, запропоноване Коуром, у коментарях нижче.

stackoverflow.com/a/55684308/1033581

[EDIT] 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. Не роби цього, насправді. Мета рядка - не бути спеціальним механізмом генерації ключів у гетто. Просто обмежуйся Tбуттям Hashable.
Олександр - Відновіть Моніку

@Alexander Я застосував цю ідею в новій відповіді: stackoverflow.com/a/55684308/1033581
Cœur

Ідеальна відповідь, як я хочу. Дуже дякую.
Hardik Thakkar

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

Немає потреби використовувати a 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до набору можуть бути додані лише типи даних, але будь-який тип даних може бути доданий до масиву.
Mecki

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
Замість того, щоб використовувати a Setзі звичайним DistinctWrapper, вам слід використовувати Dictionaryвід distctAtributes до об’єктів. Якщо ви будете слідувати цій логіці, ви, зрештою, втілите в життя [ 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) })
  }
}

Немає потреби використовувати a 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

вихід масивуWithoutDuplicates - [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.