Najlepszy sposób na usunięcie zduplikowanych wartości z NSMutableArray w Objective-C?

147

Najlepszy sposób na usunięcie zduplikowanych wartości ( NSString) z NSMutableArrayObjective-C?

Czy to najłatwiejszy i właściwy sposób?

uniquearray = [[NSSet setWithArray:yourarray] allObjects];
Teo Choong Ping
źródło
5
Możesz chcieć wyjaśnić, czy chcesz wyeliminować odniesienia do dokładnie tego samego obiektu, czy też tych, które są odrębnymi obiektami, ale mają te same wartości dla każdego pola.
Amagrammer
Czy nie ma sposobu, aby to zrobić bez tworzenia kopii tablicy?
hfossli
Ten sposób jest dość łatwy i może najlepszy. Ale na przykład to nie zadziała w moim przypadku - elementy tablicy nie są pełnymi duplikatami i powinny być porównywane według jednej właściwości.
Vyachaslav Gerchicov
Spróbuj tego raz. Stackoverflow.com/a/38007095/3908884
Poznaj Doshi,

Odpowiedzi:

242

Twoje NSSetpodejście jest najlepsze, jeśli nie martwisz się o kolejność obiektów, ale z drugiej strony, jeśli nie martwisz się o kolejność, dlaczego nie przechowujesz ich NSSetna początku?

Napisałem odpowiedź poniżej w 2009 roku; w 2011 roku Apple dodał NSOrderedSetiOS 5 i Mac OS X 10.7. To, co kiedyś było algorytmem, to teraz dwa wiersze kodu:

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

Jeśli martwisz się o kolejność i korzystasz z systemu iOS 4 lub starszego, wykonaj pętlę nad kopią tablicy:

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
źródło
53
Jeśli potrzebujesz wyjątkowości ORAZ kolejności, po prostu użyj [NSOrderedSet orderedSetWithArray:array];Możesz następnie odzyskać tablicę przez array = [orderedSet allObjects];lub po prostu użyj NSOrderedSets zamiast NSArrayw pierwszej kolejności.
Regexident
10
Rozwiązanie @ Regexident jest idealne. Wystarczy wymienić [orderedSet allObjects]z [orderedSet array]!
inket
Niezły;) Podoba mi się odpowiedź, która sprawia, że ​​programista kopiuje i wkleja bez wielu modyfikacji, to odpowiedź, którą polubi każdy programista iOS;) @ abo3atef
Abo3atef
Dzięki, ale powinieneś naprawić przykład. Przyczyna - zwykle mamy NSArrayi powinniśmy stworzyć temp NSMutableArray. W twoim przykładzie pracujesz odwrotnie
Vyachaslav Gerchicov
Czy ktoś wie, który widok jest najlepszy do usuwania duplikatów, czy ta metoda (użycie NSSet) lub link @Simon Whitaker zapobiega przed dodaniem zduplikowanej wartości, co jest skutecznym sposobem?
Mathi Arasan
78

Wiem, że to stare pytanie, ale istnieje bardziej elegancki sposób usuwania duplikatów, NSArray jeśli nie zależy ci na zamówieniu .

Jeśli użyjemy operatorów obiektów z Key Value Coding, możemy to zrobić:

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

Jak zauważył również AnthoPak , możliwe jest usuwanie duplikatów na podstawie właściwości. Przykładem może być:@distinctUnionOfObjects.name

Tiago Almeida
źródło
3
Tak, tego też używam! To bardzo potężne podejście, którego wielu programistów iOS nie zna!
Lefteris
1
Byłem zaskoczony, gdy dowiedziałem się, że to możliwe. Myślałem, że wielu programistów iOS nie może tego wiedzieć, dlatego zdecydowałem się dodać tę odpowiedź :)
Tiago Almeida
12
To nie utrzymuje porządku obiektów.
Rudolf Adamkovič
2
Tak, to łamie porządek.
Rostyslav Druzhchenko 07.07.14
Zauważ, że może być również używany @distinctUnionOfObjects.propertydo usuwania duplikatów według właściwości tablicy obiektów niestandardowych. Na przykład@distinctUnionOfObjects.name
AnthoPak
47

Tak, używanie NSSet jest rozsądnym podejściem.

Aby dodać do odpowiedzi Jima Pulsa, oto alternatywne podejście do usuwania duplikatów przy zachowaniu porządku:

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

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

Jest to zasadniczo to samo podejście, co Jim's, ale kopiuje unikalne elementy do świeżej, zmiennej tablicy, zamiast usuwać duplikaty z oryginału. To sprawia, że ​​jest nieco bardziej wydajna pamięciowo w przypadku dużej tablicy z dużą ilością duplikatów (nie ma potrzeby wykonywania kopii całej tablicy) i jest moim zdaniem trochę bardziej czytelna.

Zauważ, że w obu przypadkach sprawdzenie, czy element jest już uwzględniony w tablicy docelowej (używając containsObject:w moim przykładzie lub indexOfObject:inRange:w Jim's), nie jest dobrze skalowane dla dużych tablic. Te testy są uruchamiane w czasie O (N), co oznacza, że ​​jeśli podwoisz rozmiar oryginalnej tablicy, wykonanie każdego sprawdzenia potrwa dwa razy dłużej. Ponieważ sprawdzasz każdy obiekt w tablicy, będziesz także uruchamiać więcej tych droższych testów. Ogólny algorytm (zarówno mój, jak i Jima) działa w czasie O (N 2 ), co szybko staje się kosztowne, gdy oryginalna tablica rośnie.

Aby sprowadzić to do czasu O (N), możesz użyć a NSMutableSetdo przechowywania rekordu elementów już dodanych do nowej tablicy, ponieważ wyszukiwania NSSet mają wartość O (1) zamiast O (N). Innymi słowy, sprawdzenie, czy element jest członkiem zestawu NSSet, zajmuje tyle samo czasu, niezależnie od tego, ile elementów znajduje się w zestawie.

Kod wykorzystujący to podejście wyglądałby mniej więcej tak:

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

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

To wciąż wydaje się trochę marnotrawne; nadal generujemy nową tablicę, gdy pytanie jasno wskazywało, że oryginalna tablica jest zmienna, więc powinniśmy być w stanie usunąć ją na miejscu i zaoszczędzić trochę pamięci. Coś takiego:

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

AKTUALIZACJA : Yuri Niyazov wskazał, że moja ostatnia odpowiedź faktycznie działa w czasie O (N 2 ), ponieważ removeObjectAtIndex:prawdopodobnie działa w czasie O (N).

(Mówi „prawdopodobnie”, ponieważ nie wiemy na pewno, jak to zaimplementowano; ale jedną z możliwych implementacji jest to, że po usunięciu obiektu w indeksie X metoda przechodzi przez każdy element od indeksu X + 1 do ostatniego obiektu w tablicy , przenosząc je do poprzedniego indeksu. Jeśli tak jest, to rzeczywiście jest to wydajność O (N)).

Więc co robić? To zależy od sytuacji. Jeśli masz dużą tablicę i spodziewasz się tylko niewielkiej liczby duplikatów, funkcja de-duplikacji w miejscu będzie działać dobrze i pozwoli zaoszczędzić na tworzeniu zduplikowanej tablicy. Jeśli masz tablicę, w której spodziewasz się wielu duplikatów, prawdopodobnie najlepszym rozwiązaniem jest utworzenie oddzielnej, pozbawionej dupleksu tablicy. Wniosek jest taki, że notacja duże-O opisuje tylko charakterystykę algorytmu, a nie mówi ostatecznie, który jest najlepszy w danych okolicznościach.

Simon Whitaker
źródło
20

Jeśli celujesz w iOS 5+ (który obejmuje cały świat iOS), najlepiej używać NSOrderedSet. Usuwa duplikaty i zachowuje kolejność plików NSArray.

Po prostu zrób

NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:yourArray];

Możesz teraz przekonwertować go z powrotem na unikalny NSArray

NSArray *uniqueArray = orderedSet.array;

Lub po prostu użyć orderedSet ponieważ ma takie same metody jak NSArray podobnego objectAtIndex:, firstObjecti tak dalej.

Sprawdzanie członkostwa w usłudze containsjest jeszcze szybsze NSOrderedSetniż w przypadku domenyNSArray

Aby uzyskać więcej informacji, zapoznaj się z dokumentacją NSOrderedSet Reference

lukaswelte
źródło
To dostałem mój głos, przeczytałem je wszystkie i to najlepsza odpowiedź. Nie mogę uwierzyć, że najlepszą odpowiedzią jest ręczna pętla. Och, teraz skopiowali tę odpowiedź.
malhal
19

Dostępne w systemie OS X 10.7 i nowszych.

Jeśli martwisz się o zamówienie, właściwy sposób

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

Oto kod usuwania zduplikowanych wartości z NSArray w kolejności.

Sultania
źródło
1
allObjects powinny być array
malhal
7

Potrzebujesz porządku

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

lub nie potrzebujesz zamówienia

NSSet *set = [NSSet setWithArray:yourarray];
NSArray *arrayWithoutOrder = [set allObjects];
NSLog(@"%@",arrayWithoutOrder);
Mikrofon
źródło
3

Tutaj usunąłem zduplikowane wartości nazw z mainArray i zapisz wynik w NSMutableArray (listOfUsers)

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
źródło
1

Zwróć uwagę, że jeśli masz posortowaną tablicę, nie musisz sprawdzać każdego innego elementu w tablicy, tylko ostatni element. Powinno to być znacznie szybsze niż sprawdzanie wszystkich elementów.

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

Wygląda na to, że NSOrderedSetodpowiedzi, które również są sugerowane, wymagają dużo mniej kodu, ale jeśli NSOrderedSetz jakiegoś powodu nie możesz użyć , a masz posortowaną tablicę, uważam, że moje rozwiązanie byłoby najszybsze. Nie jestem pewien, jak wypada to w porównaniu z szybkością NSOrderedSetrozwiązań. Zauważ również, że mój kod jest sprawdzany isEqualToString:, więc ta sama seria liter nie pojawi się więcej niż raz w newArray. Nie jestem pewien, czy NSOrderedSetrozwiązania usuwają duplikaty na podstawie wartości lub lokalizacji w pamięci.

Mój przykład zakłada, że sortedSourceArrayzawiera tylko NSStrings, tylko NSMutableStrings lub ich połączenie. Jeśli sortedSourceArrayzamiast tego zawiera tylko NSNumbers lub tylko NSDates, możesz zamienić

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

z

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

i powinno działać idealnie. Jeśli sortedSourceArrayzawiera kombinację NSStrings, NSNumbers i / lub NSDates, prawdopodobnie ulegnie awarii.

GeneralMike
źródło
1

Istnieje operator obiektu KVC, który oferuje bardziej eleganckie rozwiązanie. uniquearray = [yourarray valueForKeyPath:@"@distinctUnionOfObjects.self"];Oto kategoria NSArray .

Piotr
źródło
1

Jeszcze jeden prosty sposób, który możesz wypróbować, który nie doda zduplikowanej wartości przed dodaniem obiektu do tablicy: -

// Załóżmy, że mutableArray jest przydzielona i zainicjowana oraz zawiera pewną wartość

if (![yourMutableArray containsObject:someValue])
{
   [yourMutableArray addObject:someValue];
}
Hussain Shabbir
źródło
1

Usuń zduplikowane wartości z NSMutableArray w Objective-C

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

Oto kod usuwania zduplikowanych wartości z NSMutable Array. . to będzie działać dla Ciebie. myArray to Twoja Mutable Array, z której chcesz usunąć zduplikowane wartości.

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
źródło
0

Użycie Orderedsetwystarczy. Pozwoli to zachować usuwanie duplikatów z tablicy i zachować kolejność, której zestawy normalnie nie działają

abhi
źródło
-3

po prostu użyj tego prostego kodu:

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

ponieważ nsset nie zezwala na zduplikowane wartości i wszystkie obiekty zwracają tablicę

Dinesh619
źródło
Pracował dla mnie. Wszystko, co musisz zrobić, to ponownie posortować tablicę NSArray, ponieważ NSSet zwraca nieposortowaną tablicę NSArray.
lindinax
Lub po prostu użyj NSOrderedSetinsteed of NSSet.
lindinax