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?
arrays
swift
standard-library
Altair357
la source
la source
NSSet
, NSSet est une collection d'objets non ordonnée, si nécessaire pour maintenir l'ordre NSOrderedSet.$.uniq(array)
github.com/ankurp/Dollar#uniq---uniqSet
de Swift? Vous pourrez fournir une liste d'éléments non ordonnés et uniques.Réponses:
Vous pouvez rouler le vôtre, par exemple comme ceci ( mis à jour pour Swift 1.2 avec Set ):
Version Swift 3:
Et comme extension pour
Array
:la source
var addedDict = [T:Bool](); return filter(source) { addedDict(true, forKey: $0) == nil }
updateValue(true, forKey: $0)...
place deaddedDict(true, forKey: $0)...
return filter(source) { addedDict.updateValue(true, forKey: $0) == nil }
comme tu dis.let uniques = Array(Set(vals))
Vous pouvez facilement convertir un ensemble et revenir à un tableau:
Il n'est pas garanti de conserver l'ordre d'origine de la baie.
la source
originals
ne le sont pasHashable
; seulsHashable
les types de données peuvent être ajoutés à un ensemble, mais tout type de données peut être ajouté à un tableau.Beaucoup de réponses disponibles ici, mais j'ai raté cette extension simple, adaptée à Swift 2 et plus:
Le rend super simple. Peut être appelé comme ceci:
Filtrage basé sur les propriétés
Pour filtrer un tableau en fonction des propriétés, vous pouvez utiliser cette méthode:
Que vous pouvez appeler comme suit:
la source
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
).O(n²)
performances temporelles, ce qui est vraiment mauvais pour les grands tableaux.O(n²)
complexité àO(n)
Swift 3.0
la source
array
ne le sont pasHashable
; seulsHashable
les types de données peuvent être ajoutés à un ensemble, mais tout type de données peut être ajouté à un tableau.Si vous mettez les deux extensions dans votre code, la
Hashable
version la plus rapide sera utilisée lorsque cela sera possible et laEquatable
version sera utilisée comme solution de secours.Si l'ordre n'est pas important, vous pouvez toujours simplement utiliser cet initialiseur Set .
la source
O(n²)
performances temporelles, ce qui est vraiment mauvais pour les grands tableaux.modifier / mettre à jour Swift 4 ou version ultérieure
Nous pouvons également étendre le
RangeReplaceableCollection
protocole pour lui permettre d'être également utilisé avec desStringProtocol
types:Méthode de mutation:
Pour Swift 3, cliquez ici
la source
reduce
implémentation, donc maintenant la complexité est différente.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 leO(n)
temps!Swift 4
toute tentative de
insert
se 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.
la source
O(n^2)
, et personne ne l'a remarqué.Swift 4
Garanti pour continuer à commander.
la source
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.O(n²)
performances temporelles, ce qui est vraiment mauvais pour les grands tableaux.O(n²)
. Il n'y a rien de rapide à ce sujet.reduce
oureduce(into:)
ne ferait pas une différence critique. Réécrire ceci pour ne pas appeler à plusieurs reprisescontains
ferait une BEAUCOUP plus grande différence.Voici une catégorie sur
SequenceType
laquelle préserve l'ordre d'origine du tableau, mais utilise aSet
pour effectuer lescontains
recherches afin d'éviter leO(n)
coût de lacontains(_:)
méthode Array .Si vous n'êtes pas hashable ou equatable, vous pouvez passer un prédicat pour effectuer la vérification d'égalité:
Maintenant, si vous n'avez pas de hashable, mais que vous êtes équitable, vous pouvez utiliser cette méthode:
Enfin, vous pouvez ajouter une version de chemin d'accès clé unique comme ceci:
Vous pouvez coller les deux dans votre application, Swift choisira la bonne en fonction du
Iterator.Element
type de votre séquence .la source
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/3141234Inspiré 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:
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:
Usage
Si nous voulons l'unicité pour les éléments eux-mêmes, comme dans la question, nous utilisons le keyPath
\.self
:Si nous voulons l'unicité pour autre chose (comme pour
id
une collection d'objets) alors nous utilisons le chemin de clé de notre choix:Solution de mutation
Nous étendons avec une fonction de mutation qui est capable de filtrer pour l'unicité sur n'importe quel keyPath:
Usage
Si nous voulons l'unicité pour les éléments eux-mêmes, comme dans la question, nous utilisons le keyPath
\.self
:Si nous voulons l'unicité pour autre chose (comme pour
id
une collection d'objets) alors nous utilisons le chemin de clé de notre choix:la source
keyPath
par défaut\.self
, car c'est probablement la majorité des cas d'utilisation.Element
toujours le faireHashable
. Une alternative à une valeur par défaut consiste à ajouter une simple surcharge sans paramètres:extension Sequence where Element: Hashable { func unique() { ... } }
Une solution alternative (sinon optimale) d' ici utilisant des types immuables plutôt que des variables:
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.la source
contains
et 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.filter
. C'est O (n ^ 2) (qui est requis si vous ne voulez pas exiger laHashable
conformité), mais vous devriez au moins le dire explicitementrapide 2
avec la fonction uniq réponse:
utilisation:
la source
Bool
valeur est évidemment redondante, car votre code ne la lit jamais. Utilisez unSet
au lieu d'unDictionary
et vous obtenez mon vote positif.Dans Swift 5
La sortie sera
la source
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:
Étant donné le tableau entier:
Code fonctionnel:
Code d'extension de tableau:
Ce code tire parti du résultat renvoyé par l'
insert
opération onSet
, qui s'exécute leO(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,
filter
l'exclura du résultat final.la source
defer
le code ferait l'opération de test définie deux fois, une aveccontains
et une avecinsert
. En lisant la documentation Swift, j'ai trouvé queinsert
renvoie un tuple indiquant si l'élément a été inséré ou non, j'ai donc simplifié le code en supprimant lacontains
vérification.extension Sequence where Iterator.Element: Hashable { ... }
insert
etcontains
ont laO(1)
complexité.O(1) + O(1) = O(1)
. Ces deux opérations sont ensuite effectuées plusieursn
fois (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 estO(n)
.Swift 4.x:
usage:
ou
la source
O(n^2)
. Ne fais pas ça.Swift 5
la source
extension Sequence { // Returns distinct elements based on a key value. func distinct<key: Hashable>(by: ((_ el: Iterator.Element) -> key)) -> [Iterator.Element] { var existing = Set<key>() return self.filter { existing.insert(by($0)).inserted } } }
Bool
, lorsque la seule valeur que vous utilisez esttrue
. Vous atteignez un "type d'unité" (un type avec une seule valeur possible). Le type d'unité de Swift estVoid
, 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'inventerSet
. UtilisezSet
plutôt. Voir stackoverflow.com/a/55684308/3141234 Veuillez supprimer cette réponse.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
enumerated
pour obtenir l'index etmap
revenir à la liste des valeurs.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:
Et une petite variété d'entrées de test:
Donne en sortie:
la source
Array(Set(myArray))
cela, cela fonctionne pour les choses qui ne le sont pasHashable
Array(Set(myArray))
l'ordre de votre tableau est conservé.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.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:
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:
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)
Il fonctionnera également avec une seule valeur d'unicité (en utilisant $ 1 et en ignorant $ 0).
la source
"\()"
, car il ne peut pas vous donner des valeurs uniques comme se conformer àHashable
devrait. Exemple, si vos éléments se conforment àPrintable
tous en retournant les mêmesdescription
, alors votre filtrage échoue.T
à l'êtreHashable
.Vous pouvez utiliser directement une collection de jeux pour supprimer les doublons, puis les reconstituer dans un tableau
Ensuite, vous pouvez commander votre tableau comme vous le souhaitez
la source
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 :
Exemple d'implémentation d'un type personnalisé qui peut être utilisé avec
uniq(_:)
(qui doit être conformeHashable
, et doncEquatable
, parceHashable
qu'étendEquatable
):Dans le code ci-dessus ...
id
, tel qu'il est utilisé dans la surcharge de==
, peut être n'importe quelEquatable
type (ou méthode qui renvoie unEquatable
type, par exemple,someMethodThatReturnsAnEquatableType()
). Le code mis en commentaire montre l'extension de la vérification de l'égalité, où sesomeOtherEquatableProperty
trouve une autre propriété d'unEquatable
type (mais peut également être une méthode qui renvoie unEquatable
type).id
, tel qu'il est utilisé dans lahashValue
propriété calculée (requis pour se conformer àHashable
), peut être toute propriété (Hashable
et doncEquatable
) (ou méthode qui renvoie unHashable
type).Exemple d'utilisation
uniq(_:)
:la source
Bool
, lorsque la seule valeur que vous utilisez esttrue
. Vous atteignez un "type d'unité" (un type avec une seule valeur possible). Le type d'unité de Swift estVoid
, 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'inventerSet
. UtilisezSet
plutôt. Voir stackoverflow.com/a/55684308/3141234Si vous avez besoin de trier les valeurs, cela fonctionne (Swift 4)
let sortedValues = Array(Set(array)).sorted()
la source
.sorted()
sert la fin. Cordialement.[2, 1, 1]
? Il sortirait[1, 2]
, ce n'est pas ordonné: p[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.array
ne le sont pasHashable
; seulsHashable
les types de données peuvent être ajoutés à un ensemble, mais tout type de données peut être ajouté à un tableau.Voici une solution qui
NS
type héritéO(n)
la source
ici, j'ai fait une solution O (n) pour les objets. Pas une solution en quelques lignes, mais ...
la source
Set
avec un personnaliséDistinctWrapper
, vous devez utiliser unDictionary
from 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/w90pVe0pJ'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.
la source
Bool
, lorsque la seule valeur que vous utilisez esttrue
. Vous atteignez un "type d'unité" (un type avec une seule valeur possible). Le type d'unité de Swift estVoid
, 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'inventerSet
. UtilisezSet
plutôt. Voir stackoverflow.com/a/55684308/3141234Ceci 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.
la source
O(n^2)
.Usage:
la source
O(n²)
.Terminé....
Exemple
sortie de arrayWithoutDuplicates - [1,2,4,6,8]
la source
Une version légèrement raccourcie basée sur la réponse d'extension de tableau de @ Jean-Philippe Pellet:
la source
insert
renvoie 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.Vous pouvez toujours utiliser un dictionnaire, car un dictionnaire ne peut contenir que des valeurs uniques. Par exemple:
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:
.
la source
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:
la source