128

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?

Cœur
  • 37,241
  • 25
  • 195
  • 267
lajos
  • 25,525
  • 19
  • 65
  • 75

9 Answers9

145

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" }
Niall Kiddle
  • 1,477
  • 1
  • 16
  • 35
lajos
  • 25,525
  • 19
  • 65
  • 75
125

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.

Josh Paradroid
  • 1,172
  • 18
  • 45
Stuart
  • 36,683
  • 19
  • 101
  • 139
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];
}]];
pckill
  • 3,709
  • 36
  • 48
  • 1
    Finally an answer not only mentioning filtering with blocks, but also giving a good example on how to do it. Thanks. – Krystian Jan 30 '16 at 11:27
  • I like this answer; even though `filteredArrayUsingPredicate` is leaner, the fact that you don't use any predicates kind of obscures the purpose. – James Perih Apr 13 '16 at 21:41
  • 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 – Motti Shneor Feb 18 '19 at 13:50
  • You will get error now ``Implicit conversion of 'NSUInteger' (aka 'unsigned long') to 'NSIndexSet * _Nonnull' is disallowed with ARC``... it expects NSIndexSets – anoop4real Apr 28 '20 at 08:01
  • @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 :) – pckill May 05 '20 at 10:40
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".

Clay Bridges
  • 11,602
  • 10
  • 68
  • 118
  • 7
    Would you mind expanding your answer with an example? Blog websites have a tendency to die when you need them the most. – Dan Abramov Mar 21 '12 at 11:22
  • Added more suggestions in case of linkrot. – Clay Bridges Mar 21 '12 at 16:12
  • 8
    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. –  Jun 07 '12 at 02:35
  • @mydogisbox: quod erat demonstrandum. – Clay Bridges Jun 19 '12 at 17:25
  • 3
    @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. – N_A Jun 19 '12 at 17:30
  • 1
    @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. – Clay Bridges Jun 19 '12 at 19:51
  • @ClayBridges SO is a collaborative space where answer editing is encouraged. According to the faq your post is licensed under http://creativecommons.org/licenses/by-sa/3.0/ . http://stackoverflow.com/faq#editing – N_A Jun 19 '12 at 20:31
  • @mydogisbox: Sorry, but I'm inclined to agree with Clay on this one. His answer has 12 upvotes, so the community already likes it, and your edit changes too much. Post your own answer if you feel the existing ones are inadequate. – Robert Harvey Jun 19 '12 at 21:08
  • In mydogisbox's defense, your answer will become worthless if any of the links break, and telling people to search for things doesn't really answer the question. – Robert Harvey Jun 19 '12 at 21:09
  • @RobertHarvey I'm confused how I changed too much. According to the faq: "this site is collaboratively edited, like Wikipedia. If you see something that needs improvement, click edit and help us make it so!" Putting the linked code in the answer is standard procedure to combat link rot and reduces the amount of time required to understand an answer. The only part I removed or changed, other than adding linked code, was a bad link. – N_A Jun 19 '12 at 21:16
  • 2
    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 http://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. – Robert Harvey Jun 19 '12 at 21:55
  • @mydogisbox: There's already an accepted answer that's probably satisfactory. – Robert Harvey Jun 19 '12 at 21:58
  • @RobertHarvey Hmmm ok. I thought secondary answers were also up for improvement since they also provide value (as in this case). I stand corrected. – N_A Jun 19 '12 at 22:05
  • @RobertHarvey As you are generally defending my me: thanks, really, a lot. I do think that the answer stands alone without the links (as would "Use blocks instead."), so I think "will become worthless" is hyperbole. As to why & when blocks are better than NSPredicate, it boils down to pith, craft, and opinion, and those things are difficult to explain during my SO-budgeted-time. Thus, "you are probably better off". – Clay Bridges Jun 20 '12 at 08:35
  • For interested readers (all three of you), I strongly recommend clicking [@RobertHarvey's link](http://meta.stackexchange.com/questions/8231). Controversy brews there, and this matter is hardly settled. – Clay Bridges Jun 20 '12 at 08:37
  • The accepted answer at that link is the currently-accepted consensus. Your answer got a pass because it has so many upvotes, but we routinely delete answers that merely contain a single link. – Robert Harvey Jun 20 '12 at 14:46
  • What is the difference in performance between a predicate and block based approach? – AlexR Oct 19 '12 at 08:37
  • @AlexR Off hand, dunno. Better asked as a separate question, I think. – Clay Bridges Apr 02 '13 at 21:12
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.

Ashley Clark
  • 8,813
  • 3
  • 35
  • 35
  • 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). – Motti Shneor Feb 18 '19 at 14:01
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

Stuart M
  • 11,458
  • 6
  • 45
  • 59
Durai Amuthan.H
  • 31,670
  • 10
  • 160
  • 241
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;
}];
Jordi Puigdellívol
  • 1,710
  • 3
  • 23
  • 31
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;
}
jalmatari
  • 331
  • 5
  • 8
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];
}
Dan Rosenstark
  • 68,471
  • 58
  • 283
  • 421