Create a NSSet from NSArray based on property

拟墨画扇 提交于 2019-12-01 00:59:29

问题


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.


回答1:


NSSet *distinctSet = [NSSet setWithArray:[array valueForKeyPath:@"@distinctUnionOfObjects.property"]];



回答2:


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



回答3:


NSMutableSet* classes = [[NSMutableSet alloc] init];
NSMutableSet* actualSet = [[NSMutableSet alloc] init];

for(id object in array) {
    if([classes containsObject:[object class]] == NO) {
        [classes addObject:[object class]];
        [actualSet addObject:object];
    }        
}



回答4:


You would use:

NSSet* mySetWithUniqueItems= [NSSet setWithArray: yourArray];

This should work regardless of the type of objects in your array and would populate the NSSet with only one occurence of any duplicate objects in your array.

I hope this helps.

Update: Next best thing: is use concatenation of class name and object property first then use the above method.

self.concatenatedArray=[NSMutableArray arrayWithCapacity:4];

for (TheClass* object in self.myArray)
    [self.concatenatedArray addObject:[NSString stringWithFormat:@"%@-%@",[object class], object.theProperty]];

self.mySet=[NSSet setWithArray:self.concatenatedArray];

I am not sure what you will use the NSSet output for but you can probably modify the concatenation elements to have the information you need in the NSSet output.




回答5:


I have created a simple library, called Linq to ObjectiveC, which is a collection of methods that makes this kind of problem much easier to solve. In your case you need the Linq-to-ObjectiveC distinct method:

NSSet* dictionary = [NSSet setWithArray:[sourceArray distinct:^id(id item) {
    return [item type] ;
}]];

This returns a set where each item has a distinct type property.



来源:https://stackoverflow.com/questions/15262198/create-a-nsset-from-nsarray-based-on-property

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!