¿La mejor manera de eliminar valores duplicados de NSMutableArray en Objective-C?

147

¿La mejor manera de eliminar valores duplicados ( NSString) NSMutableArrayen Objective-C?

¿Es esta la forma más fácil y correcta de hacerlo?

uniquearray = [[NSSet setWithArray:yourarray] allObjects];
Teo Choong Ping
fuente
55
Es posible que desee aclarar si desea eliminar referencias al mismo objeto exacto, o también aquellos que son objetos distintos pero que tienen los mismos valores para cada campo.
Amagrammer
¿No hay una manera de hacer esto sin crear ninguna copia de la matriz?
hfossli
De esta manera es bastante fácil y quizás lo mejor. Pero, por ejemplo, no funcionará para mi caso: los elementos de la matriz no son duplicados completos y deben ser comparados por una propiedad.
Vyachaslav Gerchicov
Prueba esto por una vez ... stackoverflow.com/a/38007095/3908884
Conoce a Doshi el

Respuestas:

242

Su NSSetenfoque es el mejor si no está preocupado por el orden de los objetos, pero, de nuevo, si no está preocupado por el orden, ¿por qué no los está almacenando NSSetpara empezar?

Escribí la respuesta a continuación en 2009; en 2011, Apple agregó NSOrderedSeta iOS 5 y Mac OS X 10.7. Lo que había sido un algoritmo ahora son dos líneas de código:

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

Si le preocupa el pedido y está ejecutando iOS 4 o anterior, repita una copia de la matriz:

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
fuente
53
Si necesita unicidad Y orden, simplemente use [NSOrderedSet orderedSetWithArray:array];Puede recuperar una matriz a través de array = [orderedSet allObjects];o simplemente usar NSOrderedSets en lugar de NSArrayen primer lugar.
Regexident
10
La solución de @ Regexident es ideal. Solo necesito reemplazar [orderedSet allObjects]con [orderedSet array]!
entrada
Nice One;) Me gusta la respuesta que hace que el desarrollador copie y pegue sin muchas modificaciones, esta es la respuesta que le gustará a todos los desarrolladores de iOS;) @ abo3atef
Abo3atef
Gracias pero deberías arreglar tu ejemplo. Motivo: generalmente tenemos NSArrayy debemos crear temp NSMutableArray. En su ejemplo, usted trabaja al revés
Vyachaslav Gerchicov
Alguien sabe cuál es el mejor fin de eliminar duplicados es este método (usando NSSet) o @Simon Whitaker enlace evitan antes de añadir valor duplicado que es la forma eficiente?
Mathi Arasan
78

Sé que esta es una vieja pregunta, pero hay una forma más elegante de eliminar duplicados en un NSArray si no te importa el pedido .

Si usamos operadores de objetos de la codificación de valor clave , podemos hacer esto:

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

Como también señaló AnthoPak , es posible eliminar duplicados en función de una propiedad. Un ejemplo sería:@distinctUnionOfObjects.name

Tiago Almeida
fuente
3
¡Sí, esto es lo que yo uso también! Este es un enfoque muy poderoso, que muchos desarrolladores de iOS no conocen.
Lefteris
1
Me sorprendió cuando supe que esto podría ser posible. Pensé que muchos desarrolladores de iOS no podían saber esto, por eso decidí agregar esta respuesta :)
Tiago Almeida
12
Esto no mantiene el orden de los objetos.
Rudolf Adamkovič
2
Sí, rompe el orden.
Rostyslav Druzhchenko
Tenga en cuenta que también se puede usar @distinctUnionOfObjects.propertypara eliminar duplicados por propiedad de una matriz de objetos personalizados. Por ejemplo@distinctUnionOfObjects.name
AnthoPak
47

Sí, usar NSSet es un enfoque sensato.

Para agregar a la respuesta de Jim Puls, aquí hay un enfoque alternativo para eliminar los duplicados mientras se mantiene el orden:

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

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

Es esencialmente el mismo enfoque que el de Jim, pero copia elementos únicos en una nueva matriz mutable en lugar de eliminar duplicados del original. Esto hace que sea un poco más eficiente en memoria en el caso de una gran matriz con muchos duplicados (no es necesario hacer una copia de toda la matriz), y en mi opinión es un poco más legible.

Tenga en cuenta que, en cualquier caso, verificar si un elemento ya está incluido en la matriz de destino (usando containsObject:en mi ejemplo, o indexOfObject:inRange:en el de Jim) no escala bien para grandes matrices. Esas comprobaciones se ejecutan en tiempo O (N), lo que significa que si duplica el tamaño de la matriz original, cada comprobación tardará el doble de tiempo en ejecutarse. Como está haciendo la verificación de cada objeto en la matriz, también ejecutará más de esas verificaciones más costosas. El algoritmo general (tanto el mío como el de Jim) se ejecuta en tiempo O (N 2 ), que se vuelve caro rápidamente a medida que crece la matriz original.

Para reducir el tiempo a O (N), puede usar a NSMutableSetpara almacenar un registro de elementos ya agregados a la nueva matriz, ya que las búsquedas de NSSet son O (1) en lugar de O (N). En otras palabras, verificar si un elemento es miembro de un NSSet toma el mismo tiempo, independientemente de cuántos elementos haya en el conjunto.

El código que usa este enfoque se vería así:

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

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

Sin embargo, esto todavía parece un poco derrochador; todavía estamos generando una nueva matriz cuando la pregunta dejó en claro que la matriz original es mutable, por lo que deberíamos poder eliminarla en su lugar y ahorrar algo de memoria. Algo como esto:

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

ACTUALIZACIÓN : Yuri Niyazov señaló que mi última respuesta en realidad se ejecuta en O (N 2 ) porque removeObjectAtIndex:probablemente se ejecuta en tiempo O (N).

(Dice "probablemente" porque no sabemos con certeza cómo se implementa; pero una posible implementación es que después de eliminar el objeto en el índice X, el método recorre cada elemento desde el índice X + 1 hasta el último objeto en la matriz , moviéndolos al índice anterior. Si ese es el caso, entonces ese es el rendimiento O (N).)

¿Entonces lo que hay que hacer? Depende de la situación. Si tiene una matriz grande y solo espera una pequeña cantidad de duplicados, la desduplicación en el lugar funcionará bien y le ahorrará tener que construir una matriz duplicada. Si tiene una matriz donde espera muchos duplicados, entonces construir una matriz separada y duplicada es probablemente el mejor enfoque. La conclusión aquí es que la notación big-O solo describe las características de un algoritmo, no le dirá definitivamente cuál es el mejor para cualquier circunstancia.

Simon Whitaker
fuente
20

Si está apuntando a iOS 5+ (lo que cubre todo el mundo de iOS), mejor uso NSOrderedSet. Elimina duplicados y conserva el orden de su NSArray.

Solo haz

NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:yourArray];

Ahora puede volver a convertirlo en una matriz NSA única

NSArray *uniqueArray = orderedSet.array;

O simplemente use el setSet ordenado porque tiene los mismos métodos como un NSArray objectAtIndex:, firstObjecty así sucesivamente.

Una verificación de membresía containses aún más rápida en lo NSOrderedSetque sería en unNSArray

Para obtener más información, consulte la referencia NSOrderedSet

lukaswelte
fuente
Esto obtuvo mi voto, los leí todos y es la mejor respuesta. No puedo creer que la respuesta principal sea un bucle manual. Oh, ahora han copiado esta respuesta.
malhal
19

Disponible en OS X v10.7 y posterior.

Si está preocupado por el pedido, la forma correcta de hacerlo

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

Aquí está el código para eliminar valores duplicados de NSArray en orden.

Sultania
fuente
1
allObjects debe ser array
malhal
7

necesita orden

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

o no necesita orden

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

Aquí eliminé valores de nombre duplicados de mainArray y almacené el resultado en 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
fuente
1

Tenga en cuenta que si tiene una matriz ordenada, no necesita verificar con cada otro elemento de la matriz, solo con el último elemento. Esto debería ser mucho más rápido que verificar todos los artículos.

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

Parece que las NSOrderedSetrespuestas que también se sugieren requieren mucho menos código, pero si NSOrderedSetpor alguna razón no puede usar una y tiene una matriz ordenada, creo que mi solución sería la más rápida. No estoy seguro de cómo se compara con la velocidad de las NSOrderedSetsoluciones. También tenga en cuenta que mi código está comprobando isEqualToString:, por lo que la misma serie de letras no aparecerá más de una vez newArray. No estoy seguro de si las NSOrderedSetsoluciones eliminarán duplicados según el valor o la ubicación de la memoria.

Mi ejemplo supone que sortedSourceArraycontiene solo NSStrings, solo NSMutableStrings o una mezcla de los dos. Si en su sortedSourceArraylugar contiene solo NSNumbers o solo NSDates, puede reemplazar

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

con

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

y debería funcionar perfectamente. Si sortedSourceArraycontiene una mezcla de NSStrings, NSNumbersy / o NSDates, probablemente se bloqueará.

GeneralMike
fuente
1

Hay un operador de objetos KVC que ofrece una solución más elegante. uniquearray = [yourarray valueForKeyPath:@"@distinctUnionOfObjects.self"];Aquí hay una categoría NSArray .

Peter
fuente
1

Una forma más simple de probar que no agregará valor duplicado antes de agregar un objeto en la matriz: -

// Supongamos que mutableArray está asignado e inicializado y contiene algún valor

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

Eliminar valores duplicados de NSMutableArray en Objective-C

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

Aquí está el código para eliminar valores duplicados de NSMutable Array. Funcionará para usted. myArray es su matriz mutable que desea eliminar valores duplicados.

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
fuente
0

Usar Orderedsethará el truco. Esto mantendrá la eliminación de duplicados de la matriz y mantendrá el orden que normalmente no funciona

abhi
fuente
-3

solo usa este código simple:

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

dado que nsset no permite valores duplicados y todos los objetos devuelven una matriz

Dinesh619
fuente
Trabajó para mi. Todo lo que tiene que hacer es ordenar su NSArray nuevamente ya que NSSet devuelve un NSArray sin clasificar.
lindinax
O simplemente use en NSOrderedSetlugar de NSSet.
lindinax