6

Consider the arrays below. The arrays contain objects of type 'Alpha'. We only care about the property username which is of type NSString.

NSArray *some_usernames = @[ <multiple values of type Alpha> ]
NSArray *all_usernames = @[ <multiple values of type Alpha> ]

I basically want a list of all the usernames that are not in the array some_usernames, i.e.

NSArray *remaining_usernames = @[ <all_usernames but not in some_usernames> ];

The way I would intend to do is:

NSPredicates *predicates;
for (Alpha *alpha in some_usernames)
{
    predicate = [predicate with @"username != %@", alpha.username];
    predicates.add(predicate)
}

create compound predicate
filter all_usernames

But this feels like a bad way of doing this. Is there a way to do this in two lines? I have seen it before but I can't point to the code reference anymore.

p0lAris
  • 4,750
  • 8
  • 45
  • 80

2 Answers2

13
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"not (self.username IN %@)", [some_usernames valueForKey:@"username"]];
NSArray *remaining_usernames = [all_usernames filteredArrayUsingPredicate:predicate];

complete example

@interface Alpha : NSObject
@property (nonatomic, copy) NSString *username;
-(instancetype) initWithUsername:(NSString *)username;
@end

@implementation Alpha
-(instancetype) initWithUsername:(NSString *)username
{
    self = [super init];
    if (self) {
        self.username = username;
    }
    return self;
}

-(NSString *)description{
    return [NSString stringWithFormat:@"%@: %@", NSStringFromClass([self class]), self.username];
}
@end


NSArray *all_usernames = @[[[Alpha alloc] initWithUsername:@"a"], [[Alpha alloc] initWithUsername:@"b"], [[Alpha alloc] initWithUsername:@"z"], [[Alpha alloc] initWithUsername:@"f"], [[Alpha alloc] initWithUsername:@"e"]];
NSArray *some_usernames = @[[[Alpha alloc] initWithUsername:@"b"], [[Alpha alloc] initWithUsername:@"f"]];

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"not (self.username  IN %@)", [some_usernames valueForKey:@"username"]];
NSArray *remaining_usernames = [all_usernames filteredArrayUsingPredicate:predicate];

NSLog(@"%@", remaining_usernames);

prints

(
    "Alpha: a",
    "Alpha: z",
    "Alpha: e"
)
vikingosegundo
  • 52,040
  • 14
  • 137
  • 178
  • Is that a replacement for `@"username != %@"? If so, thanks. But I would hate to use my method here. – p0lAris Jan 07 '15 at 02:09
  • Thanks a lot it works. You know you could have taken your time. I was banging my head throughout your edits. :P Thanks a lot though. :) – p0lAris Jan 07 '15 at 02:29
1

I want to add another answer:

If the ordering of the objects isn't needed (and — most likely — equal objects unwanted) you could instead of using predicate filtering on arrays use Sets and set arithmetic. To do so we must teach Alpha what equality means and provide a hash method. In this case we just use NSStrings implementation:

@implementation Alpha
-(instancetype) initWithUsername:(NSString *)username
{
    self = [super init];
    if (self) {
        self.username = username;
    }
    return self;
}

-(NSString *)description{
    return [NSString stringWithFormat:@"%@: %@", NSStringFromClass([self class]), self.username];
}

-(BOOL)isEqual:(id)object
{
    return [self.username isEqual:[object username]];
}

-(NSUInteger)hash
{
    return [self.username hash];
}

@end



NSArray *all_usernames = @[[[Alpha alloc] initWithUsername:@"a"],
                           [[Alpha alloc] initWithUsername:@"b"],
                           [[Alpha alloc] initWithUsername:@"z"],
                           [[Alpha alloc] initWithUsername:@"f"],
                           [[Alpha alloc] initWithUsername:@"e"]];

NSArray *some_usernames = @[[[Alpha alloc] initWithUsername:@"b"],
                            [[Alpha alloc] initWithUsername:@"f"]];

NSSet *allSet = [NSSet setWithArray:all_usernames];
NSSet *someSet = [NSSet setWithArray:some_usernames];

NSMutableSet *remainingSet = [allSet mutableCopy];
[remainingSet minusSet:someSet];

NSLog(@"%@", remainingSet);

prints

{(
    Alpha: z,
    Alpha: e,
    Alpha: a
)}

This code should be much faster for more data. Please watch WWDC 2013: Designing Code for Performance

vikingosegundo
  • 52,040
  • 14
  • 137
  • 178