Comparing an Array with another using NSPredicate - iphone

I've the following structure:
TxnSummary * t1 = [[TxnSummary alloc] init];
t1.txnId = #"1";
t1.shortDesc = #"First one";
t1.filters = [[NSArray alloc] initWithObjects:#"F1", #"F2", nil];
TxnSummary * t2 = [[TxnSummary alloc] init];
t2.txnId = #"2";
t2.shortDesc = #"Second one";
t2.filters = [[NSArray alloc] initWithObjects:#"F1",#"F2", #"F3", nil];
TxnSummary * t3 = [[TxnSummary alloc] init];
t3.txnId = #"3";
t3.shortDesc = #"Third one";
t3.filters = [[NSArray alloc] initWithObjects:#"F1", #"F3", nil];
TxnSummary * t4 = [[TxnSummary alloc] init];
t4.txnId = #"4";
t4.shortDesc = #"Fourth one";
t4.filters = [[NSArray alloc] initWithObjects:#"F4", nil];
NSArray * xnArray = [[NSArray alloc] initWithObjects:t1,t2,t3,t4, nil];
Now if I want to find out which of the txn summaries have filters F1, then I could do this:
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"filters CONTAINS[cd] %#", #"F1"];
NSArray * filteredArray = [xnArray filteredArrayUsingPredicate:predicate];
This works well if I'm comparing for only one string, but if want to find out which all txn summaries have filters "F1", or "F2", then if I have to follow the above mechanism I'll have to create two predicates - each for F1 and F2 and then run it against the xnArray (which seems to be inefficient). I want to be able to create a list of filters strings and use that to fetch the matching txs from the xn array.
NSArray * filterStrings = [[NSArray alloc] initWithObjects:#"F1",#"F2", nil];
Does NSPredicate have functionality to achieve this or should I resort to some other method of filtering?
Appreciate your help.
Thanks, Kumar

If I understand you correctly, you can achieve this by creating a compound predicate from an array of predicates, for example:
NSPredicate *newFilterPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:selectedItemIDs];
EDIT: added more detailed explanation:
Compound predicates combine predicates into one predicate. For example, if you want to filter for items that contain "F1" or "F2" you do this:
// Normally build this in some kind of loop
NSPredicate *firstPredicate = [NSPredicate predicateWithFormat:#"filter =%#", #"F1"];
NSPredicate *secondPredicate = [NSPredicate predicateWithFormat:#"filter =%#", #"F1"];
// Create the array of predicates
NSArray *arrayOfPredicates = [NSArray arrayWithObjects:firstPredicate, secondPredicate, nil];
// Create the compound predicate
NSPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:arrayOfPredicates];
There are also methods for "and" instead of "or" as well as other boolean conditions. Full reference can be found here: NSCompoundPredicate Class Reference
Hope this helps,
Dave

You can do something like:
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"filters CONTAINS[cd] %# || filters CONTAINS[cd] %#", #"F1", #"F4"];
If you want to add all the keys that are in a array you can do something like that:
NSArray * filterStrings = [[NSArray alloc] initWithObjects:#"F1",#"F4", nil];
NSString* predicateString = [filterStrings componentsJoinedByString:#"'|| filters CONTAINS[cd] '"];
predicateString = [NSString stringWithFormat:#"filters CONTAINS[cd] '%#'",predicateString];
NSPredicate * predicate = [NSPredicate predicateWithFormat:predicateString];
NSArray * filteredArray = [xnArray filteredArrayUsingPredicate:predicate];

I wouldn't use NSArray to store the filters. This is a perfect book-like example for using NSSet/NSMutableSet instead. You can initialize similarly to the array:
t1.filters = [[NSSet alloc] initWithObjects:#"F1", #"F2", nil];
Then you check if that particular string exists simply by calling:
BOOL contains = [t1.filter containsObject:#"F1"];
You can now also filter the set with methods like filteredSetUsingPredicate, objectsPassingTest (to use with blocks) or even create intersections or unions with other sets (isSubsetOfSet, intersectsSet, etc). So for example you could create a new set with the searched elements and check if the set contains them:
NSSet* toFind = [[NSSet alloc] initWithObjects:#"F1", #"F3", nil];
[toFind isSubsetOfSet:t1.filters];
Searching a set is much quicker than an array because set is backed up by a Hash table, whereas an array has to be searched linearly.

If exact matching is OK, you could use the IN predicate like so:
NSArray *filterStrings = [NSArray arrayWithObjects:#"F1", #"F2", nil];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"filters IN %#", filterStrings];
NSArray *filteredArray = [xnArray filteredArrayUsingPredicate:predicate];

Related

How to with create a compoundPredicate with both "OR" and "AND"

I have the following 3 categories fetched, but want to add a "AND" predicate to narrow the results, where the "mark" is on.
NSMutableArray *questionNumberArray=[[NSMutableArray alloc] init];
// Fetch questions
NSManagedObjectContext *context = self.document.managedObjectContext;
NSFetchRequest *fetchRequest =
[[NSFetchRequest alloc] initWithEntityName:#"Questions"];
//building the predicate with selected category
NSMutableArray *parr = [NSMutableArray array];
if ([cat0str length]){
[parr addObject:[NSPredicate predicateWithFormat:#"question.category CONTAINS[c] %#",cat0str]];
}
if ([cat1str length]){
[parr addObject:[NSPredicate predicateWithFormat:#"question.category CONTAINS[c] %#",cat1str]];
}
if ([cat2str length]){
[parr addObject:[NSPredicate predicateWithFormat:#"question.category CONTAINS[c] %#",cat2str]];
}
NSPredicate *compoundPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:parr];
[fetchRequest setPredicate:compoundPredicate];
NSPredicate *markPredicate =[NSPredicate predicateWithFormat:#"question.mark == 1"];
}
//I'd like to do something like that:
NSPredicate *finalPredicate = [compoundPredicate && markPredicate];
[fetchRequest setPredicate:finalPredicate];
Is this what you are looking for?
NSPredicate *finalPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:
[NSArray arrayWithObjects:compoundPredicate, markPredicate, nil]];
or, using the "modern Objective-C" syntax for container literals:
NSPredicate *finalPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:
#[compoundPredicate, markPredicate]];

NSPredicate against NSArray in iphone

How can I use NSPredicate to filter the values from array ivar against search query ? I manage to use NSPredicate against string ivar. I have one user defined class named as "User" which is used to create User from AddressBook. Here is my code
// User Defined Class named as "User"
// In User.h
#interface User : NSObject {
NSString *firstName;
NSString *lastName;
NSString *company;
UIImage *userImage;
NSArray *phoneNumberArray;
NSArray *emailArray;
NSArray *urlArray;
NSArray *addressArray;
NSString *notes;
NSString *dateOfBirth;
}
// In SearchViewController.m
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText{
// contactsArray is NSMutableArray
contactsArray = [Search searchContactsWithQuery:searchText];
NSLog(#"contactsArray count => %d",[contactsArray count]);
[contactsTableView reloadData];
}
// In Search.m
+(NSMutableArray*)searchContactsWithQuery:(NSString*)query {
NSLog(#"Query => %#",query);
NSMutableArray* predicates=[[NSMutableArray alloc] init];
// Create all predicates
NSPredicate * firstNamePredicate = [NSPredicate predicateWithFormat:#"firstName contains %#",query];
[predicates addObject:firstNamePredicate];
NSPredicate * lastNamePredicate = [NSPredicate predicateWithFormat:#"lastName contains %#",query];
[predicates addObject:lastNamePredicate];
NSPredicate * companyPredicate = [NSPredicate predicateWithFormat:#"company contains %#",query];
[predicates addObject:companyPredicate];
// Don't know how to use on array
// === START of ARRAY Predicate ====
NSPredicate *phoneNoPredicate = [NSPredicate predicateWithFormat:#"phoneNumberArray IN %#",query];
[predicates addObject:phoneNoPredicate];
NSPredicate *emailPredicate = [NSPredicate predicateWithFormat:#"emailArray contains %#",query];
[predicates addObject:emailPredicate];
NSPredicate *urlPredicate = [NSPredicate predicateWithFormat:#"urlArray contains %#",query];
[predicates addObject:urlPredicate];
NSPredicate *addressPredicate = [NSPredicate predicateWithFormat:#"addressArray contains %#",query];
// === END of ARRAY Predicate ====
NSPredicate *notesPredicate = [NSPredicate predicateWithFormat:#"notes contains %#",query];
[predicates addObject:notesPredicate];
NSPredicate *dobPredicate = [NSPredicate predicateWithFormat:#"dateOfBirth contains %#",query];
[predicates addObject:dobPredicate];
// Add predicates to array
NSPredicate *compoundPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:predicates];
NSArray * filteredArray = [APP_DELEGATE.allUsersArray filteredArrayUsingPredicate:compoundPredicate];
return [NSMutableArray arrayWithArray:filteredArray];
}
phoneNumberArray,emailArray,urlArray & addressArray are used because user may have multiple entries for phone no , email, address , url like HOME , WORK, iPhone, Other etc.
How can I use predicates on array? Any kind of help is appreciated. Thanks
You are doing the correct way, but you need to create your Predicates with String values instead of passing the Objects directly.
You need to use %k instead of %# like
[NSPredicate predicateWithFormat:#"firstName contains %k",query];
Please let me know if this helps.

Using #min,#max and etc inside a predicate?

Is it possible to use aggregate operator such as #min inside a predicate?
BTW The predicate filters an array of objects.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY SELF.score == #min.score"];
I know you can retrieve the min value using the key-value operators eg:
NSNumber *minScore = [myArray valueForKeyPath:#"#min.score"];
So far I only get errors about the objects not being key value compliant for "#min".
Thanks
The reason that you're getting that error is that the predicate is being applied to each object in myArray, and those objects apparently don't support the key path #min.score. NSPredicate does have support for at least some of the collection operators, though. Here's a simple example that works:
NSDictionary *d1 = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:35] forKey:#"score"];
NSDictionary *d2 = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:52] forKey:#"score"];
NSDictionary *d3 = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:13] forKey:#"score"];
NSDictionary *d4 = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:19] forKey:#"score"];
NSArray *array = [NSArray arrayWithObjects:d1, d2, d3, d4, nil];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.score == %#.#min.score", array];
NSLog(#"Min dictionaries: %#", [array filteredArrayUsingPredicate:predicate]);
You can see that in this case, the #min.score key path is applied to the array, which makes sense. The output is an array containing the one dictionary that contains the minimum value:
Min dictionaries: (
{
score = 13;
}
)

NSCompoundPredicate fails to match

I'm building a NSPredicate using the code below for an iPhone app. The logging shows the prediate to be: location CONTAINS "head" AND shape CONTAINS "oval" AND texture CONTAINS "bumpy" AND colour CONTAINS "red"
I get no results. If I limit the predicate to a single item it will work, more than 1 fails.
Can anyone tell me why?
Many thanks
NSMutableArray *subPredicates = [[NSMutableArray alloc] init];
for (Ditem in self.tableDataSource) {
NSString *Title = [Ditem valueForKey:#"Title"];
NSString *Value = [Ditem valueForKey:#"Value"];
if([[Value lowercaseString] isEqualToString: #"all"]){
Value = #"";
}
else{
NSPredicate *p = [NSComparisonPredicate predicateWithLeftExpression:[NSExpression expressionForKeyPath:[Title lowercaseString]] rightExpression:[NSExpression expressionForConstantValue:[Value lowercaseString]] modifier:NSDirectPredicateModifier type:NSContainsPredicateOperatorType options:0];
[subPredicates addObject:p];
}
}
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:subPredicates];
NSLog(#"predicate: %#", predicate);[self.fetchedResultsController.fetchRequest setPredicate:predicate];
Your predicate is requiring that all of the values in your filterable objects be strings. Is that correct?
Also, I would simplify your subpredicate creation to:
NSPredicate * p = [NSPredicate predicateWithFormat:#"%K CONTAINS %#", [Title lowercaseString], [Value lowercaseString]];

Sort Array by Key

I have two arrays used in a small game.
If the player gets a score above a certain value their name & score gets output via
an UILabel.
NSArray *namesArray = [mainArray objectForKey:#"names"];
NSArray *highScoresArray = [mainArray objectForKey:#"scores"];
I need the UILabels to display with the highest score in descending order, with the corresponding name. I've used an NSSortDescriptor to sort the score values numerically.
NSSortDescriptor *sortDescriptor;
sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:#"self"
ascending:NO] autorelease];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *sortedScore = [[NSArray alloc]init];
sortedScore = [scoresArray sortedArrayUsingDescriptors:sortDescriptors];
NSMutableArray *scoreLabels = [NSMutableArray arrayWithCapacity:10];
[scoreLabels addObject:scoreLabel1];
......
NSUInteger _index = 0;
for (NSNumber *_number in sortedScore) {
UILabel *_label = [scoreLabels objectAtIndex:_index];
_label.text = [NSString stringWithFormat:#"%d", [_number intValue]];
_index++;
}
This works well enough as the scores now display in descending order.
The problem is that I need the corresponding name to also display according in the new sorted order.
I cant use the same sort selector and I don't wont to sort them alphabetically, they need
to correspond to the name/score values that were first input.
Thanks in advance
You need to put the name and the score together into a single instance of NSDictionary, and then have an NSArray of those NSDictionary instances. Then when you sort by score, you can pull up the corresponding name.