Filtering Arrays in Objective C
Recently I have been getting into Objective C a bit, and its really a fun language. One thing that I haven't been overly joyous about is writing more old-style for loops1.
int i, count = [someList count];
for (i=0; i<count;i++) {
NSObject * obj = [someList objectAtIndex:i];
NSLog(@"Object at %d: %@" , count, obj);
}
That is a bit tacky compared to Python
for obj in someList:
some_func(obj)
or Lisp
(map #'some-func someList)
but in the end its livable.
On the other hand, a current project I am working on does a lot of list filtering, and I was looking for how to do that in Objective C without a writing a for loop for each of the situations I wanted to filter. Essentially I wanted a way to be able to do this:
(setf filtered (filter #'is-positive-numberp numbers))
or
filtered = [ x for x in numbers if x > 0]
Fortunately it turns out that Objective C's NSArray has pretty good support for this operation. Lets take a look.
Simple Filtering of NSArrays
First lets look at replicating the above example (filtering positive numbers from a list of numbers).
NSMutableArray *arr = [[NSMutableArray alloc] init];
[arr addObject:[[NSNumber alloc] initWithDouble:53.0]];
[arr addObject:[[NSNumber alloc] initWithDouble:17.0]];
[arr addObject:[[NSNumber alloc] initWithDouble:-34.0]];
[arr addObject:[[NSNumber alloc] initWithDouble:0.0]];
NSPredicate *pred = [NSPredicate predicateWithFormat:@"doubleValue > 0.0"];
NSArray *filtered = [arr filteredArrayUsingPredicate:pred];
[filtered retain]; // didn't alloc, init, or copy so not yet retained
[arr release]; // we did alloc & init this, so we need to release it
We use NSPredicate instances to help filter lists. The syntax can get a lot more complex, but its still requiring you to mess around with a mini-language to filter your lists (although list comprehensions in other languages tend to suffer the same flaw).
Sometimes the predicate mini-language won't be a very elegant solution to your problem, but fortunately you can use the predicate language to send a message to your objects that will do the evaluation for you.
Lets take a look.
Filtering Objects in NSArrays
For this example we are going to have make a Transaction class, which will contain two pieces of data: a NSString which describes it, and a double that holds its value. We'll write the code to filter lists of Transactions into positive and negative values, and also to match against their description.
Transaction.h
#import <Cocoa/Cocoa.h>
@interface Transaction : NSObject
{
@private
double value;
NSString type;
}
-(id)initWithValue: (double)aValue withType:aType;
-(double)value;
-(void)setValue: (double)aValue;
-(NSString )type;
-(void)setType: (NSString *)aType;
-(BOOL)isPositive;
-(BOOL)isNegative;
@end
and the implementation will be pretty simple as well.
Transaction.m
#import "Transaction.h"
@implementation Transaction
/*
initWithValue:aValue withType:aType is a standard
init method, storing the two values in the
'value' and 'type' fields respectivey.
the 'value', 'setValue', 'type' and 'setType' methods
are also normal getter/setter methods
*/
-(BOOL) isPositive {
if (value > 0.0) return YES;
else return NO;
}
-(BOOL) isNegative {
return ![self isPositive];
}
@end
The isPositive and isNegative methods are very simple here, and could be easily expressed in the predicate mini-language, but the take home lesson here is that you could do an arbitrarily complex calculation there instead of a simple double comparison.
Now lets do a bit of filtering with our Transaction class.
// only positive transactions
-(NSArray *)positiveTransactions: (NSArray *)someTransactions {
NSPredicate *isPositive = [NSPredicate predicateWithFormat:@"isPositive == YES"];
return [someTransactions filteredArrayUsingPredicate:isPositive];
}
// only negative transactions
-(NSArray )negativeTransactions: (NSArray )someTransactions {
NSPredicate *isNegative = [NSPredicate predicateWithFormat:@"isNegative == YES"];
return [someTransactions filteredArrayUsingPredicate:isNegative];
}
// are of a specified type
-(NSArray )getTransactions: (NSArray )someTransactions withType: (NSString )aType {
NSPredicate ofType = [NSPredicate predicateWithFormat:@"type like %@", aType];
return [someTransactions filteredArrayUsingPredicate:ofType];
}
Filtering arrays in Objective C isn't awesomely simple and convenient, but it is doable. Best of luck.
What Apple is calling Objective C 2.0 included with Leopard has a more concise looping syntax, but I am still using Tiger, and I'm uncertain is the gcc ObjC compiler supports the new ObjC2.0 syntax.↩