Create a NSSet from NSArray based on property
Asked Answered
L

5

9

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.

Laid answered 7/3, 2013 at 2:52 Comment(8)
Do you only want the first instance of each type? Or an arbitrary instance? This is not really an obvious mapping from an array to a set.With
Iterate through the array and add those objects into an NSMutableSet.Rosy
What is the type of type? Is it a string, a number, an object?Werbel
I am after a library method to get one object from each occurrence of a property value in an array of the same object type. I have written one myself to create the set, but was wondering if there was an optimized library method.Laid
I have a string property on an object. And in an array holding objects of this type, I want to extract a single instance for each different value of the string property.Laid
@Helium3, please see my comment under my question to confirm. Are you looking for the resulting NSSet to hold the different properties (of the same object) or to hold the duplicate objects that have different properties.Groupie
Yes, e.g. 4 objects in an array with their (NSString *) type properties set to @"type1", @"type2", @"type1", @"type3". Passing this array into a library method to return the 3 objects, that contain the three unique names. So rather than returning the unique strings, the objects that contain the unique strings should be returned. I wrote this method myself, but wondered if there was an optimised library method already in use.Laid
The short answer to your question is no. There is no "library" method to do what you want. Whatever method you already wrote is what you need. If your objects with the same type value returned YES from isEqual: then all you would need to do is create a set from the array and be done. But since that doesn't appear to be the case, you must write your own custom processing.Ianthe
F
8
NSSet *distinctSet = [NSSet setWithArray:[array valueForKeyPath:@"@distinctUnionOfObjects.property"]];
Fieldstone answered 7/3, 2013 at 3:5 Comment(0)
K
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
Kristoforo answered 7/3, 2013 at 20:41 Comment(0)
G
0

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.

Groupie answered 7/3, 2013 at 2:59 Comment(6)
I would like to get a set of the same object type based on unique property values.Laid
@ReyGonzales, I believe the complexity here is that the above method would create an NSSet with unique objects from the array. Helium is looking to populate the NSSet with objects that are similar at the object level but have a different property each.Groupie
Yes, e.g. 4 objects in an array with their (NSString *) type properties set to @"type1", @"type2", @"type1", @"type3". Passing this array into a library method to return the 3 objects, that contain the three unique names. So rather than returning the unique strings, the objects that contain the unique strings should be returned. I wrote this method myself, but wondered if there was an optimised library method already in use.Laid
Oh, you don't need it written. Perhaps that should be specified in your question.Paryavi
@Helium3, so your concern most likely here is not that the NSSet will contain duplicates of an instance of your class (since NSSet takes care of that automatically in my answer) but rather that you want to make sure that the resulting NSSet does not include different instances but that have the same underlying property?Groupie
Please see update to my answer, you can do it with an extra couple of lines.Groupie
P
0
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];
    }        
}
Paryavi answered 7/3, 2013 at 3:3 Comment(0)
A
0

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.

Acosta answered 8/3, 2013 at 20:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.