Houman
Houman

Reputation: 66320

How to use one array as predicate for another array?

NSArray *arrClient = [[NSArray alloc] initWithObjects:@"record 1", @"record 2", nil];
NSArray *arrServer = [[NSArray alloc] initWithObjects:@"record 1", @"record 3", nil];

On arrServer I would like to apply predicates to filter only those entries that DON'T already exist in arrClient. e.g. in this case record 1 exist in both arrays and shall be ignored, hence only an array with one entry with the "record 3" string shall be returned.

Is this possible?

UPDATE

The answers below are great. I believe I need to give a real example to verify if what I am doing makes sense after all. (I am still giving a compact version below)

Now the clientItems will be of type FTRecord (Core Data)

@interface FTRecord : NSManagedObject
...
@property (nonatomic) NSTimeInterval recordDate;

@end

@implementation FTRecord
...
@dynamic recordDate;

@end

This class below is a holder for parsing json from a REST service. Hence the serverItems we mentioned earlier will be of this type.

@interface FTjsonRecord : NSObject <JSONSerializable>
{

}

@property (nonatomic) NSDate *recordDate;

@implementation FTjsonRecord

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

- (BOOL)isEqual:(id)object
{
    if ([object isKindOfClass:[FTjsonRecord self]]) {
        FTjsonRecord *other = object;
        return [[self recordDate] isEqualToDate:[other recordDate]];
    }
    else if ([object isKindOfClass:[FTRecord self]]) {
        FTRecord *other = object;
        return [[self recordDate] isEqualToDate:[NSDate dateWithTimeIntervalSinceReferenceDate:[other recordDate]]];
    }
    else {
        return NO;
    }
}

Going with Wain's example, this seems to work fine. Now is this feasible? Keep in mind that serverItems are just temporary and only used for syncing with server, and will be thrown away. clientItems is the one that remains in place.

UPDATE 2:

This time I am trying Manu's solution:

I have created this method on my Client DBStore, which is called by the predicate. The reason I can't use containsObject is because the class types in serverItems and clientItems are not the same type.

-(BOOL)recordExistsForDate:(NSDate *)date
{
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"recordDate == %@", date];
    NSArray *arr = [allRecords filteredArrayUsingPredicate:predicate];
    if (arr && [arr count] > 0) {
        return YES;
    } else {
        return NO;
    }
}

NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(FTjsonRecord *evaluatedObject, NSDictionary *bindings) {
                return ![store recordExistsForDate:[evaluatedObject recordDate]];
}];

NSSet *set = [[serverRecords items] filteredSetUsingPredicate:predicate]; 

What worries me about this solution though, is the linear read from my clientItems (allRecords). I am not sure how efficient it is using the predicate on the array, wonder if there is a better way to achieve this.

Upvotes: 0

Views: 562

Answers (2)

Wain
Wain

Reputation: 119031

You can use NSSet to get the union, intersection and difference (minus) with other sets. This more accurately matches what you're trying to do.

NSMutableSet *serverItems = [[NSMutableSet alloc] init];
[arrServerItems addObjectsFromArray:arrServer];

NSSet *clientItems = [[NSSet alloc] init];
[clientItems addObjectsFromArray:arrClient];

[arrServerItems minus:clientItems];

This does remove the ordering information though.

For predicates you can use:

NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"NOT (SELF IN %@)", arrClient];

Upvotes: 1

Manu
Manu

Reputation: 788

depend to the predicate that you want to use:

you can use an array of arguments using this

[NSPredicate predicateWithFormat:<#(NSString *)#> argumentArray:<#(NSArray *)#>];

and build your predicate using the objects in the array

or use a predicate with block

[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
    <#code#>
}]

and in the block evluate the object comparing it with the object in your array

a possible check you can do is:

return ![arrClient containsObject:evaluatedObject];

will exclude objects contained in arrClient

containsObject: use 'isEqual:' to compare the objects

Upvotes: 0

Related Questions