NSArray: удалить объекты с повторяющимися свойствами
У меня есть NSMutableArray, который содержит несколько пользовательских объектов. Два объекта имеют такие же свойства, как название и автор. Я хочу удалить дубликат объекта и оставить другой.
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];
Поскольку они НЕ являются одним и тем же объектом, но имеют только дублирующие свойства, как удалить дубликат?
Ответы
Ответ 1
Вы можете создать HashSet, и в качестве цикла вы можете добавить конкатенированный набор "title + author" в HashSet (NSMutableSet). Когда вы прибудете к каждому элементу, если HashSet содержит ваш ключ, удалите его или не копируйте (удаляя или создавая копию без дубликатов).
Это делает порядок n (1 цикл)
Здесь класс NSMutableSet:
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSMutableSet_Class/Reference/NSMutableSet.html#//apple_ref/occ/cl/NSMutableSet
EDIT с кодом:
Мяч кода - это один цикл.
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(@"****Filtered****");
print(items);
[pool drain];
return 0;
}
Здесь вывод:
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] ****Filtered****
Craplet[11991:707] Developer/John Smith
Craplet[11991:707] Writer/Steve Johnson
Ответ 2
Вы можете использовать уникальность NSSet
для получения отдельных элементов из исходного массива. Если у вас есть исходный код для Assest
, вам необходимо переопределить методы hash
и isEqual:
в классе 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
Затем для реализации:
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);
И если вам нужен массив в конце, вы можете просто вызвать [distinctItems allObjects]
Ответ 3
Во-первых, я бы переопределил метод isEqual: для Asset следующим образом:
-(BOOL)isEqual:(Asset *)otherAsset {
return [self.title isEqual:otherAsset.title] && [self.author isEqual:otherAsset.author];
}
Затем, если вы хотите избежать размещения дубликатов в массиве в первую очередь:
NSUInteger idx = [items indexOfObject:asset]; // tests objects for equality using isEqual:
if (idx == NSNotFound) [items addObject:asset];
Если массив уже содержит дубликаты, то любой найденный алгоритм имеет время работы, которое уже хуже, чем линейное, но я думаю, что создание нового массива и добавление уникальных элементов, таких как выше, является лучшим алгоритмом. Что-то вроде этого:
NSMutableArray *itemsWithUniqueElements = [NSMutableArray arrayWithCapacity:[items count]];
for (Asset *anAsset in items) {
if ([items indexOfObject:anAsset] == NSNotFound)
[itemsWithUniqueElements addObject:anAsset];
}
[items release];
items = [itemsWithUniqueElements retain];
В худшем случае (все элементы уже уникальны) число итераций:
1 + 2 + 3 + ... + n = n * (n+1) / 2
Это все еще O (n ^ 2), но немного лучше алгоритма @Justin Meiners. Без обид!:)
Ответ 4
Это один из способов сделать это
:
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];
}
Ответ 5
Если вы хотите, чтобы ваши пользовательские подклассы NSObject считались равными, когда их имена равны, вы можете реализовать isEqual:
и hash
. Это позволит вам добавить объекты к NSSet
/NSMutableSet
(набор различных объектов).
Затем вы можете легко создать отсортированный NSArray
с помощью метода NSSet
sortedArrayUsingDescriptors:
.
MikeAsh написал довольно солидную статью о реализации пользовательского равенства: Friday Q & A 2010-06-18: Реализация равенства и хищения