Rimozione di elementi duplicati da un array in Swift


252

Oggi in Swift digiti semplicemente Set( yourArray ) per rendere un array unico. (O un set ordinato, se necessario.)

Prima che fosse possibile, come è stato fatto?


Potrei avere un array che assomiglia al seguente:

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

O, in realtà, qualsiasi sequenza di porzioni di dati tipizzate in modo simile. Quello che voglio fare è assicurarmi che ci sia solo uno di ciascun elemento identico. Ad esempio, l'array sopra diventa:

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

Si noti che i duplicati di 2, 6 e 15 sono stati rimossi per garantire che esistesse solo uno per ogni elemento identico. Swift fornisce un modo per farlo facilmente o dovrò farlo da solo?


11
Il modo più semplice è convertire l'array in un NSSet, NSSet è una raccolta non ordinata di oggetti, se è necessario mantenere l'ordine NSOrderedSet.
Andrea,

È possibile utilizzare la funzione di intersezione come è possibile trovare in questa classe con le funzioni per gli array: github.com/pNre/ExSwift/blob/master/ExSwift/Array.swift
Edwin Vermeer

Non faccio parte di Swift ma io uso Dollar. $.uniq(array) github.com/ankurp/Dollar#uniq---uniq
Andy,

Probabilmente la risposta più elegante, più intelligente e più veloce è fornita dalla risposta di mxcl di seguito. Il che aiuta anche a mantenere l'ordine
Honey

1
Perché non usi semplicemente SetSwift? Sarai in grado di fornire un elenco di elementi non ordinati e unici.
TibiaZ,

Risposte:


133

Puoi farlo tu stesso, ad esempio in questo modo ( aggiornato per Swift 1.2 con 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]

Versione 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
}

E come estensione per 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
Potresti anche implementare il corpo di quella funzione comevar addedDict = [T:Bool](); return filter(source) { addedDict(true, forKey: $0) == nil }
Airspeed Velocity

1
@AirspeedVelocity: volevi dire updateValue(true, forKey: $0)...invece diaddedDict(true, forKey: $0)...
Jawwad il

1
Oops sì scusa ho accidentalmente il metodo! Dovrebbe essere return filter(source) { addedDict.updateValue(true, forKey: $0) == nil }come dici tu.
Velocità della velocità

21
Solo una parola di cautela: evita di discutere delle prestazioni per funzioni semplici come questa fino a quando non dipenderai in modo dimostrabile dalle loro prestazioni, a quel punto l'unica cosa che dovresti fare è benchmark. Troppo spesso ho visto codice non mantenibile o codice ancora meno performante a causa di ipotesi. :) Inoltre, questo è probabilmente più facile da capire:let uniques = Array(Set(vals))
Blixt

11
@Blixt concordato. Ancora una volta, qui il vantaggio sta nel rispetto dell'ordine degli elementi dell'array originale.
Jean-Philippe Pellet,

493

È possibile convertire in un set e tornare nuovamente in un array abbastanza facilmente:

let unique = Array(Set(originals))

Questo non è garantito per mantenere l'ordine originale dell'array.


37
Esiste un modo per utilizzare un set preservando l'ordine originale dell'array?
Crashalot,

6
@Crashalot Vedi la mia risposta.
Jean-Philippe Pellet,

5
Se devi mantenere gli oggetti univoci per una proprietà specifica, implementa anche il protocollo Hashable ed Equatable su quella classe, invece di usare semplicemente la trasformazione Array-> Set-> Array
Fawkes

2
Bello!! Qual è la complessità temporale di questa soluzione, per favore?
JW.ZG,

2
Non riesce se gli elementi in originalsnon lo sono Hashable; solo Hashableun tipo di dati può essere aggiunto a un set, ma qualsiasi tipo di dati può essere aggiunto a un array.
Mecki,

69

Molte risposte disponibili qui, ma ho perso questa semplice estensione, adatta per Swift 2 e versioni successive:

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
    }
}

Lo rende super semplice. Può essere chiamato così:

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

Filtro basato su proprietà

Per filtrare un array in base alle proprietà, è possibile utilizzare questo metodo:

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
    }
}

Che puoi chiamare come segue:

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

@Antoine Grazie per il filtro basato sull'estensione delle proprietà. È davvero utile. Ma puoi spiegare come funziona. È troppo difficile da capire per me. Grazie
Mostafa Mohamed Raafat,

Aggiornamenti per swift 3: func filterDuplicates (_ includeElement: (_ lhs: Element, _ rhs: Element) -> Bool) -> [Element] {
cbartel

La prima parte di questa risposta ( extension Array where Element: Equatable) è sostituita da stackoverflow.com/a/36048862/1033581 che offre una soluzione più potente ( extension Sequence where Iterator.Element: Equatable).
Cœur,

7
Ciò avrà O(n²)prestazioni temporali, il che è davvero negativo per array di grandi dimensioni.
Duncan C,

Dovresti usare un set per tenere traccia degli elementi visti finora, per riportare questa terribile O(n²)complessità aO(n)
Alexander - Reinstate Monica il

63

Swift 3.0

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

1
let uniqueOrderedNames = Array (NSOrderedSet (array: userNames)) come! [String] se hai una matrice di String, non di Any
Zaporozhchenko Oleksandr,

Non riesce se gli elementi in arraynon lo sono Hashable; solo Hashableun tipo di dati può essere aggiunto a un set, ma qualsiasi tipo di dati può essere aggiunto a un array.
Mecki,

Testato in Swift 5.1b5, dato che gli elementi sono Hashable e il desiderio di mantenere l'ordinamento, il NSOrderedSet (array: array) .array è leggermente più veloce del puro swift func uniqued () usando un set con filtro. Ho provato con 5100 stringhe che hanno prodotto 13 valori univoci.
dlemex,

62

Se si inseriscono entrambe le estensioni nel codice, Hashablequando possibile verrà utilizzata la versione più veloce e la Equatableversione verrà utilizzata come fallback.

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

Se l'ordine non è importante, puoi sempre utilizzare questo inizializzatore Set .


ok, capito. non sono riuscito a chiamarlo perché il mio array è un array di strutture ... come lo gestirò nel mio caso? struct di 20 variabili diverse, stringa e [stringa]
David Seek,

@David Seek Sembra che tu non abbia reso il tuo rigoroso hasable o equatable. È corretto?
Jessy,

1
@DavidCerca in questo modo, uniqueArray = nonUniqueArray.uniqueElements
Mert Celik

sì, non ti preoccupare. ha funzionato subito dopo. Sono passati quasi 2 anni: P
David Cerca il

Ciò avrà O(n²)prestazioni temporali, il che è davvero negativo per array di grandi dimensioni.
Duncan C,

44

modifica / aggiorna Swift 4 o successivo

Possiamo anche estendere il RangeReplaceableCollectionprotocollo per consentirne l'utilizzo anche con StringProtocoltipi:

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"

Metodo di mutazione:

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

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

Per Swift 3 fai clic qui


1
Mi piace, funziona anche con una serie di dizionari!
DeyaEldeen,

6
O (N ^ 2) è male :(
Alexander - Reinstalla Monica il

1
@Alexander Leo Dabus ha sostituito l' reduceimplementazione, quindi ora la complessità è diversa.
Cœur il

1
I risultati sono interessanti Sia per 1 milione di articoli unici che per 8 milioni, la versione del filtro è più veloce. Tuttavia, la versione basata su filtro impiega 8,38 volte più a lungo per 8 milioni di elementi unici (un capello nel O(n)tempo), dove la versione basata su flatmap richiede 7,47 volte più a lungo per 8 milioni di voci univoche rispetto a 1 milione, suggerendo che la versione basata su flatmap si ridimensiona meglio . In qualche modo la versione basata su flatmap fa leggermente meglio del O(n)tempo!
Duncan C,

1
In effetti, quando eseguo il test con 64 volte più elementi nell'array, la versione basata su flatmap è più veloce.
Duncan C,

43

Swift 4

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

ogni tentativo di insertsarà anche restituire una tupla: (inserted: Bool, memberAfterInsert: Set.Element). Vedere la documentazione .

L'uso del valore restituito ci aiuta a evitare il loop o qualsiasi altra operazione.


7
Dopo una semplice profilazione, questo metodo è molto veloce. È centinaia di volte più veloce rispetto a ridurre (_: _ :), o addirittura ridurre (in: _ :)
Kelvin

3
@Kelvin Perché erano tutti quegli altri algoritmi O(n^2)e nessuno se ne accorse.
Alexander - Ripristina Monica il

@Kelvin questa risposta è identica alla risposta di Eneko Alonso + al mio commento (16 giugno 17).
Cœur il

27

Swift 4

Garantito per continuare a ordinare.

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

Lo uso ora, ho cambiato solo il nome del metodo per rimuovere duplicati :)
J. Doe,

Credo che questa soluzione è compatta, ma credo che la soluzione deanWombourne registrato un anno prima potrebbe essere leggermente più efficiente di una reduce: nel complesso, è solo una linea più in tutto il progetto di scrivere la funzione come: var unique: [Iterator.Element] = []; for element in self where !unique.contains(element) { unique.append(element) }; return unique. Ammetto di non aver ancora testato le prestazioni relative.
Cœur

3
Ciò avrà O(n²)prestazioni temporali, il che è davvero negativo per array di grandi dimensioni.
Duncan C,

@NickGaens No, non lo è O(n²). Non c'è niente di rapido in questo.
Alexander - Ripristina Monica il

@Cœur reduceo reduce(into:)non farebbe la differenza. Riscriverlo per non chiamare più volte containsfarebbe una differenza MOLTO più grande.
Alexander - Ripristina Monica il

16

Ecco una categoria in SequenceTypecui viene preservato l'ordine originale dell'array, ma che utilizza a Setper eseguire le containsricerche per evitare il O(n)costo del contains(_:)metodo dell'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 }
    }
}

Se non sei Hashable o Equatable, puoi passare un predicato per fare il controllo di uguaglianza:

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
    }
}

Ora, se non hai Hashable, ma sei Equatable, puoi usare questo metodo:

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

Infine, puoi aggiungere una versione del percorso chiave di uniqued in questo modo:

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] }
    }
}

Puoi inserirli entrambi nella tua app, Swift sceglierà quello giusto in base al Iterator.Elementtipo di sequenza .


Heyyy finalmente qualcuno con una O(n)soluzione. Puoi anche combinare le operazioni "check" e "insert" in una, a proposito. Vedi stackoverflow.com/a/46354989/3141234
Alexander - Ripristina Monica il

Oh, è intelligente :)
DeanWombourne, il

15

Ispirato da https://www.swiftbysundell.com/posts/the-power-of-key-paths-in-swift , possiamo dichiarare uno strumento più potente che è in grado di filtrare per unicità su qualsiasi keyPath. Grazie ai commenti di Alexander su varie risposte riguardanti la complessità, le soluzioni seguenti dovrebbero essere quasi ottimali.

Soluzione non mutante

Estendiamo con una funzione che è in grado di filtrare per unicità su qualsiasi 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 }
    }
}

Nota: nel caso in cui il tuo oggetto non sia conforme a RangeReplaceableCollection, ma sia conforme a Sequence, puoi avere questa estensione aggiuntiva, ma il tipo restituito sarà sempre un 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 }
    }
}

uso

Se vogliamo l'unicità per gli elementi stessi, come nella domanda, usiamo 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] */

Se vogliamo unicità per qualcos'altro (come per la idraccolta di oggetti), utilizziamo il keyPath di nostra scelta:

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}] */

Soluzione mutante

Estendiamo con una funzione mutante in grado di filtrare l'unicità su qualsiasi 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 }
    }
}

uso

Se vogliamo l'unicità per gli elementi stessi, come nella domanda, usiamo 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] */

Se vogliamo unicità per qualcos'altro (come per la idraccolta di oggetti), utilizziamo il keyPath di nostra scelta:

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
Questa è una buona implementazione! Solo con quel percorso chiave sono convertibile in chiusure, in modo da poter usare un arg di chiusura per supportare sia il codice arbitrario (in chiusure) sia le semplici ricerche di proprietà (tramite percorsi chiave). L'unica modifica che vorrei apportare è l' keyPathimpostazione predefinita \.self, poiché è probabilmente la maggior parte dei casi d'uso.
Alexander - Ripristina Monica il

1
@Alexander Ho provato a impostare automaticamente il Sé, ma avrei bisogno di fare Elementsempre Hashable. In alternativa ad un valore di default è l'aggiunta di un sovraccarico semplice senza parametri:extension Sequence where Element: Hashable { func unique() { ... } }
Cuore


1
Fantastico ... semplice e soprattutto "flessibile". Grazie.
BonanzaDriver

12

Una soluzione alternativa (se non ottimale) da qui utilizzando tipi immutabili anziché variabili:

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
}

Incluso per contrastare l'approccio imperativo di Jean-Pillippe con un approccio funzionale.

Come bonus questa funzione funziona con stringhe e array!

Modifica: questa risposta è stata scritta nel 2014 per Swift 1.0 (prima Setera disponibile in Swift). Non richiede conformità Hashable e corre in tempo quadratico.


8
Attenzione, non ce n'è uno, ma due modi in cui questo viene eseguito in tempo quadratico: entrambi containse l'array append vengono eseguiti in O (n). Anche se ha il vantaggio di richiedere solo equatable, non hash.
Velocità

questo è un modo davvero complicato di scrivere filter. È O (n ^ 2) (che è richiesto se non vuoi richiedere la Hashableconformità), ma dovresti almeno chiamarlo esplicitamente
Alexander - Reinstate Monica

10

veloce 2

con risposta della funzione 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
    })
}

uso:

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

Il Boolvalore ovviamente è ridondante, poiché il codice non lo legge mai. Usa a Setinvece di a Dictionarye ottieni il mio voto.
Nikolai Ruhe,

10

In Swift 5

 var array: [String] =  ["Aman", "Sumit", "Aman", "Sumit", "Mohan", "Mohan", "Amit"]

 let uniq = Array(Set(array))
 print(uniq)

L'output sarà

 ["Sumit", "Mohan", "Amit", "Aman"]

2
Questa è una ripetizione di molte delle risposte già qui e non preserva l'ordinamento.
Alexander - Ripristina Monica il

9

Un'altra soluzione Swift 3.0 per rimuovere i duplicati da un array. Questa soluzione migliora su molte altre soluzioni già proposte da:

  • Preservare l'ordine degli elementi nell'array di input
  • Complessità lineare O (n): filtro a passaggio singolo O (n) + imposta inserzione O (1)

Dato l'array intero:

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]

Codice funzionale:

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]

Codice estensione array:

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]

Questo codice sfrutta il risultato restituito insertdall'operazione on Set, che viene eseguita on O(1), e restituisce una tupla che indica se l'elemento è stato inserito o se esiste già nel set.

Se l'articolo era nel set, filterlo escluderà dal risultato finale.


1
Non essere pignoli ma eseguirai l'inserimento e il test di appartenenza tutte le volte che ci sono elementi, quindi dovresti contare anche il loro costo come O (n). Questo non significa 3xO (n), tuttavia, perché queste O e non hanno lo stesso costo con il filtro, quindi l'aggiunta di O (n) è mele alle arance. Se consideriamo le operazioni impostate come una parte O (1) del costo del filtro, la complessità è semplicemente O (n), sebbene con una "O" più grande. Spingendolo al limite, potresti anche evitare gli inserimenti quando l'elemento è già nel set.
Alain T.

Hai ragione, usare deferil codice farebbe l'operazione di test impostata due volte, una con containse una con insert. Leggendo ulteriormente la documentazione di Swift, ho scoperto che insertrestituisce una tupla che indica se l'elemento è stato inserito o meno, quindi ho semplificato il codice rimuovendo il containssegno di spunta.
Eneko Alonso,

2
Bello. L'estensione potrebbe essere ottimale eseguendola suextension Sequence where Iterator.Element: Hashable { ... }
Cœur

@AlainT. No. Entrambi inserte containshanno O(1)complessità. O(1) + O(1) = O(1). Queste due operazioni vengono quindi eseguite nvolte (una volta per chiamata della chiusura passata filter, che viene chiamata una volta per elemento) Vale a dire se un'operazione richiede un tempo costante indipendentemente dalla dimensione dell'input, quindi eseguirla due volte fa comunque impiegare un tempo costante cioè indipendentemente dalle dimensioni dell'input. La complessità totale di questo è O(n).
Alexander - Ripristina Monica il

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] }
  }
}

utilizzo:

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

o

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

Questo è O(n^2). Non farlo
Alexander - Ripristina Monica il

8

Swift 5

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

Ho fatto alcune variazioni in modo da poter selezionare una chiave da confrontare. 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 } } }
Marcelo de Aguiar,

Non è necessario utilizzare a Bool, quando l'unico valore utilizzato è true. Stai raggiungendo un "tipo di unità" (un tipo con un solo valore possibile). Il tipo di unità di Swift è Void, il cui unico valore è ()(ovvero la tupla vuota). Quindi puoi semplicemente usare [T: Void]. Anche se non dovresti farlo, perché in pratica hai appena inventato Set. Usa Setinvece. Vedi stackoverflow.com/a/55684308/3141234 Elimina questa risposta.
Alexander - Ripristina Monica il

8

Pensa come un programmatore funzionale :)

Per filtrare l'elenco in base alla presenza dell'elemento, è necessario l'indice. È possibile utilizzare enumeratedper ottenere l'indice e mapper tornare all'elenco dei valori.

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

Questo garantisce l'ordine. Se non ti dispiace per l'ordine, la risposta esistente di Array(Set(myArray))è più semplice e probabilmente più efficiente.


AGGIORNAMENTO: Alcune note su efficienza e correttezza

Alcune persone hanno commentato l'efficienza. Sono sicuramente nella scuola di scrivere prima il codice corretto e semplice e poi di capire i colli di bottiglia in seguito, anche se apprezzo che sia discutibile se questo sia più chiaro di Array(Set(array)).

Questo metodo è molto più lento di Array(Set(array)). Come notato nei commenti, mantiene l'ordine e funziona su elementi che non sono Hashable.

Tuttavia, il metodo di @Alain T preserva anche l'ordine ed è anche molto più veloce. Quindi, a meno che il tuo tipo di elemento non sia hash o non ti serva solo una fodera veloce, ti suggerirei di andare con la loro soluzione.

Ecco alcuni test su un MacBook Pro (2014) su Xcode 11.3.1 (Swift 5.1) in modalità di rilascio.

La funzione profiler e due metodi per confrontare:

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}
}

E una piccola varietà di input di test:

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

Fornisce come output:

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
diversamente Array(Set(myArray)), questo funziona per cose che non lo sonoHashable
Porter Child,

1
... e diversamente Array(Set(myArray))dall'ordine dell'array viene mantenuto.
Sander Saelmans,

Mi sembra la risposta migliore, almeno al momento in cui Swift 5 è già la versione corrente.
oradyvan,

Questa è una soluzione molto elegante; sfortunatamente, è anche piuttosto lento.
Colin Stark,

1
@ TimMB Oh ho letto male il tuo post. Ho visto l'adattamento di qualcuno che ha usato il lastIndex(of:). In questo caso, non sono assolutamente d'accordo sul punto di chiarezza rispetto all'ottimizzazione. Non credo che questa implementazione sia particolarmente chiara, soprattutto rispetto a una semplice soluzione basata su set. In ogni caso, tale codice dovrebbe essere estratto in una funzione di estensione. Questo algoritmo diventa sostanzialmente inutilizzabile anche con una dimensione di input bassa, come da migliaia a decine di migliaia. Non è difficile trovare tali set di dati, le persone possono avere migliaia di brani, file, contatti, ecc.
Alexander - Reinstalla Monica il

6

Per le matrici in cui gli elementi non sono né Hashable né Comparable (ad esempio oggetti complessi, dizionari o strutture), questa estensione fornisce un modo generalizzato per rimuovere i duplicati:

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))" }

Non devi preoccuparti di creare valori Hashable e ti permette di usare diverse combinazioni di campi per unicità.

Nota: per un approccio più solido, consultare la soluzione proposta da Coeur nei commenti seguenti.

stackoverflow.com/a/55684308/1033581

[EDIT] Alternativa di Swift 4

Con Swift 4.2 puoi usare la classe Hasher per creare un hash molto più semplice. L'estensione sopra potrebbe essere modificata per sfruttare questo:

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}     
    }
}

La sintassi chiamante è leggermente diversa perché la chiusura riceve un parametro aggiuntivo contenente una funzione per eseguire l'hashing di un numero variabile di valori (che deve essere Hashable singolarmente)

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

Funzionerà anche con un singolo valore di unicità (usando $ 1 e ignorando $ 0).

peopleArray = peopleArray.filterDuplicate{ $1.name } 

Questo potrebbe dare risultati casuali a seconda del comportamento di "\()", in quanto potrebbe non darti valori unici come conformarsi a Hashabledovrebbe. Esempio, se i tuoi elementi sono conformi Printablerestituendo tutti lo stesso description, allora il tuo filtro fallisce.
Cor

Concordato. La selezione dei campi (o della formula) che produrrà il modello di unicità desiderato dovrà tenerne conto. Per molti casi d'uso, ciò fornisce una semplice soluzione ad hoc che non richiede alcuna modifica della classe o della struttura dell'elemento.
Alain T.

2
@AlainT. Non farlo, davvero. Lo scopo di String non è quello di essere un meccanismo di generazione di chiavi ad hoc del ghetto. Basta vincolare Tall'essere Hashable.
Alexander - Ripristina Monica il

@Alexander Ho applicato questa idea in una nuova risposta: stackoverflow.com/a/55684308/1033581
Cœur

Risposta perfetta come voglio. Grazie mille.
Hardik Thakkar

4

È possibile utilizzare direttamente una raccolta di set per rimuovere i duplicati, quindi eseguirne il cast su un array

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]

Quindi puoi ordinare l'array come desideri

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

"Quindi puoi ordinare il tuo array come desideri" E se volessi lo stesso ordinamento del array originale? Non è così facile.
Alexander - Ripristina Monica il

3

Versione della sintassi leggermente più sintetica della risposta Swift 2 di Daniel Krom , usando una chiusura finale e un nome di argomento abbreviato, che sembra essere basato sulla risposta originale di 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 }
}

Esempio di implementazione di un tipo personalizzato che può essere utilizzato con uniq(_:)(che deve essere conforme Hashable, e quindi Equatable, poiché si Hashableestende 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
  }

}

Nel codice sopra ...

id, come utilizzato nel sovraccarico di ==, potrebbe essere qualsiasi Equatabletipo (o metodo che restituisce un Equatabletipo, ad es someMethodThatReturnsAnEquatableType().). Il codice commentato dimostra l'estensione del controllo per l'uguaglianza, dove someOtherEquatablePropertyè un'altra proprietà di un Equatabletipo (ma potrebbe anche essere un metodo che restituisce un Equatabletipo).

id, come usato nella hashValueproprietà calcolata (richiesta per conformarsi a Hashable), potrebbe essere qualsiasi Hashable(e quindi Equatable) proprietà (o metodo che restituisce un Hashabletipo).

Esempio di utilizzo 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

Non è necessario utilizzare a Bool, quando l'unico valore utilizzato è true. Stai raggiungendo un "tipo di unità" (un tipo con un solo valore possibile). Il tipo di unità di Swift è Void, il cui unico valore è ()(ovvero la tupla vuota). Quindi puoi semplicemente usare [T: Void]. Anche se non dovresti farlo, perché in pratica hai appena inventato Set. Usa Setinvece. Vedi stackoverflow.com/a/55684308/3141234
Alexander - Ripristina Monica il

3

Nel caso abbiate bisogno di valori ordinati, questo funziona (Swift 4)

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


2
In questo caso perdi gli elementi.
Shmidt,

Niente affatto, ecco a cosa .sorted()serve la fine. Saluti.
Mauricio Chirino,

@MauricioChirino E se il tuo array originale fosse [2, 1, 1]? Sarebbe uscito [1, 2], non è stato ordinato: p
Alexander - Reinstate Monica il

2
@MauricioChirino No, non lo sono. Se l'obiettivo è rimuovere valori duplicati da una sequenza, pur mantenendo l'ordine in cui gli elementi sono apparsi in modo univoco, questo non lo fa. Il contro esempio molto chiaro è [2, 1, 1]. La prima apparizione di elementi unici, in ordine è [2, 1]. Questa è la risposta corretta Ma usando il tuo algoritmo (errato), ottieni [1, 2], che è ordinato, ma non nell'ordine corretto, originale.
Alexander - Ripristina Monica il

2
Non riesce se gli elementi in arraynon lo sono Hashable; solo Hashableun tipo di dati può essere aggiunto a un set, ma qualsiasi tipo di dati può essere aggiunto a un array.
Mecki,

3

Ecco una soluzione che

  • Non utilizza NStipi legacy
  • È ragionevolmente veloce con O(n)
  • È conciso
  • Conserva l'ordine degli elementi
extension Array where Element: Hashable {

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

2

qui ho fatto una soluzione O (n) per gli oggetti. Soluzione non di poche righe, ma ...

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
Invece di usare a Setcon un'abitudine DistinctWrapper, dovresti usare a Dictionaryda distinti attributi per gli oggetti. Quando segui questa logica, finiresti per implementare [ Dictionary.init(_:uniquingKeysWith:)] pastebin.com/w90pVe0p(https://developer.apple.com/documentation/… , che ora è integrato nella libreria standard. Scopri quanto è semplice pastebin.com/w90pVe0p
Alexander - Ripristina Monica il

2

Ho usato la risposta di Jean-Philippe Pellet e ho realizzato un'estensione di array che esegue operazioni simili a array su array, mantenendo l'ordine degli elementi.

/// 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) })
  }
}

Non è necessario utilizzare a Bool, quando l'unico valore utilizzato è true. Stai raggiungendo un "tipo di unità" (un tipo con un solo valore possibile). Il tipo di unità di Swift è Void, il cui unico valore è ()(ovvero la tupla vuota). Quindi puoi semplicemente usare [T: Void]. Anche se non dovresti farlo, perché in pratica hai appena inventato Set. Usa Setinvece. Vedi stackoverflow.com/a/55684308/3141234
Alexander - Ripristina Monica il

2

Questa è solo un'implementazione molto semplice e conveniente. Una proprietà calcolata in un'estensione di un array con elementi equabili.

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
    }
}

1
Questo è anche O(n^2).
Alexander - Ripristina Monica il

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

Uso:

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

Penso che questo sia il più semplice
Jack Rus

mantiene l'ordine e ti dà un array che desideri
Jack Rus

Questo è anche O(n²).
Alexander - Ripristina Monica il

2
  1. Per prima cosa aggiungi tutti gli elementi di un array a NSOrderedSet.
  2. Ciò rimuoverà tutti i duplicati nell'array.
  3. Converti di nuovo questo set ordinato in un array.

Fatto....

Esempio

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

output di arrayWithoutDuplicates - [1,2,4,6,8]


2

Una versione leggermente abbreviata basata sulla risposta dell'estensione dell'array @ 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)
        }
    }
}

Ciò esegue due operazioni di hashing per elemento, il che non è necessario. insertrestituisce una tupla che indica se l'elemento era già presente o è stato aggiunto per la prima volta. stackoverflow.com/a/55684308/3141234 Elimina questa risposta.
Alexander - Ripristina Monica il

1

Puoi sempre utilizzare un dizionario, poiché un dizionario può contenere solo valori univoci. Per esempio:

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

Come puoi vedere, l'array risultante non sarà sempre in 'ordine'. Se si desidera ordinare / ordinare l'array, aggiungere questo:

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

Il modo più semplice sarebbe utilizzare NSOrderedSet, che memorizza elementi unici e conserva l'ordine degli elementi. Piace:

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)

Mi chiedo come questa performance sia paragonabile alle migliori risposte qui. Hai confrontato?
Alexander - Ripristina Monica il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.