Irrational Exuberance!

Filtering Arrays in Objective C

February 15, 2008. Filed under functionalobjc

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.


  1. 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.