130

I have an NSArray and I'd like to create a new NSArray with objects from the original array that meet certain criteria. The criteria is decided by a function that returns a BOOL.

I can create an NSMutableArray, iterate through the source array and copy over the objects that the filter function accepts and then create an immutable version of it.

Is there a better way?

9 Answers 9

144

NSArray and NSMutableArray provide methods to filter array contents. NSArray provides filteredArrayUsingPredicate: which returns a new array containing objects in the receiver that match the specified predicate. NSMutableArray adds filterUsingPredicate: which evaluates the receiver’s content against the specified predicate and leaves only objects that match. These methods are illustrated in the following example.

NSMutableArray *array =
    [NSMutableArray arrayWithObjects:@"Bill", @"Ben", @"Chris", @"Melissa", nil];

NSPredicate *bPredicate =
    [NSPredicate predicateWithFormat:@"SELF beginswith[c] 'b'"];
NSArray *beginWithB =
    [array filteredArrayUsingPredicate:bPredicate];
// beginWithB contains { @"Bill", @"Ben" }.

NSPredicate *sPredicate =
    [NSPredicate predicateWithFormat:@"SELF contains[c] 's'"];
[array filteredArrayUsingPredicate:sPredicate];
// array now contains { @"Chris", @"Melissa" }
Sign up to request clarification or add additional context in comments.

5 Comments

I listened to Papa Smurf's podcast and Papa Smurf said answers should live in StackOverflow so the community can rate and improve them.
@mmalc - Maybe more apporopriate, but certainly more convenient to view it right here.
NSPredicate is dead, long live blocks! cf. my answer below.
What does contains[c] mean? I always see the [c] but I don't understand what it does?
@user1007522, the [c] makes the match case-insensitive
130

There are loads of ways to do this, but by far the neatest is surely using [NSPredicate predicateWithBlock:]:

NSArray *filteredArray = [array filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *bindings) {
    return [object shouldIKeepYou];  // Return YES for each object you want in filteredArray.
}]];

I think that's about as concise as it gets.


Swift:

For those working with NSArrays in Swift, you may prefer this even more concise version:

let filteredArray = array.filter { $0.shouldIKeepYou() }

filter is just a method on Array (NSArray is implicitly bridged to Swift’s Array). It takes one argument: a closure that takes one object in the array and returns a Bool. In your closure, just return true for any objects you want in the filtered array.

4 Comments

What role is NSDictionary *bindings playing here?
@Kaitain bindings is required by the NSPredicate predicateWithBlock: API.
@Kaitain The bindings dictionary can contain variable bindings for templates.
Much more useful than accepted answer when dealing with complex objects :)
50

Based on an answer by Clay Bridges, here is an example of filtering using blocks (change yourArray to your array variable name and testFunc to the name of your testing function):

yourArray = [yourArray objectsAtIndexes:[yourArray indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
    return [self testFunc:obj];
}]];

5 Comments

Finally an answer not only mentioning filtering with blocks, but also giving a good example on how to do it. Thanks.
I like this answer; even though filteredArrayUsingPredicate is leaner, the fact that you don't use any predicates kind of obscures the purpose.
This is the most modern way to do this. Personally I long for the old shorthand "objectsPassingTest" that disappeared from the API at certain point. Still this runs fast and good. I do like NSPredicate - but for other things, where the heavier hammer is needed
You will get error now Implicit conversion of 'NSUInteger' (aka 'unsigned long') to 'NSIndexSet * _Nonnull' is disallowed with ARC... it expects NSIndexSets
@anoop4real, I think the cause for the warning you mentioned is that you mistakenly used indexOfObjectPassingTest instead of indexesOfObjectsPassingTest. Easy to miss, but big difference :)
46

If you are OS X 10.6/iOS 4.0 or later, you're probably better off with blocks than NSPredicate. See -[NSArray indexesOfObjectsPassingTest:] or write your own category to add a handy -select: or -filter: method (example).

Want somebody else to write that category, test it, etc.? Check out BlocksKit (array docs). And there are many more examples to be found by, say, searching for e.g. "nsarray block category select".

18 Comments

Would you mind expanding your answer with an example? Blog websites have a tendency to die when you need them the most.
The protection against link rot is to excerpt relevant code and whatnot from the linked articles, not add more links. Keep the links, but add some example code.
@ClayBridges I found this question looking for ways to filter in cocoa. Since your answer doesn't have any code samples in it, it took me about 5 minutes to dig through your links in order to find out that blocks won't achieve what I need. If the code had been in the answer it would have taken maybe 30 seconds. This answer is FAR less useful without the actual code.
@mydogisbox et alia: there are only 4 answers here, and thus plenty of room for a shiny & superior code-sampled-up answer of your own. I'm happy with mine, so please leave it alone.
mydogisbox is right on this point; if all you're doing is providing a link, it's not really an answer, even if you add more links. See meta.stackexchange.com/questions/8231. The litmus test: Can your answer stand on its own, or does it require clicking the links to be of any value? In particular, you state "you're probably better off with blocks than NSPredicate," but you don't really explain why.
|
17

Assuming that your objects are all of a similar type you could add a method as a category of their base class that calls the function you're using for your criteria. Then create an NSPredicate object that refers to that method.

In some category define your method that uses your function

@implementation BaseClass (SomeCategory)
- (BOOL)myMethod {
    return someComparisonFunction(self, whatever);
}
@end

Then wherever you'll be filtering:

- (NSArray *)myFilteredObjects {
    NSPredicate *pred = [NSPredicate predicateWithFormat:@"myMethod = TRUE"];
    return [myArray filteredArrayUsingPredicate:pred];
}

Of course, if your function only compares against properties reachable from within your class it may just be easier to convert the function's conditions to a predicate string.

1 Comment

I liked this answer, because it is graceful and short, and short-cuts the need to go learn again how to formalise an NSPredicate for the most basic thing - accessing properties and boolean-method. I even think the wrapper was not needed. A simple [myArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"myMethod = TRUE"]]; would suffice. Thanks! (I do love the alternatives as well, but this one is nice).
3

NSPredicate is nextstep's way of constructing condition to filter a collection (NSArray, NSSet, NSDictionary).

For example consider two arrays arr and filteredarr:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF contains[c] %@",@"c"];

filteredarr = [NSMutableArray arrayWithArray:[arr filteredArrayUsingPredicate:predicate]];

the filteredarr will surely have the items that contains the character c alone.

to make it easy to remember those who little sql background it is

*--select * from tbl where column1 like '%a%'--*

1)select * from tbl --> collection

2)column1 like '%a%' --> NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF contains[c] %@",@"c"];

3)select * from tbl where column1 like '%a%' -->

[NSMutableArray arrayWithArray:[arr filteredArrayUsingPredicate:predicate]];

I hope this helps

Comments

0

Checkout this library

https://github.com/BadChoice/Collection

It comes with lots of easy array functions to never write a loop again

So you can just do:

NSArray* youngHeroes = [self.heroes filter:^BOOL(Hero *object) {
    return object.age.intValue < 20;
}];

or

NSArray* oldHeroes = [self.heroes reject:^BOOL(Hero *object) {
    return object.age.intValue < 20;
}];

Comments

0

The Best and easy Way is to create this method And Pass Array And Value:

- (NSArray *) filter:(NSArray *)array where:(NSString *)key is:(id)value{
    NSMutableArray *temArr=[[NSMutableArray alloc] init];
    for(NSDictionary *dic in self)
        if([dic[key] isEqual:value])
            [temArr addObject:dic];
    return temArr;
}

Comments

0

Another category method you could use:

- (NSArray *) filteredArrayUsingBlock:(BOOL (^)(id obj))block {
    NSIndexSet *const filteredIndexes = [self indexesOfObjectsPassingTest:^BOOL (id _Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
                                       return block(obj);
                                   }];

    return [self objectsAtIndexes:filteredIndexes];
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.