Heutzutage geben Sie in Swift einfach ein Set( yourArray )
, um ein Array einzigartig zu machen. (Oder ein bestelltes Set, falls erforderlich.)
Wie wurde es gemacht, bevor das möglich war?
Ich könnte ein Array haben, das wie folgt aussieht:
[1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
Oder wirklich jede Folge von gleichartigen Teilen von Daten. Ich möchte sicherstellen, dass es nur eines von jedem identischen Element gibt. Zum Beispiel würde das obige Array werden:
[1, 4, 2, 6, 24, 15, 60]
Beachten Sie, dass die Duplikate von 2, 6 und 15 entfernt wurden, um sicherzustellen, dass nur eines von jedem identischen Element vorhanden ist. Bietet Swift eine Möglichkeit, dies einfach zu tun, oder muss ich es selbst tun?
arrays
swift
standard-library
Altair357
quelle
quelle
NSSet
NSSet zu konvertieren. NSSet ist eine ungeordnete Sammlung von Objekten, wenn die Reihenfolge NSOrderedSet beibehalten werden muss.$.uniq(array)
github.com/ankurp/Dollar#uniq---uniqSet
Swift? Sie können eine Liste ungeordneter und eindeutiger Elemente bereitstellen.Antworten:
Sie können Ihre eigenen rollen, zB wie folgt ( aktualisiert für Swift 1.2 mit Set ):
Swift 3 Version:
Und als Erweiterung für
Array
:quelle
var addedDict = [T:Bool](); return filter(source) { addedDict(true, forKey: $0) == nil }
updateValue(true, forKey: $0)...
stattaddedDict(true, forKey: $0)...
return filter(source) { addedDict.updateValue(true, forKey: $0) == nil }
wie du sagst.let uniques = Array(Set(vals))
Sie können ganz einfach in ein Set und wieder in ein Array konvertieren:
Es wird nicht garantiert, dass die ursprüngliche Reihenfolge des Arrays beibehalten wird.
quelle
originals
nicht vorhanden sindHashable
.Hashable
Einem Set können nur Datentypen hinzugefügt werden, einem Array kann jedoch jeder Datentyp hinzugefügt werden.Viele Antworten finden Sie hier, aber ich habe diese einfache Erweiterung verpasst, die für Swift 2 und höher geeignet ist:
Macht es super einfach. Kann so genannt werden:
Filterung basierend auf Eigenschaften
Um ein Array basierend auf Eigenschaften zu filtern, können Sie folgende Methode verwenden:
Was Sie wie folgt anrufen können:
quelle
extension Array where Element: Equatable
) wird durch stackoverflow.com/a/36048862/1033581 ersetzt, das eine leistungsfähigere Lösung bietet (extension Sequence where Iterator.Element: Equatable
).O(n²)
zeitliche Leistung, was für große Arrays sehr schlecht ist.O(n²)
Komplexität wieder aufO(n)
Swift 3.0
quelle
array
nicht vorhanden sindHashable
.Hashable
Einem Set können nur Datentypen hinzugefügt werden, einem Array kann jedoch jeder Datentyp hinzugefügt werden.Wenn Sie beide Erweiterungen in Ihren Code einfügen,
Hashable
wird nach Möglichkeit die schnellere Version und dieEquatable
Version als Fallback verwendet.Wenn die Reihenfolge nicht wichtig ist, können Sie immer nur diesen Set-Initialisierer verwenden .
quelle
O(n²)
zeitliche Leistung, was für große Arrays sehr schlecht ist.Swift 4 oder höher bearbeiten / aktualisieren
Wir können das
RangeReplaceableCollection
Protokoll auch erweitern , damit es auch mitStringProtocol
Typen verwendet werden kann:Mutationsmethode:
Für Swift 3 klicken Sie hier
quelle
reduce
Implementierung ersetzt, daher ist die Komplexität jetzt anders.O(n)
Zeit), während die Flatmap-basierte Version für 8 Millionen eindeutige Einträge 7,47-mal länger dauert als 1 Million, was darauf hindeutet, dass die Flatmap-basierte Version besser skaliert . Irgendwie ist die Flatmap-basierte Version etwas besser als dieO(n)
Zeit!Swift 4
Bei jedem Versuch
insert
wird auch ein Tupel zurückgegeben :(inserted: Bool, memberAfterInsert: Set.Element)
. Siehe Dokumentation .Die Verwendung des zurückgegebenen Werts hilft uns, Schleifen oder andere Operationen zu vermeiden.
quelle
O(n^2)
und niemand es bemerkte.Swift 4
Garantiert weiterhin zu bestellen.
quelle
reduce
: Insgesamt ist es nur eine weitere Zeile in Ihrem gesamten Projekt, Ihre Funktion wie folgt zu schreiben :var unique: [Iterator.Element] = []; for element in self where !unique.contains(element) { unique.append(element) }; return unique
. Ich gebe zu, ich habe die relativen Leistungen noch nicht getestet.O(n²)
zeitliche Leistung, was für große Arrays sehr schlecht ist.O(n²)
. Daran ist nichts schnelles.reduce
oderreduce(into:)
würde keinen kritischen Unterschied machen. Das Umschreiben, um nicht wiederholt anzurufen,contains
würde einen VIEL größeren Unterschied machen.Hier ist eine Kategorie auf
SequenceType
dem die ursprüngliche Reihenfolge der Anordnung bewahrt, sondern verwendet einSet
die tuncontains
Lookups das zu vermeiden ,O(n)
Kosten auf Array dercontains(_:)
Methode.Wenn Sie nicht Hashable oder Equatable sind, können Sie ein Prädikat übergeben, um die Gleichheitsprüfung durchzuführen:
Nun, wenn Sie nicht Hashable haben, aber sind gleichzusetzen, können Sie diese Methode verwenden:
Schließlich können Sie eine Schlüsselpfadversion von Uniqued wie folgt hinzufügen:
Sie können beide in Ihre App einfügen. Swift wählt je nach Sequenztyp die richtige aus
Iterator.Element
.quelle
O(n)
Lösung. Sie können die Set-Operationen "check" und "insert" übrigens zu einer kombinieren. Siehe stackoverflow.com/a/46354989/3141234Inspiriert von https://www.swiftbysundell.com/posts/the-power-of-key-paths-in-swift können wir ein leistungsfähigeres Tool deklarieren, das auf jedem keyPath nach Einheitlichkeit filtern kann. Dank der Kommentare von Alexander zu verschiedenen Antworten bezüglich der Komplexität sollten die folgenden Lösungen nahezu optimal sein.
Nicht mutierende Lösung
Wir erweitern mit einer Funktion, die in der Lage ist, auf jedem keyPath nach Einigkeit zu filtern:
Hinweis: Wenn Ihr Objekt nicht RangeReplaceableCollection, sondern Sequence entspricht, können Sie diese zusätzliche Erweiterung verwenden. Der Rückgabetyp ist jedoch immer ein Array:
Verwendung
Wenn wir wie in der Frage eine Einheit für die Elemente selbst wünschen, verwenden wir den keyPath
\.self
:Wenn wir Einheit für etwas anderes wollen (wie für die
id
eine Sammlung von Objekten), verwenden wir den Schlüsselpfad unserer Wahl:Mutierende Lösung
Wir erweitern mit einer Mutationsfunktion, die in der Lage ist, auf jedem keyPath nach Einigkeit zu filtern:
Verwendung
Wenn wir wie in der Frage eine Einheit für die Elemente selbst wünschen, verwenden wir den keyPath
\.self
:Wenn wir die Einheitlichkeit für etwas anderes wollen (wie für
id
eine Sammlung von Objekten), verwenden wir den Schlüsselpfad unserer Wahl:quelle
keyPath
Standardeinstellung\.self
, da dies wahrscheinlich die Mehrheit der Anwendungsfälle ist.Element
immer machenHashable
. Eine Alternative zu einem Standardwert ist das Hinzufügen einer einfachen Überladung ohne Parameter:extension Sequence where Element: Hashable { func unique() { ... } }
Eine alternative (wenn nicht optimale) Lösung von hier aus , bei der unveränderliche Typen anstelle von Variablen verwendet werden:
Eingeschlossen, um Jean-Pillippes imperativen Ansatz einem funktionalen Ansatz gegenüberzustellen.
Als Bonus funktioniert diese Funktion sowohl mit Strings als auch mit Arrays!
Bearbeiten: Diese Antwort wurde 2014 für Swift 1.0 geschrieben (zuvor
Set
war sie in Swift verfügbar). Es erfordert keine Hashable-Konformität und wird in quadratischer Zeit ausgeführt.quelle
contains
und Array-Anhänge werden in O (n) ausgeführt. Obwohl es den Vorteil hat, nur gleichwertig zu sein, nicht hashbar.filter
. Es ist O (n ^ 2) (was erforderlich ist, wenn Sie keineHashable
Konformität benötigen ), aber Sie sollten dies zumindest explizitschnell 2
mit uniq funktion antwort :
verwenden:
quelle
Bool
Wert ist offensichtlich redundant, da Ihr Code ihn nie liest. Verwenden Sie aSet
anstelle von aDictionary
und Sie erhalten meine positive Bewertung.In Swift 5
Ausgabe wird sein
quelle
Eine weitere Swift 3.0-Lösung zum Entfernen von Duplikaten aus einem Array. Diese Lösung verbessert viele andere Lösungen, die bereits vorgeschlagen wurden von:
Angesichts des Integer-Arrays:
Funktionscode:
Array-Erweiterungscode:
Dieser Code nutzt das Ergebnis, das von der
insert
Operation on zurückgegeben wirdSet
, die on ausgeführt wirdO(1)
, und gibt ein Tupel zurück, das angibt, ob das Element eingefügt wurde oder ob es bereits in der Gruppe vorhanden war.Wenn sich das Element im Set befand,
filter
wird es vom Endergebnis ausgeschlossen.quelle
defer
wenn Sie den Code verwenden, wird die eingestellte Testoperation zweimal ausgeführt, einmal mitcontains
und einmal mitinsert
. Beim Lesen der Swift-Dokumentation stellte ich fest, dassinsert
ein Tupel zurückgegeben wird, das angibt, ob das Element eingefügt wurde oder nicht. Daher habe ich den Code zum Entfernen dercontains
Prüfung vereinfacht .extension Sequence where Iterator.Element: Hashable { ... }
insert
undcontains
habenO(1)
Komplexität.O(1) + O(1) = O(1)
. Diese beiden Operationen werden dannn
mal ausgeführt (einmal pro Aufruf des an übergebenen Abschlussesfilter
, der einmal pro Element aufgerufen wird). Wenn eine Operation unabhängig von der Eingabegröße eine konstante Zeit benötigt, dauert die zweimalige Ausführung immer noch eine konstante Zeit das ist unabhängig von der Eingabegröße. Die Gesamtkomplexität davon istO(n)
.Swift 4.x:
Verwendung:
oder
quelle
O(n^2)
. Tu das nicht.Swift 5
quelle
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
, wenn der einzige Wert, den Sie verwenden, isttrue
. Sie greifen nach einem "Einheitentyp" (einem Typ mit nur einem möglichen Wert). Swifts Einheitentyp istVoid
, dessen einziger Wert()
(auch bekannt als leeres Tupel) ist. Sie können also einfach verwenden[T: Void]
. Obwohl Sie das nicht tun sollten, weil Sie im Grunde nur erfunden habenSet
. Verwenden SieSet
stattdessen. Siehe stackoverflow.com/a/55684308/3141234 Bitte löschen Sie diese Antwort.Denken Sie wie ein funktionierender Programmierer :)
Um die Liste danach zu filtern, ob das Element bereits aufgetreten ist, benötigen Sie den Index. Sie können verwenden
enumerated
, um den Index abzurufen undmap
zur Werteliste zurückzukehren.Dies garantiert die Bestellung. Wenn Ihnen die Reihenfolge nichts ausmacht, ist die vorhandene Antwort von
Array(Set(myArray))
einfacher und wahrscheinlich effizienter.UPDATE: Einige Hinweise zu Effizienz und Korrektheit
Einige Leute haben die Effizienz kommentiert. Ich bin definitiv in der Schule, zuerst korrekten und einfachen Code zu schreiben und später Engpässe herauszufinden, obwohl ich es zu schätzen weiß, dass es fraglich ist, ob dies klarer ist als
Array(Set(array))
.Diese Methode ist viel langsamer als
Array(Set(array))
. Wie in den Kommentaren erwähnt, bleibt die Reihenfolge erhalten und es werden Elemente verarbeitet, die nicht haschbar sind.Die Methode von @Alain T bewahrt jedoch auch die Ordnung und ist auch viel schneller. Wenn Ihr Elementtyp also nicht hashbar ist oder Sie nur einen schnellen Einzeiler benötigen, würde ich vorschlagen, mit ihrer Lösung fortzufahren.
Hier sind einige Tests auf einem MacBook Pro (2014) unter Xcode 11.3.1 (Swift 5.1) im Release-Modus.
Die Profiler-Funktion und zwei Methoden zum Vergleichen:
Und eine kleine Auswahl an Testeingaben:
Gibt als Ausgabe:
quelle
Array(Set(myArray))
dazu funktioniert dies für Dinge, die es nicht sindHashable
Array(Set(myArray))
zur Reihenfolge Ihres Arrays wird beibehalten.lastIndex(of:)
. Ich bin in diesem Fall völlig anderer Meinung über die Klarheit gegenüber dem Optimierungspunkt. Ich denke nicht, dass diese Implementierung besonders klar ist, insbesondere im Vergleich zu einer einfachen satzbasierten Lösung. In jedem Fall sollte dieser Code in eine Erweiterungsfunktion extrahiert werden. Dieser Algorithmus wird selbst bei einer geringen Eingabegröße wie Tausenden bis Zehntausenden grundsätzlich unbrauchbar. Es ist nicht schwer, solche Datensätze zu finden. Menschen können Tausende von Songs, Dateien, Kontakten usw. haben.Für Arrays, in denen die Elemente weder Hashbar noch Vergleichbar sind (z. B. komplexe Objekte, Wörterbücher oder Strukturen), bietet diese Erweiterung eine allgemeine Möglichkeit, Duplikate zu entfernen:
Sie müssen sich nicht darum kümmern, Werte Hashable zu machen, und Sie können verschiedene Kombinationen von Feldern für die Eindeutigkeit verwenden.
Hinweis: Für einen robusteren Ansatz lesen Sie bitte die von Coeur vorgeschlagene Lösung in den Kommentaren unten.
stackoverflow.com/a/55684308/1033581
[EDIT] Swift 4 Alternative
Mit Swift 4.2 können Sie die Hasher-Klasse verwenden, um einen Hash viel einfacher zu erstellen. Die obige Erweiterung könnte geändert werden, um dies zu nutzen:
Die aufrufende Syntax ist etwas anders, da der Abschluss einen zusätzlichen Parameter erhält, der eine Funktion zum Hashing einer variablen Anzahl von Werten enthält (die einzeln Hash-fähig sein müssen).
Es funktioniert auch mit einem einzelnen Eindeutigkeitswert (unter Verwendung von $ 1 und Ignorieren von $ 0).
quelle
"\()"
, da Sie möglicherweise keine eindeutigen Werte erhalten, wie zHashable
. Beispiel: Wenn Ihre Elemente übereinstimmen,Printable
indem alle dasselbe zurückgebendescription
, schlägt Ihre Filterung fehl.T
auf das SeinHashable
.Sie können eine Set-Sammlung direkt verwenden, um Duplikate zu entfernen und sie dann wieder in ein Array umzuwandeln
Dann können Sie Ihr Array bestellen, wie Sie möchten
quelle
Etwas prägnantere Syntaxversion von Daniel Kroms Swift 2-Antwort unter Verwendung eines abschließenden Verschlusses und eines Kurzargumentnamens, der anscheinend auf der ursprünglichen Antwort von Airspeed Velocity basiert :
Beispiel für einen benutzerdefinierten Typ implementieren, die mit verwendet werden können
uniq(_:)
(die entsprechen müssenHashable
, und damitEquatable
, weilHashable
sichEquatable
):Im obigen Code ...
id
, wie bei der Überladung von verwendet==
, kann ein beliebigerEquatable
Typ sein (oder eine Methode, die einenEquatable
Typ zurückgibt , zsomeMethodThatReturnsAnEquatableType()
. B. ). Der auskommentierte Code demonstriert die Erweiterung der Prüfung auf Gleichheit, wobeisomeOtherEquatableProperty
es sich um eine andere Eigenschaft einesEquatable
Typs handelt (es kann sich aber auch um eine Methode handeln, die einenEquatable
Typ zurückgibt ).id
, wie in derhashValue
berechneten Eigenschaft verwendet (erforderlich, um konform zu seinHashable
), kann eine beliebigeHashable
(und damitEquatable
) Eigenschaft (oder Methode, die einenHashable
Typ zurückgibt ) sein.Anwendungsbeispiel
uniq(_:)
:quelle
Bool
, wenn der einzige Wert, den Sie verwenden, isttrue
. Sie greifen nach einem "Einheitentyp" (einem Typ mit nur einem möglichen Wert). Swifts Einheitentyp istVoid
, dessen einziger Wert()
(auch bekannt als leeres Tupel) ist. Sie können also einfach verwenden[T: Void]
. Obwohl Sie das nicht tun sollten, weil Sie im Grunde nur erfunden habenSet
. Verwenden SieSet
stattdessen. Siehe stackoverflow.com/a/55684308/3141234Wenn Sie sortierte Werte benötigen, funktioniert dies (Swift 4)
let sortedValues = Array(Set(array)).sorted()
quelle
.sorted()
am Ende da. Grüße.[2, 1, 1]
? Es würde herauskommen[1, 2]
, das ist nicht bestellt: p[2, 1, 1]
. Das erste Auftreten von einzigartigen Elementen in der Reihenfolge ist[2, 1]
. Das ist die richtige Antwort. Aber Ihren (falschen) Algorithmus verwendet, erhalten Sie[1, 2]
, das ist sortiert, aber ist nicht in dem richtigen, ursprünglich, um.array
nicht vorhanden sindHashable
.Hashable
Einem Set können nur Datentypen hinzugefügt werden, einem Array kann jedoch jeder Datentyp hinzugefügt werden.Hier ist eine Lösung, die
NS
TypenO(n)
quelle
Hier habe ich eine O (n) -Lösung für Objekte durchgeführt. Nicht wenige Zeilen Lösung, aber ...
quelle
Set
mit einem Benutzerdefinierten zu verwendenDistinctWrapper
, sollten Sie aDictionary
von differentAttributes für Objekte verwenden. Wenn Sie dieser Logik folgen, werden Sie schließlich [Dictionary.init(_:uniquingKeysWith:)
] pastebin.com/w90pVe0p(https://developer.apple.com/documentation/… implementieren , das jetzt in die Standardbibliothek integriert ist. Überprüfen Sie, wie einfach dies ist pastebin.com/w90pVe0pIch habe die Antwort von @ Jean-Philippe Pellet verwendet und eine Array-Erweiterung erstellt, die satzartige Operationen für Arrays ausführt, während die Reihenfolge der Elemente beibehalten wird.
quelle
Bool
, wenn der einzige Wert, den Sie verwenden, isttrue
. Sie greifen nach einem "Einheitentyp" (einem Typ mit nur einem möglichen Wert). Swifts Einheitentyp istVoid
, dessen einziger Wert()
(auch bekannt als leeres Tupel) ist. Sie können also einfach verwenden[T: Void]
. Obwohl Sie das nicht tun sollten, weil Sie im Grunde nur erfunden habenSet
. Verwenden SieSet
stattdessen. Siehe stackoverflow.com/a/55684308/3141234Dies ist nur eine sehr einfache und bequeme Implementierung. Eine berechnete Eigenschaft in einer Erweiterung eines Arrays mit gleichwertigen Elementen.
quelle
O(n^2)
.Verwendung:
quelle
O(n²)
.Getan....
Beispiel
Ausgabe von arrayWithoutDuplicates - [1,2,4,6,8]
quelle
Eine leicht verkürzte Version, die auf der Antwort der Array-Erweiterung von @ Jean-Philippe Pellet basiert:
quelle
insert
Gibt ein Tupel zurück, das angibt, ob das Element bereits vorhanden war oder zum ersten Mal hinzugefügt wurde. stackoverflow.com/a/55684308/3141234 Bitte löschen Sie diese Antwort.Sie können immer ein Wörterbuch verwenden, da ein Wörterbuch nur eindeutige Werte enthalten kann. Beispielsweise:
Wie Sie sehen können, befindet sich das resultierende Array nicht immer in der richtigen Reihenfolge. Wenn Sie das Array sortieren / bestellen möchten, fügen Sie Folgendes hinzu:
.
quelle
Am einfachsten wäre es, NSOrderedSet zu verwenden, das eindeutige Elemente speichert und die Reihenfolge der Elemente beibehält. Mögen:
quelle