How does one create a NSSet
of objects from an array based on a property.
e.g. Array of objects, each with a strong reference to a type
property, and multiple occurrences of each type exist in the array. How can this be turned into an NSSet
holding a single object of each type.
NSSet *distinctSet = [NSSet setWithArray:[array valueForKeyPath:@"@distinctUnionOfObjects.property"]];
A dictionary essentially has this functionality already. Its keys are a set, so you can create the dictionary to hold the objects, keyed by whatever attribute you're interested in:
[NSDictionary dictionaryWithObjects:arrayOfObjects
forKeys:[arrayOfObjects valueForKey:theAttribute]];
If you ask the dictionary for allValues
now, you have only one object for each attribute. I should mention that with this procedure, the later objects will be kept in favor of earlier. If the order of your original array is significant, reverse it before creating the dictionary.
You can't actually put those objects into an NSSet
, because the NSSet
will use the objects' isEqual:
and hash
methods to determine whether they should be members, rather than the key attribute (of course, you can override these methods if this is your own class, but that would likely interfere with their behavior in other collections).
If you really really feel that you need a set, you will have to write your own class. You can subclass NSSet
, but conventional wisdom is that composition of Cocoa collections is far easier than subclassing. Essentially, you write a class which covers any set methods you're interested in. Here's a (quite incomplete and totally untested) sketch:
@interface KeyedMutableSet : NSObject
/* This selector is performed on any object which is added to the set.
* If the result already exists, then the object is not added.
*/
@property (assign, nonatomic) SEL keySEL;
- (id)initWithKeySEL:(SEL)keySEL;
- (id)initWithArray:(NSArray *)initArray usingKeySEL:(SEL)keySEL;
- (void)addObject:(id)obj;
- (NSArray *)allObjects;
- (NSArray *)allKeys;
- (BOOL)containsObject:(id)obj;
- (NSUInteger)count;
-(void)enumerateObjectsUsingBlock:(void (^)(id, BOOL *))block;
// And so on...
@end
#import "KeyedMutableSet.h"
@implementation KeyedMutableSet
{
NSMutableArray * _objects;
NSMutableSet * _keys;
}
- (id)initWithKeySEL:(SEL)keySEL
{
return [self initWithArray:nil usingKeySEL:keySEL];
}
- (id)initWithArray:(NSArray *)initArray usingKeySEL:(SEL)keySEL
{
self = [super init];
if( !self ) return nil;
_keySEL = keySEL;
_objects = [NSMutableArray array];
_keys = [NSMutableSet set];
for( id obj in initArray ){
[self addObject:obj];
}
return self;
}
- (void)addObject:(id)obj
{
id objKey = [obj performSelector:[self keySEL]];
if( ![keys containsObject:objKey] ){
[_keys addObject:objKey];
[_objects addObject:obj];
}
}
- (NSArray *)allObjects
{
return _objects;
}
- (NSArray *)allKeys
{
return [_keys allObjects];
}
- (BOOL)containsObject:(id)obj
{
return [_keys containsObject:[obj performSelector:[self keySEL]]];
}
- (NSUInteger)count
{
return [_objects count];
}
- (NSString *)description
{
return [_objects description];
}
-(void)enumerateObjectsUsingBlock:(void (^)(id, BOOL *))block
{
for( id obj in _objects ){
BOOL stop = NO;
block(obj, &stop);
if( stop ) break;
}
}
@end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With