Il modo migliore per rimuovere valori duplicati da NSMutableArray in Objective-C?


147

Il modo migliore per rimuovere valori duplicati ( NSString) da NSMutableArrayin Objective-C?

È questo il modo più semplice e giusto per farlo?

uniquearray = [[NSSet setWithArray:yourarray] allObjects];

5
Potresti voler chiarire se vuoi eliminare i riferimenti allo stesso oggetto esatto o anche quelli che sono oggetti distinti ma hanno gli stessi valori per ogni campo.
Amagrammer,

Non c'è modo di farlo senza creare alcuna copia dell'array?
hfossli,

In questo modo è abbastanza facile e forse il migliore. Ma per esempio non funzionerà nel mio caso - gli elementi dell'array non sono duplicati completi e dovrebbero essere confrontati da una proprietà.
Vyachaslav Gerchicov,

Risposte:


242

Il tuo NSSetapproccio è il migliore se non sei preoccupato per l'ordine degli oggetti, ma di nuovo, se non sei preoccupato per l'ordine, allora perché non li memorizzi in un NSSetinizio?

Ho scritto la risposta qui sotto nel 2009; nel 2011, Apple ha aggiunto NSOrderedSetiOS 5 e Mac OS X 10.7. Quello che era stato un algoritmo è ora due righe di codice:

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

Se sei preoccupato per l'ordine e stai eseguendo su iOS 4 o versioni precedenti, passa in rassegna una copia dell'array:

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
Se hai bisogno di unicità e ordine, usa semplicemente [NSOrderedSet orderedSetWithArray:array];Puoi quindi recuperare un array tramite array = [orderedSet allObjects];o semplicemente usare NSOrderedSets invece che NSArrayin primo luogo.
Regexident,

10
La soluzione di @ Regexident è l'ideale. Devo solo sostituire [orderedSet allObjects]con [orderedSet array]!
sigillo

Nice One;) Mi piace la risposta che fa copiare e incollare lo sviluppatore senza molte modifiche, questa è la risposta che piacerà a tutti gli sviluppatori iOS;) @ abo3atef
Abo3atef

Grazie ma dovresti risolvere il tuo esempio. Motivo: di solito abbiamo NSArraye dovremmo creare temp NSMutableArray. Nel tuo esempio lavori viceversa
Vyachaslav Gerchicov il

Qualcuno sa quale sia la migliore vista per rimuovere i duplicati è questo metodo (utilizzando NSSet) o il link @Simon Whitaker impedire prima di aggiungere un valore duplicato che è un modo efficace?
Mathi Arasan,

78

So che questa è una vecchia domanda, ma c'è un modo più elegante per rimuovere i duplicati in un NSArray se non ti interessa l'ordine .

Se utilizziamo gli operatori oggetto dalla codifica dei valori chiave , possiamo fare questo:

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

Come notato anche AnthoPak , è possibile rimuovere i duplicati in base a una proprietà. Un esempio potrebbe essere:@distinctUnionOfObjects.name


3
Sì, questo è quello che uso anche io! Questo è un approccio molto potente, che molti sviluppatori iOS non conoscono!
Lefteris,

1
Sono stato sorpreso quando ho saputo che questo poteva essere possibile. Pensavo che molti sviluppatori iOS non potessero saperlo, per questo ho deciso di aggiungere questa risposta :)
Tiago Almeida,

12
Questo non mantiene l'ordine degli oggetti.
Rudolf Adamkovič,

2
Sì, rompe l'ordine.
Rostyslav Druzhchenko,

Si noti che può anche essere utilizzato come @distinctUnionOfObjects.propertyrimuovere i duplicati per proprietà di una matrice di oggetti personalizzati. Ad esempio@distinctUnionOfObjects.name
AnthoPak,

47

Sì, l'utilizzo di NSSet è un approccio sensato.

Per aggiungere alla risposta di Jim Puls, ecco un approccio alternativo per rimuovere i duplicati mantenendo l'ordine:

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

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

È essenzialmente lo stesso approccio di Jim, ma copia elementi unici in un nuovo array mutabile piuttosto che eliminare duplicati dall'originale. Ciò rende leggermente più efficiente la memoria nel caso di un array di grandi dimensioni con molti duplicati (non è necessario eseguire una copia dell'intero array) ed è, a mio avviso, un po 'più leggibile.

Si noti che in entrambi i casi, verificando se un elemento è già incluso nell'array di destinazione (usando containsObject:nel mio esempio o indexOfObject:inRange:in quello di Jim) non si adatta bene per array di grandi dimensioni. Tali controlli vengono eseguiti nel tempo O (N), il che significa che se si raddoppia la dimensione dell'array originale, ciascun controllo richiederà il doppio del tempo per l'esecuzione. Dal momento che stai eseguendo il controllo per ogni oggetto dell'array, eseguirai anche più di quei controlli più costosi. L'algoritmo generale (sia mio che di Jim) viene eseguito nel tempo O (N 2 ), che diventa costoso rapidamente man mano che l'array originale cresce.

Per arrivare a O (N) tempo è possibile utilizzare a NSMutableSetper memorizzare un record di elementi già aggiunti al nuovo array, poiché le ricerche NSSet sono O (1) anziché O (N). In altre parole, verificare se un elemento è un membro di un NSSet richiede lo stesso tempo, indipendentemente dal numero di elementi nel set.

Il codice che utilizza questo approccio sarebbe simile al seguente:

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

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

Questo sembra comunque un po 'dispendioso; stiamo ancora generando un nuovo array quando la domanda ha chiarito che l'array originale è mutabile, quindi dovremmo essere in grado di de-duplicarlo sul posto e risparmiare un po 'di memoria. Qualcosa come questo:

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

AGGIORNAMENTO : Yuri Niyazov ha sottolineato che la mia ultima risposta in realtà viene eseguita in O (N 2 ) perché removeObjectAtIndex:probabilmente viene eseguita in O (N).

(Dice "probabilmente" perché non sappiamo con certezza come sia implementato, ma una possibile implementazione è che dopo aver eliminato l'oggetto all'indice X il metodo quindi scorre attraverso ogni elemento dall'indice X + 1 all'ultimo oggetto dell'array , spostandoli all'indice precedente. In tal caso, si tratta effettivamente delle prestazioni O (N).)

Quindi che si fa? Dipende dalla situazione. Se hai un array di grandi dimensioni e ti aspetti solo un numero limitato di duplicati, la de-duplicazione sul posto funzionerà perfettamente e ti farà risparmiare la creazione di un array duplicato. Se hai un array in cui ti aspetti molti duplicati, probabilmente costruire un array separato e non duplicato è probabilmente l'approccio migliore. Il take-away qui è che la notazione big-O descrive solo le caratteristiche di un algoritmo, non ti dirà definitivamente quale sia la migliore per una data circostanza.


20

Se scegli come target iOS 5+ (ciò che copre l'intero mondo iOS), è meglio utilizzarlo NSOrderedSet. Rimuove i duplicati e mantiene l'ordine del tuo NSArray.

Basta fare

NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:yourArray];

Ora puoi riconvertirlo in un NSArray univoco

NSArray *uniqueArray = orderedSet.array;

O semplicemente utilizzare l'orderedSet perché ha gli stessi metodi come un NSArray come objectAtIndex:, firstObjecte così via.

Un controllo di appartenenza con containsè ancora più veloce NSOrderedSetdi quanto sarebbe su unNSArray

Per ulteriori informazioni, consultare il riferimento NSOrderedSet


Questo ha ottenuto il mio voto, li ho letti tutti ed è la risposta migliore. Non riesco a credere che la risposta migliore sia un ciclo manuale. Oh, ora hanno copiato questa risposta.
Malhal,

19

Disponibile in OS X v10.7 e versioni successive.

Se sei preoccupato per l'ordine, il modo giusto per farlo

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

Ecco il codice per rimuovere i valori duplicati da NSArray in Order.


1
allObjects dovrebbe essere array
malhal il

7

bisogno di ordine

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

o non è necessario l'ordine

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

3

Qui ho rimosso i valori dei nomi duplicati da mainArray e archiviare i risultati in 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

Si noti che se si dispone di un array ordinato, non è necessario verificare con ogni altro elemento dell'array, solo l'ultimo elemento. Questo dovrebbe essere molto più veloce del confronto con tutti gli oggetti.

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

Sembra che le NSOrderedSetrisposte suggerite richiedano anche molto meno codice, ma se non riesci a usarne uno NSOrderedSetper qualche motivo e hai un array ordinato, credo che la mia soluzione sarebbe la più veloce. Non sono sicuro di come si confronta con la velocità delle NSOrderedSetsoluzioni. Nota anche che il mio codice sta verificando isEqualToString:, quindi la stessa serie di lettere non apparirà più di una volta newArray. Non sono sicuro se le NSOrderedSetsoluzioni rimuoveranno i duplicati in base al valore o in base alla posizione della memoria.

Il mio esempio presuppone che sortedSourceArraycontenga solo NSStrings, solo NSMutableStrings o un mix dei due. Se sortedSourceArrayinvece contiene solo NSNumbers o solo NSDates, puoi sostituirlo

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

con

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

e dovrebbe funzionare perfettamente. Se sortedSourceArraycontiene un mix di NSStrings, NSNumbers e / o NSDates, probabilmente si bloccherà.


1

C'è un operatore oggetti KVC che offre una soluzione più elegante uniquearray = [yourarray valueForKeyPath:@"@distinctUnionOfObjects.self"];Ecco una categoria NSArray .


1

Un altro modo semplice per provare che non aggiungerà valore duplicato prima di aggiungere un oggetto nella matrice: -

// Supponiamo che mutableArray sia allocato e inizializzato e contenga un valore

if (![yourMutableArray containsObject:someValue])
{
   [yourMutableArray addObject:someValue];
}

1

Rimuovere i valori duplicati da NSMutableArray in Objective-C

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

0

Ecco il codice per rimuovere i valori duplicati dall'array NSMutable. .it funzionerà per te. myArray è il tuo array mutabile che desideri rimuovere i valori duplicati.

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

Usare Orderedsetfarà il trucco. Ciò manterrà i duplicati rimossi dall'array e manterrà l'ordine che i set normalmente non fanno


-3

usa questo semplice codice:

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

poiché nsset non consente valori duplicati e tutti gli oggetti restituiscono un array


Ha funzionato per me. Tutto quello che devi fare è ordinare nuovamente il tuo NSArray poiché NSSet restituisce un NSArray non ordinato.
Lindinax,

O semplicemente usa il NSOrderedSetrospo NSSet.
Lindinax,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.