NSArray: eliminar objects con properties duplicadas

Tengo un NSMutableArray que contiene algunos objects personalizados. Dos de los objects tienen las mismas properties como título y autor. Quiero eliminar el object duplicado y abandonar el otro.

Asset *asset; NSMutableArray *items = [[[NSMutableArray alloc] init] autorelease]; // First asset = [[Asset alloc] init]; asset.title = @"Developer"; asset.author = @"John Smith"; [items addObject:asset]; [asset release]; // Second asset = [[Asset alloc] init]; asset.title = @"Writer"; asset.author = @"Steve Johnson"; [items addObject:asset]; [asset release]; // Third asset = [[Asset alloc] init]; asset.title = @"Developer"; asset.author = @"John Smith"; [items addObject:asset]; [asset release]; 

Dado que NO son el mismo object, sino que solo tienen properties duplicadas, ¿cómo puedo eliminar el duplicado?

Podrías crear un HashSet y como loop, podrías agregar un set concatenado "title + author" al HashSet (NSMutableSet). A medida que llega a cada elemento, si HashSet contiene su key, elimínela o no copie (borrando o creando una copy sin duplicates).

Eso hace que ordere n (1 bucle)

Aquí está la class NSMutableSet:

http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSMutableSet_Class/Reference/NSMutableSet.html#//apple_ref/occ/cl/NSMutableSet

EDITAR con el código:

La carne del código es el único bucle.

 void print(NSMutableArray *assets) { for (Asset *asset in assets) { NSLog(@"%@/%@", [asset title], [asset author]); } } int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; // // Create the initial data set // Asset *asset; NSMutableArray *items = [[[NSMutableArray alloc] init] autorelease]; // First asset = [[Asset alloc] init]; asset.title = @"Developer"; asset.author = @"John Smith"; [items addObject:asset]; [asset release]; // Second asset = [[Asset alloc] init]; asset.title = @"Writer"; asset.author = @"Steve Johnson"; [items addObject:asset]; [asset release]; // Third asset = [[Asset alloc] init]; asset.title = @"Developer"; asset.author = @"John Smith"; [items addObject:asset]; [asset release]; NSLog(@"****Original****"); print(items); // // filter the data set in one pass // NSMutableSet *lookup = [[NSMutableSet alloc] init]; for (int index = 0; index < [items count]; index++) { Asset *curr = [items objectAtIndex:index]; NSString *identifier = [NSString stringWithFormat:@"%@/%@", [curr title], [curr author]]; // this is very fast constant time lookup in a hash table if ([lookup containsObject:identifier]) { NSLog(@"item already exists. removing: %@ at index %d", identifier, index); [items removeObjectAtIndex:index]; } else { NSLog(@"distinct item. keeping %@ at index %d", identifier, index); [lookup addObject:identifier]; } } NSLog(@"****Filtenetworking****"); print(items); [pool drain]; return 0; } 

Aquí está el resultado:

 Craplet[11991:707] ****Original**** Craplet[11991:707] Developer/John Smith Craplet[11991:707] Writer/Steve Johnson Craplet[11991:707] Developer/John Smith Craplet[11991:707] distinct item. keeping Developer/John Smith at index 0 Craplet[11991:707] distinct item. keeping Writer/Steve Johnson at index 1 Craplet[11991:707] item already exists. removing: Developer/John Smith at index 2 Craplet[11991:707] ****Filtenetworking**** Craplet[11991:707] Developer/John Smith Craplet[11991:707] Writer/Steve Johnson 

Puede usar la exclusividad de un NSSet para get elementos distintos de su matriz original. Si tiene el código fuente de Assest , deberá anular el hash y el método isEqual: en la class de Asset .

 @interface Asset : NSObject @property(copy) NSString *title, *author; @end @implementation Asset @synthesize title, author; -(NSUInteger)hash { NSUInteger prime = 31; NSUInteger result = 1; result = prime * result + [self.title hash]; result = prime * result + [self.author hash]; return result; } -(BOOL)isEqual:(id)object { return [self.title isEqualToString:[object title]] && [self.author isEqualToString:[object author]]; } - (void)dealloc { [title release]; [author release]; [super dealloc]; } @end 

Luego para implementar:

 Asset *asset; NSMutableArray *items = [[[NSMutableArray alloc] init] autorelease]; // First asset = [[Asset alloc] init]; asset.title = @"Developer"; asset.author = @"John Smith"; [items addObject:asset]; [asset release]; // Second asset = [[Asset alloc] init]; asset.title = @"Writer"; asset.author = @"Steve Johnson"; [items addObject:asset]; [asset release]; // Third asset = [[Asset alloc] init]; asset.title = @"Developer"; asset.author = @"John Smith"; [items addObject:asset]; [asset release]; NSLog(@"Items: %@", items); NSSet *distinctItems = [NSSet setWithArray:items]; NSLog(@"Distinct: %@", distinctItems); 

Y si necesita una matriz al final, puede llamar a [distinctItems allObjects]

Primero, anularía el método isEqual: para Asset así:

 -(BOOL)isEqual:(Asset *)otherAsset { return [self.title isEqual:otherAsset.title] && [self.author isEqual:otherAsset.author]; } 

Entonces, si quiere evitar colocar duplicates en la matriz en primer lugar:

 NSUInteger idx = [items indexOfObject:asset]; // tests objects for equality using isEqual: if (idx == NSNotFound) [items addObject:asset]; 

Si la matriz ya contiene duplicates, cualquier algorithm que los encuentre tiene un time de ejecución ya peor que lineal, pero creo que crear un nuevo array y solo agregar elementos únicos como el anterior es el mejor algorithm. Algo como esto:

 NSMutableArray *itemsWithUniqueElements = [NSMutableArray arrayWithCapacity:[items count]]; for (Asset *anAsset in items) { if ([items indexOfObject:anAsset] == NSNotFound) [itemsWithUniqueElements addObject:anAsset]; } [items release]; items = [itemsWithUniqueElements retain]; 

En el peor de los casos (todos los elementos ya son únicos), el número de iteraciones es:

 1 + 2 + 3 + ... + n = n * (n+1) / 2 

Lo cual sigue siendo O (n ^ 2) pero es ligeramente mejor que el algorithm de @Justin Meiners. ¡Sin ofender! 🙂

Esta es una forma de hacerlo:

 NSMutableArray* toRemove = [NSMutableArray array]; for (Asset* asset1 in items) { for (Asset* asset2 in items) { if (asset1 != asset2) { if ([asset1.title isEqualToString:asset2.title] && [asset1.author isEqualToString:asset2.author]) { [toRemove addObject:asset2]; } } } } for (Asset* deleted in toRemove) { [items removeObject:toRemove]; } 

Si desea que sus subclasss personalizadas de NSObject se consideren iguales cuando sus nombres son iguales, puede implementar isEqual: y hash . Esto le permitirá agregar los objects a un NSSet / NSMutableSet (un set de objects distintos).

A continuación, puede crear fácilmente un NSArray orderado utilizando el NSSet sortedArrayUsingDescriptors: .

MikeAsh escribió una pieza bastante sólida sobre la implementación de la igualdad personalizada: viernes Q & A 2010-06-18: Implementación de Igualdad y Hashing