filtering NSArray into a new NSArray in Objective-C
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 NSArray
s 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.
Berry Ligtermoet
Updated on January 21, 2021Comments
-
Berry Ligtermoet over 3 years
I have an
NSArray
and I'd like to create a newNSArray
with objects from the original array that meet certain criteria. The criteria is decided by a function that returns aBOOL
.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 over 14 yearsI listened to Papa Smurf's podcast and Papa Smurf said answers should live in StackOverflow so the community can rate and improve them.
-
Bryan almost 14 years@mmalc - Maybe more apporopriate, but certainly more convenient to view it right here.
-
user4581301 over 12 yearsNSPredicate is dead, long live blocks! cf. my answer below.
-
Dan Abramov about 12 yearsWould you mind expanding your answer with an example? Blog websites have a tendency to die when you need them the most.
-
user4581301 about 12 yearsAdded more suggestions in case of linkrot.
-
toolbear almost 12 yearsThe 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 almost 12 years@mydogisbox: quod erat demonstrandum.
-
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 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 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 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 almost 12 yearsIn 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 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 almost 12 yearsmydogisbox 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 almost 12 years@mydogisbox: There's already an accepted answer that's probably satisfactory.
-
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 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 almost 12 yearsFor interested readers (all three of you), I strongly recommend clicking @RobertHarvey's link. Controversy brews there, and this matter is hardly settled.
-
Yinda Yin almost 12 yearsThe 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 over 11 yearsWhat is the difference in performance between a predicate and block based approach?
-
user4581301 about 11 years@AlexR Off hand, dunno. Better asked as a separate question, I think.
-
user1007522 almost 10 yearsWhat does contains[c] mean? I always see the [c] but I don't understand what it does?
-
jonbauer over 9 years@user1007522, the [c] makes the match case-insensitive
-
Kaitain almost 9 yearsWhat role is NSDictionary *bindings playing here?
-
ThomasW over 8 years@Kaitain bindings is required by the
NSPredicate predicateWithBlock:
API. -
Amin Negm-Awad over 8 years@Kaitain The
bindings
dictionary can contain variable bindings for templates. -
Krystian over 8 yearsFinally an answer not only mentioning filtering with blocks, but also giving a good example on how to do it. Thanks.
-
Ky - about 8 yearsMuch more useful than accepted answer when dealing with complex objects :)
-
James Perih about 8 yearsI like this answer; even though
filteredArrayUsingPredicate
is leaner, the fact that you don't use any predicates kind of obscures the purpose. -
Motti Shneor about 5 yearsThis 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 about 5 yearsI 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 about 4 yearsYou will get error now
Implicit conversion of 'NSUInteger' (aka 'unsigned long') to 'NSIndexSet * _Nonnull' is disallowed with ARC
... it expects NSIndexSets -
pckill about 4 years@anoop4real, I think the cause for the warning you mentioned is that you mistakenly used
indexOfObjectPassingTest
instead ofindexesOfObjectsPassingTest
. Easy to miss, but big difference :)