Der beste Weg, um doppelte Werte aus NSMutableArray in Objective-C zu entfernen?

147

Der beste Weg, um doppelte Werte ( NSString) aus NSMutableArrayObjective-C zu entfernen ?

Ist dies der einfachste und richtige Weg?

uniquearray = [[NSSet setWithArray:yourarray] allObjects];
Teo Choong Ping
quelle
5
Möglicherweise möchten Sie klarstellen, ob Sie Verweise auf genau dasselbe Objekt entfernen möchten oder auch auf Objekte, die unterschiedliche Objekte sind, aber für jedes Feld dieselben Werte haben.
Amagrammer
Gibt es keine Möglichkeit, dies zu tun, ohne eine Kopie des Arrays zu erstellen?
Hfossli
Dieser Weg ist einfach genug und vielleicht am besten. Aber zum Beispiel funktioniert es in meinem Fall nicht - die Elemente des Arrays sind keine vollständigen Duplikate und sollten von einer Eigenschaft verglichen werden.
Vyachaslav Gerchicov
Versuchen Sie dies einmal .. stackoverflow.com/a/38007095/3908884
Treffen Sie Doshi

Antworten:

242

Ihr NSSetAnsatz ist der beste, wenn Sie sich keine Sorgen um die Reihenfolge der Objekte machen. Wenn Sie sich jedoch keine Sorgen um die Reihenfolge machen, warum speichern Sie sie dann nicht zunächst in einer NSSet?

Ich habe die Antwort unten 2009 geschrieben. Im Jahr 2011 fügte Apple NSOrderedSetiOS 5 und Mac OS X 10.7 hinzu. Was früher ein Algorithmus war, besteht jetzt aus zwei Codezeilen:

NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:yourArray];
NSArray *arrayWithoutDuplicates = [orderedSet array];

Wenn Sie sich über die Reihenfolge Sorgen machen und iOS 4 oder früher verwenden, durchlaufen Sie eine Kopie des Arrays:

NSArray *copy = [mutableArray copy];
NSInteger index = [copy count] - 1;
for (id object in [copy reverseObjectEnumerator]) {
    if ([mutableArray indexOfObject:object inRange:NSMakeRange(0, index)] != NSNotFound) {
        [mutableArray removeObjectAtIndex:index];
    }
    index--;
}
[copy release];
Jim Puls
quelle
53
Wenn Sie Eindeutigkeit UND Reihenfolge benötigen, verwenden [NSOrderedSet orderedSetWithArray:array];Sie einfach. Sie können dann ein Array über zurückholen array = [orderedSet allObjects];oder einfach NSOrderedSets anstelle von s verwenden NSArray.
Regexident
10
Die Lösung von @ Regexident ist ideal. Ich muss einfach ersetzen [orderedSet allObjects]mit [orderedSet array]!
Inket
Nice One;) Ich mag die Antwort, die den Entwickler ohne viele Änderungen kopieren und einfügen lässt. Dies ist die Antwort, die jedem iOS-Entwickler gefallen wird;) @ abo3atef
Abo3atef
Danke, aber du solltest dein Beispiel reparieren. Grund - wir haben NSArrayund sollten normalerweise Temp schaffen NSMutableArray. In Ihrem Beispiel arbeiten Sie umgekehrt
Vyachaslav Gerchicov
NSSetWeiß jemand, welche Ansicht am besten geeignet ist, um Duplikate zu entfernen ? Ist diese Methode (mit ) oder der @ Simon Whitaker- Link zu verhindern, bevor Duplikate hinzugefügt werden?
Mathi Arasan
78

Ich weiß, dass dies eine alte Frage ist, aber es gibt eine elegantere Möglichkeit, Duplikate in einem zu entfernen, NSArray wenn Sie sich nicht für die Reihenfolge interessieren .

Wenn wir Objektoperatoren aus der Schlüsselwertcodierung verwenden , können wir dies tun:

uniquearray = [yourarray valueForKeyPath:@"@distinctUnionOfObjects.self"];

Wie AnthoPak ebenfalls feststellte, ist es möglich, Duplikate basierend auf einer Eigenschaft zu entfernen. Ein Beispiel wäre:@distinctUnionOfObjects.name

Tiago Almeida
quelle
3
Ja, das benutze ich auch! Dies ist ein sehr leistungsfähiger Ansatz, den viele iOS-Entwickler nicht kennen!
Lefteris
1
Ich war überrascht, als ich erfuhr, dass dies möglich sein könnte. Ich dachte, dass viele iOS-Entwickler dies nicht wissen könnten, deshalb habe ich beschlossen, diese Antwort hinzuzufügen :)
Tiago Almeida
12
Dadurch wird die Reihenfolge der Objekte nicht beibehalten.
Rudolf Adamkovič
2
Ja, es bricht die Reihenfolge.
Rostyslav Druzhchenko
Beachten Sie, dass es auch verwendet werden kann @distinctUnionOfObjects.property, um Duplikate nach Eigenschaften eines Arrays benutzerdefinierter Objekte zu entfernen. Zum Beispiel@distinctUnionOfObjects.name
AnthoPak
47

Ja, die Verwendung von NSSet ist ein sinnvoller Ansatz.

Um die Antwort von Jim Puls zu ergänzen, gibt es hier einen alternativen Ansatz zum Entfernen von Duplikaten unter Beibehaltung der Reihenfolge:

// Initialise a new, empty mutable array 
NSMutableArray *unique = [NSMutableArray array];

for (id obj in originalArray) {
    if (![unique containsObject:obj]) {
        [unique addObject:obj];
    }
}

Es ist im Wesentlichen der gleiche Ansatz wie bei Jim, kopiert jedoch eindeutige Elemente in ein neues veränderbares Array, anstatt Duplikate aus dem Original zu löschen. Dies macht es etwas speichereffizienter bei einem großen Array mit vielen Duplikaten (es ist nicht erforderlich, eine Kopie des gesamten Arrays zu erstellen) und ist meiner Meinung nach etwas lesbarer.

Beachten Sie, dass in beiden Fällen die Überprüfung, ob ein Element bereits im Zielarray enthalten ist ( containsObject:in meinem Beispiel oder indexOfObject:inRange:in Jims), für große Arrays nicht gut skalierbar ist. Diese Überprüfungen werden in O (N) -Zeit ausgeführt. Wenn Sie also die Größe des ursprünglichen Arrays verdoppeln, dauert die Ausführung jeder Überprüfung doppelt so lange. Da Sie die Prüfung für jedes Objekt im Array durchführen, führen Sie auch mehr dieser teureren Prüfungen durch. Der Gesamtalgorithmus (sowohl meiner als auch Jims) läuft in O (N 2 ) -Zeit, was schnell teuer wird, wenn das ursprüngliche Array wächst.

Um dies auf O (N) Zeit zu NSMutableSetreduzieren, können Sie mit a einen Datensatz von Elementen speichern, die bereits zum neuen Array hinzugefügt wurden, da NSSet-Lookups O (1) und nicht O (N) sind. Mit anderen Worten, die Überprüfung, ob ein Element Mitglied eines NSSet ist, dauert unabhängig von der Anzahl der Elemente in der Gruppe dieselbe Zeit.

Code, der diesen Ansatz verwendet, würde ungefähr so ​​aussehen:

NSMutableArray *unique = [NSMutableArray array];
NSMutableSet *seen = [NSMutableSet set];

for (id obj in originalArray) {
    if (![seen containsObject:obj]) {
        [unique addObject:obj];
        [seen addObject:obj];
    }
}

Dies scheint jedoch immer noch ein wenig verschwenderisch; Wir generieren immer noch ein neues Array, wenn die Frage klarstellt, dass das ursprüngliche Array veränderbar ist. Daher sollten wir in der Lage sein, es zu entfernen und Speicherplatz zu sparen. Etwas wie das:

NSMutableSet *seen = [NSMutableSet set];
NSUInteger i = 0;

while (i < [originalArray count]) {
    id obj = [originalArray objectAtIndex:i];

    if ([seen containsObject:obj]) {
        [originalArray removeObjectAtIndex:i];
        // NB: we *don't* increment i here; since
        // we've removed the object previously at
        // index i, [originalArray objectAtIndex:i]
        // now points to the next object in the array.
    } else {
        [seen addObject:obj];
        i++;
    }
}

UPDATE : Yuri Niyazov wies darauf hin, dass meine letzte Antwort tatsächlich in O (N 2 ) removeObjectAtIndex:läuft, weil sie wahrscheinlich in O (N) läuft.

(Er sagt "wahrscheinlich", weil wir nicht genau wissen, wie es implementiert ist. Eine mögliche Implementierung ist jedoch, dass die Methode nach dem Löschen des Objekts am Index X jedes Element vom Index X + 1 bis zum letzten Objekt im Array durchläuft Wenn dies der Fall ist, ist dies tatsächlich die O (N) -Leistung.)

Also, was tun? Es hängt von der Situation ab. Wenn Sie ein großes Array haben und nur eine kleine Anzahl von Duplikaten erwarten, funktioniert die direkte Deduplizierung einwandfrei und Sie müssen kein doppeltes Array erstellen. Wenn Sie ein Array haben, in dem Sie viele Duplikate erwarten, ist der Aufbau eines separaten, nicht duplizierten Arrays wahrscheinlich der beste Ansatz. Das Mitnehmen hier ist, dass die Big-O-Notation nur die Eigenschaften eines Algorithmus beschreibt und Ihnen nicht definitiv sagt, welche für einen bestimmten Umstand am besten ist.

Simon Whitaker
quelle
20

Wenn Sie auf iOS 5+ abzielen (was die gesamte iOS-Welt abdeckt), verwenden Sie es am besten NSOrderedSet. Es entfernt Duplikate und behält die Reihenfolge Ihrer NSArray.

Mach einfach

NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:yourArray];

Sie können es jetzt wieder in ein eindeutiges NSArray konvertieren

NSArray *uniqueArray = orderedSet.array;

Oder verwenden Sie einfach das orderSet, da es die gleichen Methoden wie ein NSArray objectAtIndex:hat firstObjectund so weiter.

Ein Mitgliedschafts-Check mit containsist auf dem noch schneller NSOrderedSetals auf einemNSArray

Weitere Informationen finden Sie in der NSOrderedSet-Referenz

lukaswelte
quelle
Das hat meine Stimme bekommen, ich habe sie alle gelesen und es ist die beste Antwort. Ich kann nicht glauben, dass die beste Antwort eine manuelle Schleife ist. Oh, sie haben diese Antwort jetzt kopiert.
Malhal
19

Verfügbar in OS X 10.7 und höher.

Wenn Sie sich Sorgen um die Bestellung machen, ist dies der richtige Weg

NSArray *no = [[NSOrderedSet orderedSetWithArray:originalArray]allObjects];

Hier ist der Code zum Entfernen doppelter Werte aus NSArray in der Reihenfolge.

Sultania
quelle
1
allObjects sollte Array sein
Malhal
7

brauche Bestellung

NSArray *yourarray = @[@"a",@"b",@"c"];
NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:yourarray];
NSArray *arrayWithoutDuplicates = [orderedSet array];
NSLog(@"%@",arrayWithoutDuplicates);

oder brauche keine Bestellung

NSSet *set = [NSSet setWithArray:yourarray];
NSArray *arrayWithoutOrder = [set allObjects];
NSLog(@"%@",arrayWithoutOrder);
Mike
quelle
3

Hier habe ich doppelte Namenswerte aus mainArray entfernt und das Ergebnis in NSMutableArray (listOfUsers) gespeichert.

for (int i=0; i<mainArray.count; i++) {
    if (listOfUsers.count==0) {
        [listOfUsers addObject:[mainArray objectAtIndex:i]];

    }
   else if ([[listOfUsers valueForKey:@"name" ] containsObject:[[mainArray objectAtIndex:i] valueForKey:@"name"]])
    {  
       NSLog(@"Same object");
    }
    else
    {
        [listOfUsers addObject:[mainArray objectAtIndex:i]];
    }
}
Bibin Joseph
quelle
1

Beachten Sie, dass Sie bei einem sortierten Array nicht mit jedem anderen Element im Array vergleichen müssen, sondern nur mit dem letzten Element. Dies sollte viel schneller sein als die Überprüfung aller Elemente.

// sortedSourceArray is the source array, already sorted
NSMutableArray *newArray = [[NSMutableArray alloc] initWithObjects:[sortedSourceArray objectAtIndex:0]];
for (int i = 1; i < [sortedSourceArray count]; i++)
{
    if (![[sortedSourceArray objectAtIndex:i] isEqualToString:[sortedSourceArray objectAtIndex:(i-1)]])
    {
        [newArray addObject:[tempArray objectAtIndex:i]];
    }
}

Es sieht so aus, als ob die NSOrderedSetAntworten, die ebenfalls vorgeschlagen werden, viel weniger Code erfordern. Wenn Sie jedoch NSOrderedSetaus irgendeinem Grund keinen verwenden können und ein sortiertes Array haben, ist meine Lösung meiner Meinung nach die schnellste. Ich bin mir nicht sicher, wie es mit der Geschwindigkeit der NSOrderedSetLösungen verglichen wird. Beachten Sie auch, dass mein Code mit überprüft isEqualToString:, sodass dieselbe Buchstabenfolge nicht mehr als einmal in angezeigt wird newArray. Ich bin nicht sicher, ob die NSOrderedSetLösungen Duplikate basierend auf dem Wert oder basierend auf dem Speicherort entfernen.

In meinem Beispiel wird davon sortedSourceArrayausgegangen, dass nur NSStrings, nur NSMutableStrings oder eine Mischung aus beiden enthalten ist. Wenn sortedSourceArraystattdessen nur NSNumbers oder nur NSDates enthält, können Sie ersetzen

if (![[sortedSourceArray objectAtIndex:i] isEqualToString:[sortedSourceArray objectAtIndex:(i-1)]])

mit

if ([[sortedSourceArray objectAtIndex:i] compare:[sortedSourceArray objectAtIndex:(i-1)]] != NSOrderedSame)

und es sollte perfekt funktionieren. Wenn es sortedSourceArrayeine Mischung aus NSStrings, NSNumbers und / oder NSDates enthält, wird es wahrscheinlich abstürzen.

GeneralMike
quelle
1

Es gibt einen KVC- Objektoperator , der eine elegantere Lösung bietet. uniquearray = [yourarray valueForKeyPath:@"@distinctUnionOfObjects.self"];Hier ist eine NSArray-Kategorie .

Peter
quelle
1

Eine weitere einfache Möglichkeit, die Sie ausprobieren können, um vor dem Hinzufügen eines Objekts zum Array keinen doppelten Wert hinzuzufügen: -

// Angenommen, mutableArray wird zugewiesen und initialisiert und enthält einen Wert

if (![yourMutableArray containsObject:someValue])
{
   [yourMutableArray addObject:someValue];
}
Hussain Shabbir
quelle
1

Entfernen Sie doppelte Werte aus NSMutableArray in Objective-C

NSMutableArray *datelistArray = [[NSMutableArray alloc]init];
for (Student * data in fetchStudentDateArray)
{
    if([datelistArray indexOfObject:data.date] == NSNotFound)
    [datelistArray addObject:data.date];
}
Arvind Patel
quelle
0

Hier ist der Code zum Entfernen doppelter Werte aus dem NSMutable Array. .es wird für Sie arbeiten. myArray ist Ihr veränderbares Array, mit dem Sie doppelte Werte entfernen möchten.

for(int j = 0; j < [myMutableArray count]; j++){
    for( k = j+1;k < [myMutableArray count];k++){
    NSString *str1 = [myMutableArray objectAtIndex:j];
    NSString *str2 = [myMutableArray objectAtIndex:k];
    if([str1 isEqualToString:str2])
        [myMutableArray removeObjectAtIndex:k];
    }
 } // Now print your array and will see there is no repeated value
IHSAN KHAN
quelle
0

Verwenden Orderedsetwird den Trick machen. Dadurch werden die Duplikate aus dem Array entfernt und die Reihenfolge beibehalten, die normalerweise nicht festgelegt ist

abhi
quelle
-3

Verwenden Sie einfach diesen einfachen Code:

NSArray *hasDuplicates = /* (...) */;
NSArray *noDuplicates = [[NSSet setWithArray: hasDuplicates] allObjects];

da nsset keine doppelten Werte zulässt und alle Objekte ein Array zurückgeben

Dinesh619
quelle
Hat für mich gearbeitet. Sie müssen lediglich Ihr NSArray erneut sortieren, da NSSet ein unsortiertes NSArray zurückgibt.
Lindinax
Oder verwenden Sie einfach NSOrderedSetinsteed von NSSet.
Lindinax