filtering NSArray into a new NSArray in Objective-C

119,434

Solution 1

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" }

Solution 2

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.

Solution 3

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];
}]];

Solution 4

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

Solution 5

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.

Share:
119,434
Berry Ligtermoet
Author by

Berry Ligtermoet

Updated on January 21, 2021

Comments

  • Berry Ligtermoet
    Berry Ligtermoet over 3 years

    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?

  • kayjay
    kayjay over 14 years
    I listened to Papa Smurf's podcast and Papa Smurf said answers should live in StackOverflow so the community can rate and improve them.
  • Bryan
    Bryan almost 14 years
    @mmalc - Maybe more apporopriate, but certainly more convenient to view it right here.
  • user4581301
    user4581301 over 12 years
    NSPredicate is dead, long live blocks! cf. my answer below.
  • Dan Abramov
    Dan Abramov about 12 years
    Would you mind expanding your answer with an example? Blog websites have a tendency to die when you need them the most.
  • user4581301
    user4581301 about 12 years
    Added more suggestions in case of linkrot.
  • toolbear
    toolbear almost 12 years
    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.
  • user4581301
    user4581301 almost 12 years
    @mydogisbox: quod erat demonstrandum.
  • N_A
    N_A almost 12 years
    @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.
  • user4581301
    user4581301 almost 12 years
    @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.
  • N_A
    N_A almost 12 years
    @ClayBridges SO is a collaborative space where answer editing is encouraged. According to the faq your post is licensed under creativecommons.org/licenses/by-sa/3.0 . stackoverflow.com/faq#editing
  • Yinda Yin
    Yinda Yin almost 12 years
    @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.
  • Yinda Yin
    Yinda Yin almost 12 years
    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.
  • N_A
    N_A almost 12 years
    @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.
  • Yinda Yin
    Yinda Yin almost 12 years
    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.
  • Yinda Yin
    Yinda Yin almost 12 years
    @mydogisbox: There's already an accepted answer that's probably satisfactory.
  • N_A
    N_A almost 12 years
    @RobertHarvey Hmmm ok. I thought secondary answers were also up for improvement since they also provide value (as in this case). I stand corrected.
  • user4581301
    user4581301 almost 12 years
    @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".
  • user4581301
    user4581301 almost 12 years
    For interested readers (all three of you), I strongly recommend clicking @RobertHarvey's link. Controversy brews there, and this matter is hardly settled.
  • Yinda Yin
    Yinda Yin almost 12 years
    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.
  • AlexR
    AlexR over 11 years
    What is the difference in performance between a predicate and block based approach?
  • user4581301
    user4581301 about 11 years
    @AlexR Off hand, dunno. Better asked as a separate question, I think.
  • user1007522
    user1007522 almost 10 years
    What does contains[c] mean? I always see the [c] but I don't understand what it does?
  • jonbauer
    jonbauer over 9 years
    @user1007522, the [c] makes the match case-insensitive
  • Kaitain
    Kaitain almost 9 years
    What role is NSDictionary *bindings playing here?
  • ThomasW
    ThomasW over 8 years
    @Kaitain bindings is required by the NSPredicate predicateWithBlock: API.
  • Amin Negm-Awad
    Amin Negm-Awad over 8 years
    @Kaitain The bindings dictionary can contain variable bindings for templates.
  • Krystian
    Krystian over 8 years
    Finally an answer not only mentioning filtering with blocks, but also giving a good example on how to do it. Thanks.
  • Ky -
    Ky - about 8 years
    Much more useful than accepted answer when dealing with complex objects :)
  • James Perih
    James Perih about 8 years
    I like this answer; even though filteredArrayUsingPredicate is leaner, the fact that you don't use any predicates kind of obscures the purpose.
  • Motti Shneor
    Motti Shneor about 5 years
    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
    Motti Shneor about 5 years
    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).
  • anoop4real
    anoop4real about 4 years
    You will get error now Implicit conversion of 'NSUInteger' (aka 'unsigned long') to 'NSIndexSet * _Nonnull' is disallowed with ARC... it expects NSIndexSets
  • pckill
    pckill about 4 years
    @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 :)