I have an array with custom objects. Each array item has a field named "name". Now I want to remove duplicate entries based on this name value.
How should I go about achieving this?
I have an array with custom objects. Each array item has a field named "name". Now I want to remove duplicate entries based on this name value.
How should I go about achieving this?
You might have to actually write this filtering method yourself:
@interface NSArray (CustomFiltering)
@end
@implementation NSArray (CustomFiltering)
- (NSArray *) filterObjectsByKey:(NSString *) key {
NSMutableSet *tempValues = [[NSMutableSet alloc] init];
NSMutableArray *ret = [NSMutableArray array];
for(id obj in self) {
if(! [tempValues containsObject:[obj valueForKey:key]]) {
[tempValues addObject:[obj valueForKey:key]];
[ret addObject:obj];
}
}
[tempValues release];
return ret;
}
@end
NSMutableSet
instead of a NSMutableArray
for lookups, the performance boost from a hash lookup is quite allot better than a linear search. –
Nigrescent [NSMutableSet set]
instead of alloc/init+release. Second, you should use -member:
instead of -containsObject:
. -containsObject:
is documented as returning whether the given object is present in the set, without defining "present". It is reasonable to assume it uses pointer equality. -member:
is documented as using -isEqual:
, which is what you actually want to test with. –
Faxen alloc/init
because I didn't want the set to be sitting around in the autorelease pool. The code only actually needs the set to be around while the loop is executing, so this way it can be released from memory at the earliest possible time. –
Yam I do not know of any standard way to to do this provided by the frameworks. So you will have to do it in code. Something like this should be doable:
NSArray* originalArray = ... // However you fetch it
NSMutableSet* existingNames = [NSMutableSet set];
NSMutableArray* filteredArray = [NSMutableArray array];
for (id object in originalArray) {
if (![existingNames containsObject:[object name]]) {
[existingNames addObject:[object name]];
[filteredArray addObject:object];
}
}
You might have to actually write this filtering method yourself:
@interface NSArray (CustomFiltering)
@end
@implementation NSArray (CustomFiltering)
- (NSArray *) filterObjectsByKey:(NSString *) key {
NSMutableSet *tempValues = [[NSMutableSet alloc] init];
NSMutableArray *ret = [NSMutableArray array];
for(id obj in self) {
if(! [tempValues containsObject:[obj valueForKey:key]]) {
[tempValues addObject:[obj valueForKey:key]];
[ret addObject:obj];
}
}
[tempValues release];
return ret;
}
@end
NSMutableSet
instead of a NSMutableArray
for lookups, the performance boost from a hash lookup is quite allot better than a linear search. –
Nigrescent [NSMutableSet set]
instead of alloc/init+release. Second, you should use -member:
instead of -containsObject:
. -containsObject:
is documented as returning whether the given object is present in the set, without defining "present". It is reasonable to assume it uses pointer equality. -member:
is documented as using -isEqual:
, which is what you actually want to test with. –
Faxen alloc/init
because I didn't want the set to be sitting around in the autorelease pool. The code only actually needs the set to be around while the loop is executing, so this way it can be released from memory at the earliest possible time. –
Yam I know this is an old question but here is another possibility, depending on what you need.
Apple does provide a way to do this -- Key-Value Coding Collection Operators.
Object operators let you act on a collection. In this case, you want:
@distinctUnionOfObjects
The @distinctUnionOfObjects operator returns an array containing the distinct objects in the property specified by the key path to the right of the operator.
NSArray *distinctArray = [arrayWithDuplicates
valueForKeyPath:@"@distinctUnionOfObjects.name"];
In your case, though, you want the whole object. So what you'd have to do is two-fold:
1) Use @distinctUnionOfArrays
instead. E.g. If you had these custom objects coming from other collections, use @distinctUnionOfArray.myCollectionOfObjects
2) Implement isEqual:
on those objects to return if their .name's are equal
I'm going to get flak for this...
You can convert your array into a dictionary. Not sure how efficient this is, depends on the implementation and comparison call, but it does use a hash map.
//Get unique entries
NSArray *myArray = @[@"Hello", @"World", @"Hello"];
NSDictionary *uniq = [NSDictionary dictionaryWithObjects:myArray forKeys:myArray];
NSLog(@"%@", uniq.allKeys);
If you'd like your custom NSObject subclasses to be considered equal when their names are equal you may implement isEqual:
and hash
. This will allow you to add of the objects to an NSSet
/NSMutableSet
(a set of distinct objects).
You may then easily create a sorted NSArray
by using NSSet
's sortedArrayUsingDescriptors:
method.
MikeAsh wrote a pretty solid piece about implementing custom equality: Friday Q&A 2010-06-18: Implementing Equality and Hashing
If you are worried about the order
NSArray * newArray =
[[NSOrderedSet orderedSetWithArray:oldArray] array]; **// iOS 5.0 and later**
It is quite simple in one line
NSArray *duplicateList = ...
If you don't care about elements order then (unordered)
NSArray *withoutDUP1 = [[NSSet setWithArray:duplicateList] allObjects];
Keep the elements in order then (ordered)
NSArray *withoutDUP2 = [[NSOrderedSet orderedSetWithArray:duplicateList] array];
Implement isEqual to make your objects comparable:
@interface SomeObject (Equality)
@end
@implementation SomeObject (Equality)
- (BOOL)isEqual:(SomeObject*)other
{
return self.hash == other.hash;
}
- (NSUInteger)hash
{
return self.name;///your case
}
@end
How to use:
- (NSArray*)distinctObjectsFromArray:(NSArray*)array
{
return [array valueForKeyPath:@"@distinctUnionOfObjects.self"];
}
© 2022 - 2024 — McMap. All rights reserved.