Suppression des éléments en double d'un tableau dans Swift

252

De nos jours, dans Swift, vous tapez simplement Set( yourArray )pour rendre un tableau unique. (Ou un ensemble commandé si nécessaire.)

Avant que cela ne soit possible, comment cela se faisait-il?


Je pourrais avoir un tableau qui ressemble à ceci:

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

Ou, vraiment, n'importe quelle séquence de parties de données de type similaire. Ce que je veux faire, c'est m'assurer qu'il n'y a qu'un seul de chaque élément identique. Par exemple, le tableau ci-dessus deviendrait:

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

Notez que les doublons de 2, 6 et 15 ont été supprimés pour garantir qu'il n'y avait qu'un seul de chaque élément identique. Swift offre-t-il un moyen de le faire facilement ou devrai-je le faire moi-même?

Altair357
la source
11
Le moyen le plus simple consiste à convertir le tableau dans un NSSet, NSSet est une collection d'objets non ordonnée, si nécessaire pour maintenir l'ordre NSOrderedSet.
Andrea
Vous pouvez utiliser la fonction d'intersection comme vous pouvez le trouver dans cette classe avec des fonctions pour les tableaux: github.com/pNre/ExSwift/blob/master/ExSwift/Array.swift
Edwin Vermeer
Ne fait pas partie de Swift mais j'utilise Dollar. $.uniq(array) github.com/ankurp/Dollar#uniq---uniq
Andy
La réponse la plus élégante, la plus intelligente et la plus rapide est probablement fournie par la réponse de mxcl ci-dessous. Ce qui aide également à maintenir l'ordre
Honey
1
Pourquoi n'utilisez-vous pas simplement Setde Swift? Vous pourrez fournir une liste d'éléments non ordonnés et uniques.
TibiaZ

Réponses:

133

Vous pouvez rouler le vôtre, par exemple comme ceci ( mis à jour pour Swift 1.2 avec 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]

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

Et comme extension pour 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
    }
}
Jean-Philippe Pellet
la source
12
Vous pouvez également implémenter le corps de cette fonction en tant quevar addedDict = [T:Bool](); return filter(source) { addedDict(true, forKey: $0) == nil }
Airspeed Velocity
1
@AirspeedVelocity: Vouliez-vous dire à la updateValue(true, forKey: $0)...place deaddedDict(true, forKey: $0)...
Jawwad
1
Oups oui désolé j'ai accidentellement la méthode! Ça devrait être return filter(source) { addedDict.updateValue(true, forKey: $0) == nil }comme tu dis.
Airspeed Velocity
21
Juste un mot d'avertissement: évitez de discuter des performances pour des fonctions simples comme celle-ci jusqu'à ce que vous dépendiez de leurs performances, auquel cas la seule chose que vous devriez faire est de comparer. Trop souvent, j'ai vu du code impossible à maintenir ou encore du code moins performant en raison d'hypothèses. :) De plus, c'est probablement plus facile à saisir:let uniques = Array(Set(vals))
Blixt
11
@Blixt D'accord. Ici encore, l'avantage réside dans le respect de l'ordre des éléments du tableau d'origine.
Jean-Philippe Pellet
493

Vous pouvez facilement convertir un ensemble et revenir à un tableau:

let unique = Array(Set(originals))

Il n'est pas garanti de conserver l'ordre d'origine de la baie.

Ben Packard
la source
37
Existe-t-il un moyen d'utiliser un ensemble tout en préservant l'ordre d'origine du tableau?
Crashalot
6
@Crashalot Voir ma réponse.
Jean-Philippe Pellet
5
Si vous devez conserver les objets uniques par une propriété spécifique, implémentez également le protocole Hashable and Equatable sur cette classe, au lieu d'utiliser simplement la transformation Array-> Set-> Array
Fawkes
2
Agréable!! Quelle est la complexité temporelle de cette solution s'il vous plaît?
JW.ZG
2
Échoue si les éléments originalsne le sont pas Hashable; seuls Hashableles types de données peuvent être ajoutés à un ensemble, mais tout type de données peut être ajouté à un tableau.
Mecki
69

Beaucoup de réponses disponibles ici, mais j'ai raté cette extension simple, adaptée à Swift 2 et plus:

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

Le rend super simple. Peut être appelé comme ceci:

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

Filtrage basé sur les propriétés

Pour filtrer un tableau en fonction des propriétés, vous pouvez utiliser cette méthode:

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

Que vous pouvez appeler comme suit:

let filteredElements = myElements.filterDuplicates { $0.PropertyOne == $1.PropertyOne && $0.PropertyTwo == $1.PropertyTwo }
Antoine
la source
@Antoine Merci pour l'extension Filtrage basé sur les propriétés. C'est vraiment utile. Mais pouvez-vous expliquer comment cela fonctionne? C'est trop difficile à comprendre pour moi. Merci
Mostafa Mohamed Raafat
Mises à jour pour swift 3: func filterDuplicates (_ includeElement: (_ lhs: Element, _ rhs: Element) -> Bool) -> [Element] {
cbartel
La première partie de cette réponse ( extension Array where Element: Equatable) est remplacée par stackoverflow.com/a/36048862/1033581 qui offre une solution plus puissante ( extension Sequence where Iterator.Element: Equatable).
Cœur
7
Cela aura des O(n²)performances temporelles, ce qui est vraiment mauvais pour les grands tableaux.
Duncan C
Vous devriez utiliser un ensemble pour garder une trace des éléments vus jusqu'à présent, pour ramener cette terrible O(n²)complexité àO(n)
Alexander - Reinstate Monica
63

Swift 3.0

let uniqueUnordered = Array(Set(array))
let uniqueOrdered = Array(NSOrderedSet(array: array))
Jovan Stankovic
la source
1
laissez uniqueOrderedNames = Array (NSOrderedSet (array: userNames)) as! [String] si vous avez un tableau de String, pas de Any
Zaporozhchenko Oleksandr
Échoue si les éléments arrayne le sont pas Hashable; seuls Hashableles types de données peuvent être ajoutés à un ensemble, mais tout type de données peut être ajouté à un tableau.
Mecki
Testé dans Swift 5.1b5, étant donné que les éléments sont hashable et un désir de conserver l'ordre, le tableau NSOrderedSet (array: array) est légèrement plus rapide que le pur swift func uniqued () utilisant un ensemble avec filtre. J'ai testé avec 5100 cordes qui ont abouti à 13 valeurs uniques.
dlemex
62

Si vous mettez les deux extensions dans votre code, la Hashableversion la plus rapide sera utilisée lorsque cela sera possible et la Equatableversion sera utilisée comme solution de secours.

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

Si l'ordre n'est pas important, vous pouvez toujours simplement utiliser cet initialiseur Set .

Jessy
la source
OK, j'ai compris. je n'ai pas pu l'appeler car mon tableau est un tableau de structures ... comment pourrais-je le gérer dans mon cas? struct de 20 variables différentes, chaîne et [chaîne]
David Seek
@David Seek Il semble que vous n'ayez pas rendu votre stricte table de lavage ou d'équitation. Est-ce exact?
Jessy
1
@DavidSeek comme ceci, uniqueArray = nonUniqueArray.uniqueElements
Mert Celik
ouais ne t'inquiète pas. l'a fait fonctionner juste après.
David Seek
Cela aura des O(n²)performances temporelles, ce qui est vraiment mauvais pour les grands tableaux.
Duncan C
44

modifier / mettre à jour Swift 4 ou version ultérieure

Nous pouvons également étendre le RangeReplaceableCollectionprotocole pour lui permettre d'être également utilisé avec des StringProtocoltypes:

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"

Méthode de mutation:

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

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

Pour Swift 3, cliquez ici

Leo Dabus
la source
1
J'aime ça, ça marche aussi avec une panoplie de dictionnaires!
DeyaEldeen
6
O (N ^ 2) est mauvais :(
Alexander - Reinstate Monica
1
@Alexander Leo Dabus a remplacé l' reduceimplémentation, donc maintenant la complexité est différente.
Cœur
1
Les résultats sont intéressants. Pour 1 million d'articles uniques et 8 millions, la version de filtre est plus rapide. Cependant, la version basée sur un filtre prend 8,38 fois plus longtemps pour 8 millions d'éléments uniques (un cheveu au fil du O(n)temps), alors que la version basée sur une carte plate prend 7,47 fois plus pour 8 millions d'entrées uniques que 1 million, ce qui suggère que la version basée sur une carte plate évolue mieux . D'une manière ou d'une autre, la version basée sur une carte plate fait légèrement mieux que le O(n)temps!
Duncan C du
1
En fait, lorsque j'exécute le test avec 64 fois plus d'éléments dans le tableau, la version basée sur le flatmap est plus rapide.
Duncan C
43

Swift 4

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

toute tentative de insertse retourner aussi un tuple: (inserted: Bool, memberAfterInsert: Set.Element). Voir documentation .

L'utilisation de la valeur renvoyée nous permet d'éviter de boucler ou d'effectuer toute autre opération.

mxcl
la source
7
Après un simple profilage, cette méthode est vraiment rapide. Ses centaines de fois plus rapide que d'utiliser réduire (_: _ :), ou même réduire (en: _ :)
Kelvin
3
@Kelvin Parce que tous ces autres algorithmes l'ont été O(n^2), et personne ne l'a remarqué.
Alexander - Reinstate Monica
@Kelvin cette réponse est identique à la réponse d' Eneko Alonso + mon commentaire (16 juin 17).
Cœur
27

Swift 4

Garanti pour continuer à commander.

extension Array where Element: Equatable {
    func removingDuplicates() -> Array {
        return reduce(into: []) { result, element in
            if !result.contains(element) {
                result.append(element)
            }
        }
    }
}
Alessandro Martin
la source
J'utilise cela maintenant, j'ai seulement changé le nom de la méthode pour supprimer les doublons :)
J. Doe
Je suppose que cette solution est compacte, mais je crois que la solution deanWombourne a affiché un an plus tôt peut - être un peu plus efficace qu'un reduce: Dans l' ensemble, il est juste une ligne dans votre projet entier pour écrire votre fonction: var unique: [Iterator.Element] = []; for element in self where !unique.contains(element) { unique.append(element) }; return unique. J'avoue que je n'ai pas encore testé les performances relatives.
Cœur
3
Cela aura des O(n²)performances temporelles, ce qui est vraiment mauvais pour les grands tableaux.
Duncan C
@NickGaens Non, ce n'est pas le cas O(n²). Il n'y a rien de rapide à ce sujet.
Alexander - Reinstate Monica
@ Cœur reduceou reduce(into:)ne ferait pas une différence critique. Réécrire ceci pour ne pas appeler à plusieurs reprises containsferait une BEAUCOUP plus grande différence.
Alexander - Reinstate Monica
16

Voici une catégorie sur SequenceTypelaquelle préserve l'ordre d'origine du tableau, mais utilise a Setpour effectuer les containsrecherches afin d'éviter le O(n)coût de la contains(_:)méthode 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 }
    }
}

Si vous n'êtes pas hashable ou equatable, vous pouvez passer un prédicat pour effectuer la vérification d'égalité:

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

Maintenant, si vous n'avez pas de hashable, mais que vous êtes équitable, vous pouvez utiliser cette méthode:

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

Enfin, vous pouvez ajouter une version de chemin d'accès clé unique comme ceci:

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

Vous pouvez coller les deux dans votre application, Swift choisira la bonne en fonction du Iterator.Elementtype de votre séquence .

deanWombourne
la source
Heyyy enfin quelqu'un avec une O(n)solution. Soit dit en passant, vous pouvez combiner les opérations de réglage "vérifier" et "insérer" en une seule. Voir stackoverflow.com/a/46354989/3141234
Alexander - Reinstate Monica
Oh, c'est intelligent :)
deanWombourne
15

Inspiré par https://www.swiftbysundell.com/posts/the-power-of-key-paths-in-swift , nous pouvons déclarer un outil plus puissant capable de filtrer pour l'unicité sur n'importe quel chemin de clé. Grâce aux commentaires d'Alexander sur diverses réponses concernant la complexité, les solutions ci-dessous devraient être presque optimales.

Solution non mutante

Nous étendons avec une fonction qui est capable de filtrer l'unicité sur n'importe quel 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 }
    }
}

Remarque: dans le cas où votre objet n'est pas conforme à RangeReplaceableCollection, mais est conforme à Sequence, vous pouvez avoir cette extension supplémentaire, mais le type de retour sera toujours un tableau:

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

Usage

Si nous voulons l'unicité pour les éléments eux-mêmes, comme dans la question, nous utilisons le 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] */

Si nous voulons l'unicité pour autre chose (comme pour idune collection d'objets) alors nous utilisons le chemin de clé de notre choix:

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

Solution de mutation

Nous étendons avec une fonction de mutation qui est capable de filtrer pour l'unicité sur n'importe quel 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 }
    }
}

Usage

Si nous voulons l'unicité pour les éléments eux-mêmes, comme dans la question, nous utilisons le 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] */

Si nous voulons l'unicité pour autre chose (comme pour idune collection d'objets) alors nous utilisons le chemin de clé de notre choix:

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}] */
Cœur
la source
1
Voilà une bonne implémentation! Avec ces chemins de clé, je pouvais uniquement être converti en fermetures, de sorte que vous pouvez utiliser un argument de fermeture pour prendre en charge à la fois du code arbitraire (dans les fermetures) et de simples recherches de propriété (via des chemins de clé). Le seul changement que je ferais serait de le faire keyPathpar défaut \.self, car c'est probablement la majorité des cas d'utilisation.
Alexander - Reinstate Monica
1
@Alexander J'ai essayé de définir par défaut Self, mais je devrais Elementtoujours le faire Hashable. Une alternative à une valeur par défaut consiste à ajouter une simple surcharge sans paramètres:extension Sequence where Element: Hashable { func unique() { ... } }
Cœur
Ah oui, c'est logique!
Alexander - Reinstate Monica
1
Brillant ... simple, et surtout "flexible". THX.
BonanzaDriver
12

Une solution alternative (sinon optimale) d' ici utilisant des types immuables plutôt que des variables:

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
}

Inclus pour opposer l'approche impérative de Jean-Pillippe à une approche fonctionnelle.

En prime, cette fonction fonctionne aussi bien avec des chaînes qu'avec des tableaux!

Edit: Cette réponse a été écrite en 2014 pour Swift 1.0 (auparavant Setétait disponible dans Swift). Il ne nécessite pas de conformité Hashable et fonctionne en temps quadratique.

Pliskin
la source
8
Méfiez-vous, il n'y a pas une, mais deux façons dont cela fonctionne en temps quadratique - les deux containset l'ajout de tableau s'exécutent en O (n). Bien qu'il présente l'avantage de ne nécessiter que des produits équitables et non lavables.
Airspeed Velocity
c'est une façon d'écrire vraiment compliquée filter. C'est O (n ^ 2) (qui est requis si vous ne voulez pas exiger la Hashableconformité), mais vous devriez au moins le dire explicitement
Alexander - Reinstate Monica
10

rapide 2

avec la fonction uniq réponse:

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

utilisation:

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
Daniel Krom
la source
La Boolvaleur est évidemment redondante, car votre code ne la lit jamais. Utilisez un Setau lieu d'un Dictionaryet vous obtenez mon vote positif.
Nikolai Ruhe
10

Dans Swift 5

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

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

La sortie sera

 ["Sumit", "Mohan", "Amit", "Aman"]
Sanjay Mishra
la source
2
Ceci est une répétition de la plupart des réponses déjà présentes et ne préserve pas l'ordre.
Alexander - Rétablir Monica le
9

Encore une solution Swift 3.0 pour supprimer les doublons d'un tableau. Cette solution améliore de nombreuses autres solutions déjà proposées par:

  • Préserver l'ordre des éléments dans le tableau d'entrée
  • Complexité linéaire O (n): filtre passe unique O (n) + insertion de l'ensemble O (1)

Étant donné le tableau entier:

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]

Code fonctionnel:

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]

Code d'extension de tableau:

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]

Ce code tire parti du résultat renvoyé par l' insertopération on Set, qui s'exécute le O(1), et renvoie un tuple indiquant si l'élément a été inséré ou s'il existait déjà dans l'ensemble.

Si l'élément était dans l'ensemble, filterl'exclura du résultat final.

Eneko Alonso
la source
1
Ne pas être difficile, mais vous effectuerez l'insertion et le test d'adhésion autant de fois qu'il y a d'éléments, vous devez donc également compter leur coût comme O (n). Cela ne signifie cependant pas 3xO (n) parce que ces O et n'ont pas le même coût que le filtre, donc l'ajout d'O (n) est des pommes aux oranges. Si nous considérons les opérations d'ensemble comme une partie O (1) du coût du filtre, la complexité est simplement O (n), bien qu'avec un "O" plus grand. En poussant cela à la limite, vous pouvez également éviter les insertions lorsque l'élément est déjà dans l'ensemble.
Alain T.
Vous avez raison, utiliser deferle code ferait l'opération de test définie deux fois, une avec containset une avec insert. En lisant la documentation Swift, j'ai trouvé que insertrenvoie un tuple indiquant si l'élément a été inséré ou non, j'ai donc simplifié le code en supprimant la containsvérification.
Eneko Alonso
2
Agréable. Votre extension pourrait être optimale en le faisantextension Sequence where Iterator.Element: Hashable { ... }
Cœur
@AlainT. Nan. Les deux insertet containsont la O(1)complexité. O(1) + O(1) = O(1). Ces deux opérations sont ensuite effectuées plusieurs nfois (une fois par appel de la fermeture passée à filter, qui est appelée une fois par élément). quelle que soit la taille d'entrée. La complexité totale de ceci est O(n).
Alexander - Reinstate Monica
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] }
  }
}

usage:

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

ou

["Ljubljana", "London", "Los Angeles", "Ljubljana"].uniqueOrdered()
Rok Gregorič
la source
C'est ça O(n^2). Ne fais pas ça.
Alexander - Reinstate Monica
8

Swift 5

extension Sequence where Element: Hashable {
    func unique() -> [Element] {
        NSOrderedSet(array: self as! [Any]).array as! [Element]
    }
}
blackjacx
la source
J'ai fait quelques variations pour pouvoir sélectionner une clé à comparer. 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
Il n'est pas nécessaire d'utiliser un Bool, lorsque la seule valeur que vous utilisez est true. Vous atteignez un "type d'unité" (un type avec une seule valeur possible). Le type d'unité de Swift est Void, dont la seule valeur est ()(alias le tuple vide). Vous pouvez donc simplement utiliser [T: Void]. Bien que vous ne deviez pas faire cela, parce que vous venez d'inventer Set. Utilisez Setplutôt. Voir stackoverflow.com/a/55684308/3141234 Veuillez supprimer cette réponse.
Alexander - Rétablir Monica
8

Pensez comme un programmeur fonctionnel :)

Pour filtrer la liste selon que l'élément s'est déjà produit, vous avez besoin de l'index. Vous pouvez utiliser enumeratedpour obtenir l'index et maprevenir à la liste des valeurs.

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

Cela garantit la commande. Si la commande ne vous dérange pas, la réponse actuelle de Array(Set(myArray))est plus simple et probablement plus efficace.


MISE À JOUR: Quelques notes sur l'efficacité et l'exactitude

Quelques personnes ont commenté l'efficacité. Je suis définitivement à l'école d'écrire du code correct et simple d'abord, puis de trouver des goulots d'étranglement plus tard, bien que j'apprécie qu'il soit discutable que ce soit plus clair que Array(Set(array)).

Cette méthode est beaucoup plus lente que Array(Set(array)). Comme indiqué dans les commentaires, il préserve l'ordre et fonctionne sur les éléments qui ne sont pas hashable.

Cependant, la méthode de @Alain T préserve également l'ordre et est également beaucoup plus rapide. Donc, à moins que votre type d'élément ne soit pas lavable, ou que vous ayez simplement besoin d'une doublure rapide, je vous suggère d'aller avec leur solution.

Voici quelques tests sur un MacBook Pro (2014) sur Xcode 11.3.1 (Swift 5.1) en mode Release.

La fonction profileur et deux méthodes pour comparer:

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

Et une petite variété d'entrées de 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)}

Donne en sortie:

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
Tim MB
la source
1
contrairement à Array(Set(myArray))cela, cela fonctionne pour les choses qui ne le sont pasHashable
Porter Child
1
... et contrairement à Array(Set(myArray))l'ordre de votre tableau est conservé.
Sander Saelmans
Cela me semble être la meilleure réponse, du moins pour le moment, alors que Swift 5 est déjà la version actuelle.
oradyvan
Il s'agit d'une solution très élégante; malheureusement, c'est aussi plutôt lent.
Colin Stark
1
@TimMB Oh j'ai mal lu votre message. J'ai vu l'adaptation de quelqu'un qui l'a utilisé lastIndex(of:). Je suis totalement en désaccord sur le point de clarté vs optimisation dans ce cas. Je ne pense pas que cette implémentation soit particulièrement claire, surtout par rapport à une solution simple basée sur un ensemble. Dans tous les cas, ce code doit être extrait vers une fonction d'extension. Cet algorithme devient fondamentalement inutilisable, même avec une taille d'entrée faible, comme des milliers à des dizaines de milliers. Il n'est pas difficile de trouver de tels ensembles de données, les gens peuvent avoir des milliers de chansons, fichiers, contacts, etc.
Alexander - Reinstate Monica
6

Pour les tableaux où les éléments ne sont ni hachables ni comparables (par exemple, des objets complexes, des dictionnaires ou des structures), cette extension fournit un moyen généralisé de supprimer les doublons:

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

Vous n'avez pas à vous soucier de rendre les valeurs Hashable et cela vous permet d'utiliser différentes combinaisons de champs pour l'unicité.

Remarque: pour une approche plus robuste, veuillez consulter la solution proposée par Coeur dans les commentaires ci-dessous.

stackoverflow.com/a/55684308/1033581

[MODIFIER] Alternative à Swift 4

Avec Swift 4.2, vous pouvez utiliser la classe Hasher pour créer un hachage beaucoup plus facilement. L'extension ci-dessus pourrait être modifiée pour en tirer parti:

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 syntaxe d'appel est un peu différente car la fermeture reçoit un paramètre supplémentaire contenant une fonction pour hacher un nombre variable de valeurs (qui doivent être hachables individuellement)

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

Il fonctionnera également avec une seule valeur d'unicité (en utilisant $ 1 et en ignorant $ 0).

peopleArray = peopleArray.filterDuplicate{ $1.name } 
Alain T.
la source
Cela peut donner des résultats aléatoires en fonction du comportement de "\()", car il ne peut pas vous donner des valeurs uniques comme se conformer à Hashabledevrait. Exemple, si vos éléments se conforment à Printabletous en retournant les mêmes description, alors votre filtrage échoue.
Cœur
D'accord. La sélection des champs (ou formule) qui produiront le motif d'unicité souhaité devra en tenir compte. Pour de nombreux cas d'utilisation, cela fournit une solution ad hoc simple qui ne nécessite aucune altération de la classe ou de la structure de l'élément.
Alain T.
2
@AlainT. Ne fais pas ça vraiment. Le but de String n'est pas d'être un mécanisme de génération de clés ad-ghetto. Contraint juste Tà l'être Hashable.
Alexander - Reinstate Monica
@Alexander J'ai appliqué cette idée dans une nouvelle réponse: stackoverflow.com/a/55684308/1033581
Cœur
Réponse parfaite comme je veux. Merci beaucoup.
Hardik Thakkar
4

Vous pouvez utiliser directement une collection de jeux pour supprimer les doublons, puis les reconstituer dans un tableau

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]

Ensuite, vous pouvez commander votre tableau comme vous le souhaitez

myArray.sort{$0 < $1} // [1, 2, 4, 6, 15, 24, 60]
Vincent Choubard
la source
"Ensuite, vous pouvez commander votre tableau comme vous le souhaitez" Et si je veux le même ordre que celui du tableau d'origine? Ce n'est pas si facile.
Alexander - Reinstate Monica
3

Version syntaxique un peu plus succincte de la réponse Swift 2 de Daniel Krom , utilisant une fermeture finale et un nom d'argument abrégé, qui semble être basé sur la réponse originale d'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 }
}

Exemple d'implémentation d'un type personnalisé qui peut être utilisé avec uniq(_:)(qui doit être conforme Hashable, et donc Equatable, parce Hashablequ'étend 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
  }

}

Dans le code ci-dessus ...

id, tel qu'il est utilisé dans la surcharge de ==, peut être n'importe quel Equatabletype (ou méthode qui renvoie un Equatabletype, par exemple, someMethodThatReturnsAnEquatableType()). Le code mis en commentaire montre l'extension de la vérification de l'égalité, où se someOtherEquatablePropertytrouve une autre propriété d'un Equatabletype (mais peut également être une méthode qui renvoie un Equatabletype).

id, tel qu'il est utilisé dans la hashValuepropriété calculée (requis pour se conformer à Hashable), peut être toute propriété ( Hashableet donc Equatable) (ou méthode qui renvoie un Hashabletype).

Exemple d'utilisation 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
Scott Gardner
la source
Il n'est pas nécessaire d'utiliser un Bool, lorsque la seule valeur que vous utilisez est true. Vous atteignez un "type d'unité" (un type avec une seule valeur possible). Le type d'unité de Swift est Void, dont la seule valeur est ()(alias le tuple vide). Vous pouvez donc simplement utiliser [T: Void]. Bien que vous ne deviez pas faire cela, parce que vous venez d'inventer Set. Utilisez Setplutôt. Voir stackoverflow.com/a/55684308/3141234
Alexander - Reinstate Monica
3

Si vous avez besoin de trier les valeurs, cela fonctionne (Swift 4)

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

Mauricio Chirino
la source
2
Vous perdez l'ordre des éléments dans ce cas.
Shmidt
Pas du tout, c'est à ça que .sorted()sert la fin. Cordialement.
Mauricio Chirino
@MauricioChirino Et si votre tableau d'origine l'était [2, 1, 1]? Il sortirait [1, 2], ce n'est pas ordonné: p
Alexander - Reinstate Monica
2
@MauricioChirino Non, je ne le suis pas. Si l'objectif est de supprimer les valeurs en double d'une séquence, tout en conservant l'ordre dans lequel les éléments sont apparus de manière unique, cela ne fait pas cela. Le contre-exemple très clair est [2, 1, 1]. La première apparition d'éléments uniques, dans l'ordre est [2, 1]. Voilà la bonne réponse. Mais en utilisant votre algorithme (incorrect), vous obtenez [1, 2], qui est trié, mais qui n'est pas dans le bon ordre d'origine.
Alexander - Rétablir Monica
2
Échoue si les éléments arrayne le sont pas Hashable; seuls Hashableles types de données peuvent être ajoutés à un ensemble, mais tout type de données peut être ajouté à un tableau.
Mecki
3

Voici une solution qui

  • N'utilise aucun NStype hérité
  • Est assez rapide avec O(n)
  • Est concis
  • Préserve l'ordre des éléments
extension Array where Element: Hashable {

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

ici, j'ai fait une solution O (n) pour les objets. Pas une solution en quelques lignes, mais ...

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")
kas-kad
la source
1
Plutôt que d'utiliser un Setavec un personnalisé DistinctWrapper, vous devez utiliser un Dictionaryfrom distinctAttributes to objects. Lorsque vous suivez cette logique, vous finirez par implémenter [ Dictionary.init(_:uniquingKeysWith:)] pastebin.com/w90pVe0p(https://developer.apple.com/documentation/… , qui est maintenant intégré à la bibliothèque standard. Découvrez à quel point c'est simple pastebin.com/w90pVe0p
Alexander - Reinstate Monica
2

J'ai utilisé la réponse de @ Jean-Philippe Pellet et fait une extension Array qui effectue des opérations de type set sur des tableaux, tout en conservant l'ordre des éléments.

/// 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) })
  }
}
Will Richardson
la source
Il n'est pas nécessaire d'utiliser un Bool, lorsque la seule valeur que vous utilisez est true. Vous atteignez un "type d'unité" (un type avec une seule valeur possible). Le type d'unité de Swift est Void, dont la seule valeur est ()(alias le tuple vide). Vous pouvez donc simplement utiliser [T: Void]. Bien que vous ne deviez pas faire cela, parce que vous venez d'inventer Set. Utilisez Setplutôt. Voir stackoverflow.com/a/55684308/3141234
Alexander - Reinstate Monica
2

Ceci est juste une implémentation très simple et pratique. Une propriété calculée dans une extension d'un tableau qui a des éléments équables.

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
    }
}
DaveAMoore
la source
1
C'est aussi O(n^2).
Alexander - Reinstate Monica
2
func removeDublicate (ab: [Int]) -> [Int] {
var answer1:[Int] = []
for i in ab {
    if !answer1.contains(i) {
        answer1.append(i)
    }}
return answer1
}

Usage:

let f = removeDublicate(ab: [1,2,2])
print(f)
Jack Rus
la source
Je pense que c'est le plus simple
Jack Rus
il garde l'ordre et vous donne un tableau que vous voulez
Jack Rus
C'est aussi O(n²).
Alexander - Reinstate Monica
2
  1. Ajoutez d'abord tous les éléments d'un tableau à NSOrderedSet.
  2. Cela supprimera tous les doublons de votre tableau.
  3. Convertissez à nouveau cet ensemble ordonné en un tableau.

Terminé....

Exemple

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

sortie de arrayWithoutDuplicates - [1,2,4,6,8]

Mahendra Thotakura
la source
2

Une version légèrement raccourcie basée sur la réponse d'extension de tableau de @ 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)
        }
    }
}
Sander Saelmans
la source
Cela fait deux opérations de hachage par élément, ce qui n'est pas nécessaire. insertrenvoie un tuple qui vous indique si l'élément était déjà là ou s'il a été ajouté pour la première fois. stackoverflow.com/a/55684308/3141234 Veuillez supprimer cette réponse.
Alexander - Rétablir Monica
1

Vous pouvez toujours utiliser un dictionnaire, car un dictionnaire ne peut contenir que des valeurs uniques. Par exemple:

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

Comme vous pouvez le voir, le tableau résultant ne sera pas toujours dans «l'ordre». Si vous souhaitez trier / commander le tableau, ajoutez ceci:

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

.

AT3D
la source
1

La façon la plus simple serait d'utiliser NSOrderedSet, qui stocke des éléments uniques et préserve l'ordre des éléments. Comme:

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)
sgl0v
la source
Je me demande comment cette performance se compare aux meilleures réponses ici. Avez-vous comparé?
Alexander - Reinstate Monica