Is there a way in Objective-C to search an array of objects by the contained object's properties if the properties are of type string?
For instance, I have an NSArray of Person objects. Person has two properties, NSString *firstName and NSString *lastName.
What's the best way to search through the array to find everyone who matches 'Ken' anywhere in the firstName OR lastName properties?
try something like this:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"firstName==%# OR lastName==%#",#"Ken",#"Ken"];
NSArray *results = [allPersons filteredArrayUsingPredicate:predicate];
Short answer: NSArray:filteredArrayUsingPredicate:
Long answer: Predicate Programming Guide
You can simply use NSPredicate to filter your search from actual result array:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.property_name contains[c] %#",stringToSearch];
filteredPendingList = [NSMutableArray arrayWithArray:[mainArr filteredArrayUsingPredicate:predicate]];
NSSortDescriptor *sortDescriptor;
sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"property_name"
ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *sortedArray;
sortedArray = [filteredPendingList sortedArrayUsingDescriptors:sortDescriptors];
So you will be getting the sorted array with filtered result. property_name above is the name of variable inside your object on which you want to perform your search operation. Hope it will help you.
You'll have to do a linear search, comparing each entry in the array to see if it matches what you're looking for.
Related
Let's suppose I have an NSArray of Person objects, those are composed of 2 properties( name and age ).
Just for the sake of understanding how it works and practice I would like to know how to create a predicate that accesses every member of the array and filters all the objects with an age property that its bigger or equal to 30.
I just can't figure that out:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#">= 30"];
[persons filteredArrayUsingPredicate:predicate];
NSLog(#"%#",persons)
What should I type on predicateWithFormat?
Thank you in advance!
Two issues:
The predicate format is key - operator - value for example
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"age >= 30"];
or with placeholders
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K >= %ld", #"age", 30];
filteredArrayUsingPredicate returns a new array
NSArray *filteredArray = [persons filteredArrayUsingPredicate:predicate];
NSLog(#"%#", filteredArray)
For further information read Predicate Format String Syntax
I know you can do something like [..."SELF.some_id == [c] %d AND SELF.some_id == [c] %d", id1, id2], but I need more than this. Is there a way to do this without building a string.
For example...
NSArray *arrayOfWantedWidgetIds = #[1,3,5,6,9,13,14,16];
NSMutableArray *allWidgets = [[WidgetManager sharedWidget] getAllWidgets];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.widget_id ==[c] %d", arrayOfWantedWidgetIds]; //obviously we can't do this, it won't accept an array of IDS for %d
[allWidgets filterArrayUsingPredicate:predicate];
How can I achieve something like this? Another way... if I looped this, and made individual predicates for each value in arrayOfWantedWidgetIds, and then add all the individual predicates into an array... this doesn't help either as filterArrayUsingPredicate only accepts an NSPredicate. Not an array of them.
You cannot pass an array to the predicate API like that. However, you can pass an array of IDs, and use the IN operator, which should be sufficient in your specific case:
NSArray *arrayOfWantedWidgetIds = #[#1, #3, #5, #6, #9, #13, #14, #16];
NSMutableArray *allWidgets = [[WidgetManager sharedWidget] getAllWidgets];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.widget_id IN %#", arrayOfWantedWidgetIds];
[allWidgets filterArrayUsingPredicate:predicate];
You can also construct a composite predicate with NSCompoundPredicate, but this is unnecessary in this situation.
Look at the in operator
NSArray *idList = #[#1,#2,#3,#5,#7];
NSMutableArray *widgetList = [[NSMutableArray alloc] init];
for (int i=0; i<20; i++) {
[widgetList addObject:[[widgets alloc] init]];
}
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.widget_id in %#",idList];
NSLog(#"%#",[widgetList filteredArrayUsingPredicate:predicate].debugDescription);
I need to be able to sort the results of my sort method, but it's unclear to me how to do that, do I need to just run a simular method again on the previous results or can it be done in one method?
Here's my method
-(NSArray*)getGameTemplateObjectOfType:(NSString *) type
{
NSArray *sortedArray;
if(editorMode == YES)
{
sortedArray = kingdomTemplateObjects;
}
else
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"type CONTAINS[cd] %#", type];
NSArray *newArray = [kingdomTemplateObjects filteredArrayUsingPredicate:predicate];
NSSortDescriptor *sortDescriptor;
sortDescriptor = [[NSSortDescriptor alloc] initWithKey:type
ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
sortedArray = [newArray sortedArrayUsingDescriptors:sortDescriptors];
}
return sortedArray;
}
type is being set to "Building" which returns all the building types in my game, but what if I then want those results sorted alphabetically according to their name? or perhaps sorted by which building is the most expensive from it's gold value?
You have to parse the array twice. NSPredicate does not provide a means to sort. Check out the NSPredicate Programming Guide. What I did actually was to quickly scan the NSPredicate BNF Syntax to look for obvious signs of sorting operators, such as ASC or DESC. Nothing is there.
Also, there are a number of similar questions here on SO:
How to sort NSPredicate
NSPredicate Sort Array and Order DESC
NSSortDescriptor and NSPredicate for sorting and filtering
To tell your getGameTemplateObjectOfType: how you want the results sorted, you might pass in some key for sorting. For example:
-(NSArray *)getGameTemplateObjectOfType:(NSString *)type sortedByKey:(NSString *)key ascending:(BOOL)ascending;
But to do so would likely complicate your code - you will have to handle all combinations of key and type inside your function. (Let me know if you don't understand what I'm saying here).
In the end it may be that you resign your filtering function getGameTemplateObjectOfType: to just that: filtering. And if the client of that function wants the results sorted in some fashion, then the client can do so. And then you will discover why it is that Apple has kept the functionalities separated.
in your code, if [kingdomTemplateObjects filteredArrayUsingPredicate:predicate]; returns the correct results
then you can use [newArray sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)]; to sort your array.
-(NSArray*)getGameTemplateObjectOfType:(NSString *) type
{
NSArray *sortedArray;
if(editorMode == YES)
{
sortedArray = kingdomTemplateObjects;
}
else
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"type CONTAINS[cd] %#", type];
NSArray *newArray = [kingdomTemplateObjects filteredArrayUsingPredicate:predicate];
sortedArray = [newArray sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
}
return sortedArray;
}
I'm trying to filter a UITableView's data using a UISearchDisplayController and NSCompoundPredicate. I have a custom cell with 3 UILabels that I want to all be filtered within the search, hence the NSCompoundPredicate.
// Filter the array using NSPredicate(s)
NSPredicate *predicateName = [NSPredicate predicateWithFormat:#"SELF.productName contains[c] %#", searchText];
NSPredicate *predicateManufacturer = [NSPredicate predicateWithFormat:#"SELF.productManufacturer contains[c] %#", searchText];
NSPredicate *predicateNumber = [NSPredicate predicateWithFormat:#"SELF.numberOfDocuments contains[c] %#",searchText];
// Add the predicates to the NSArray
NSArray *subPredicates = [[NSArray alloc] initWithObjects:predicateName, predicateManufacturer, predicateNumber, nil];
NSCompoundPredicate *compoundPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
However, when I do this, the compiler warns me:
Incompatible pointer types initializing 'NSCompoundPredicate *_strong'
with an expression of type 'NSPredicate *'
Every example I've seen online does this exact same thing, so I'm confused. The NSCompoundPredicate orPredicateWithSubpredicates: method takes an (NSArray *) in the last parameter, so I'm REALLY confused.
What's wrong?
First of all, using "contains" is very slow, consider mayber "beginswith"?
Second, what you want is:
NSPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
Three, you could've just done something like:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.productName beginswith[cd] %# OR SELF.productManufacturer contains[cd] %#", searchText, searchText];
orPredicateWithSubpredicates: is defined to return an NSPredicate*. You should be able to change your last line of code to:
NSPredicate *compoundPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
... and still have all of the compoundPredicates applied.
Here's an useful method i created based on the answers above (which i thank very much!)
It allows to create an NSPredicate dynamically, by sending an array of filter items and a string which represents the search criteria.
In the original case, the search criteria changes, so it should be an array instead of a string. But it may be helpful anyway
- (NSPredicate *)dynamicPredicate:(NSArray *)array withSearchCriteria:(NSString *)searchCriteria
{
NSArray *subPredicates = [[NSArray alloc] init];
NSMutableArray *subPredicatesAux = [[NSMutableArray alloc] init];
NSPredicate *predicate;
for( int i=0; i<array.count; i++ )
{
predicate = [NSPredicate predicateWithFormat:searchCriteria, array[i]];
[subPredicatesAux addObject:predicate];
}
subPredicates = [subPredicatesAux copy];
return [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
}
I want to get strings that have specific length in NSArray.
The array has many elements and I don't want to use fast enumeration.
Is there a possible way?
This works like a charm:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"self.length == %d", lenght];
NSArray *filtered = [array filteredArrayUsingPredicate:predicate];
No matter what you do you will be using fast enumeration whether you realize it or not. However, have you considered using an NSPredicate object and the filteredArrayWithPredicate method?
NSArray *yourArray = [[NSArray alloc] initWithObjects:#"Apple, Orange, Grapes, Cherry, nil"];
for(NSString *element in yourArray){
if(element.length==yourLength){
[filteredArray addObject:element];
}
}
NSLog(#"Filtered array now contains the elements with length %d", yourLength);
NSLog(#"Filtered array--%#", filteredArray);