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

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

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

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


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.


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


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.


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

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

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.


1

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


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

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

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

0

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


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


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.