NSPredicate predicateWithFormat not evaluating format specifier - objective-c

I have a (to me) curious case with NSPredicate's predicateWithFormat: method.
Using the following I log the description of two NSPredicate instances to the console:
NSNumber *myNumber = [NSNumber numberWithInt:1];
NSString *predicateFormatByHand = [NSString stringWithFormat:#"self MATCHES 'chp%#_img[0-9]+\\.png'", myNumber];
NSPredicate *firstPredicate = [NSPredicate predicateWithFormat:predicateFormatByHand];
NSLog(#"firstPredicate description: %#", firstPredicate);
NSPredicate *secondPredicate = [NSPredicate predicateWithFormat:#"self MATCHES 'chp%#_img[0-9]+\\.png'", myNumber];
NSLog(#"secondPredicate description: %#", secondPredicate);
This outputs:
firstPredicate description: SELF MATCHES "chp1_img[0-9]+.png"
secondPredicate description: SELF MATCHES "chp%#_img[0-9]+.png"
I would expect these descriptions to be the same.
Can someone explain why they are not?
(Following this question I've played with various escape sequences for the embedded single-quotes but when doing so keep having NSPredicate complain that it cannot then parse the format string. I'd be grateful to know what's going on.)
UPDATE: one answer suggested it's an issue with using NSNumber rather than an int, so:
NSPredicate *thirdPredicate = [NSPredicate predicateWithFormat:#"self MATCHES 'chp%d_img[0-9]+\\.png'", [myNumber intValue]];
NSLog(#"thirdPredicate description: %#", thirdPredicate);
I began with this originally, but alas the output is the same:
thirdPredicate description: SELF MATCHES "chp%d_img[0-9]+.png"
(Something means the format specifier is not evaluated.)

The answer is simple: the parser used by NSPredicate assumes that anything inside the quote marks is a string literal, and does not attempt to do any substitutions on its contents. It you need to have a dynamic string value, you will have to build the string before substituting it into the predicate format string, as in your first example.

...because the predicate is not such thing than string.
for any of the predicates you should use two format specifier 100% safety only:
one for the key (%K); and
one for the value (%#);
you cannot format neither the key nor the value when you add them to the predicate. this is why your second (and third) predicates are not formatted inside the value.
you can format the value before you add it to the predicate like:
NSNumber *myNumber = [NSNumber numberWithInt:1];
NSString *string = [NSString stringWithFormat:#"chp%#_img[0-9]+\\.png", myNumber];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", string];
NSLog(#"%#", predicate);
the result is:
SELF MATCHES "chp1_img[0-9]+\\.png"
...and never forget my first sentence: the predicates and the strings are not the same thing.

My interpretation of
%# is a var arg substitution for an object value
in the "Predicate Programming Guide" is that %# can only be used for substituting a value that a Core Data object can be compared against. For example
NSNumber *myNumber = [NSNumber numberWithInt:1];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"count = %#", myNumber];
is perfectly valid if "count" is a Number attribute of the entity. It is similar to binding values to SQLite prepared statements.
If %# could be used for general string formatting in predicates, then there would be no need to have two different format specifiers %K and %# for key paths and values.

Related

Check if strings in an array contain a punctuation or numeric substring?

My method currently checks if a substring exists within an array, but I was wondering if there's a way for it to also work with punctuation and numbers. Say I have an array of:
#[#"apple", #"!!", #"apps!", #"banana"];
and my substring is #"!", I want it to spit out: #"!!", and #"apps!"
Or another example:
#[#"apple", #"6:30", #"6 Pizzas", #"26"];
and my substring is #"6", I want it to spit out: #"6:30", and #"6", and #"6 Pizzas"
Is there a way of doing this using NSRange substringRange = [wordsArray rangeOfString:currentWord];
At the moment punctuation characters aren't being recognized. :/
Using NSPredicates makes this work much easier.
NSArray *stringArray = #[#"apple", #"!!", #"apps!", #"banana"];
NSPredicate *predicate = [NSPredicate PredicateWithFormat:#"SELF CONTAINS[cd] '%#'",searchChar];
NSArray *resultArray = [stringArray filteredArrayUsingPredicate:predicate];

NSPredicate with format has to be reversed when two format strings are used

I have a NSManagedObject with the following attributes:
status
kind
priority
Now I want to be able to filter my entity with these attributes respectively. So I would expect that I have to have a predicate along those lines:
status CONTAINS[c] ‘open’
I get really weird results, as soon as I have two variables in my predicate and I have to reverse the order of kind and value in my case so that I get the desired results:
NSString *kind = #"status"; // DEBUGGING
NSString *value = #"open"; // DEBUGGING
// This works although it defies all logic
NSString *predicate = [NSString stringWithFormat:#"('%#' CONTAINS[c] %#)", value, kind];
self.myFilterPredicate = [NSPredicate predicateWithFormat:predicate];
This however, does not work for some reason:
NSString *predicate = [NSString stringWithFormat:#"(%# CONTAINS[c] ‘%#‘)”, kind, value];
I cannot reproduce the exact problem, but generally you should not use stringWithFormat to create predicate. It causes problems as soon as the substituted key or value contain
any special characters like spaces or quotation marks.
A better way is
self.myFilterPredicate = [NSPredicate predicateWithFormat:#"%K == %#", kind, value];
%K is a placeholder to be replaced by a key path such as "status".

Why %# behave differently between predicateWithFormat and stringWithFormat?

With predicateWithFormat, %# becomes surrounded by "". We need to use %K for keys.
For example [NSPredicate predicateWithFormat #"%# == %#" ,#"someKey",#"someValue"] becomes
"someKey" == "someValue"
While at stringWithFormat, %# is not surrounded by ""
[NSString stringWithFormat #"%# == %#" ,#"someKey",#"someValue"]
becomes someKey == someValue
Why the difference?
Am I missing something?
Why use %# for "Value" in predicateWithFormat because it's not what %# mean in stringWithFormat. Why not create a new notation, say %V to get "Value" and %# remain Value like in the stringWithFormat counterpart.
Why Apple decides that the same symbol, namely %# should mean differently.
They really are different right? Am I missing anything?
String variables are surrounded with quotation marks in predicates while dynamic properties (and hence keypaths) are not quoted. Consider this example:
Lets say we have an array of people:
NSArray *people = #[
#{ #"name": #"George", #"age": #10 },
#{ #"name": #"Tom", #"age": #15 }
];
Now if we wanted to filter our array in order to find all persons by name, we would expect a predicate that would expand to something like this:
name like[c] "George"
That way we say that name is a dynamic key and George is a constant string.
So, if we used a format like #"%# like[c] %#" the expanded predicate would be:
"name" like[c] "George"
which is clearly not what we want (here both name and George are constant strings)
So the correct way to build our predicate would be:
NSPredicate *p = [NSPredicate predicateWithFormat:#"%K like[c] %#", #"name", #"George"];
I hope that this makes sense. You can find much more on predicates in Apple's documentation here
Well, NSPredicate is a function to evaluate some string, Look at this example
and NSString stringWithFormat only copy the value that is given to the corresponding place -- %#.
The usage is totally different, and you can do a lot of complex operation with NSPredicate.

NSPredicate that ignores commas?

I have implemented a UISearchDisplayController that allows users to search a table. Currently the predicate I am using to search is as follows,
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:#"Name contains[cd] %#", searchText];
Now lets say a users searches for "beans, cooked" the corresponding matches are found in the table. But if the user enters the search text as "beans cooked" without the comma, there will be no matches found.
How can I re-write my predicate to "ignore" the commas when searching? In other words how can I re-write it so that it views "beans, cooked" being equal to "beans cooked" (NO COMMMA)?
First a disclaimer:
I think that what you are trying to do is to add some "fuzzyness" to your search algorithm, seen that you want to make your match insensitive to certain differences in user input.
Predicates (which are logic constructs) are by their very nature not fuzzy, so there is an underlying impedance mismatch between the problem and the tool chosen.
Anyway, one way to go about it could be to add a method to your model object class.
In this method, you can clean your name string so it only contains the most basic characters, say numbers, ascii letters and a space.
Being totally deterministic, such a method is effectively a read-only string property on your object, and as such it can be used to match in predicates.
Here is an implementation that removes punctuation, accents and diacritics:
- (NSString *)simplifiedName
{
// First convert the name string to a pure ASCII string
NSData *asciiData = [self.name dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *asciiString = [[[NSString alloc] initWithData:asciiData encoding:NSASCIIStringEncoding] lowercaseString];
// Define the characters that we will allow in our simplified name
NSString *searchCharacters = #"0123456789 abcdefghijklmnopqrstuvwxyz";
// Remove anything else
NSString *regExPattern = [NSString stringWithFormat:#"[^%#]", searchCharacters];
NSString *simplifiedName = [asciiString stringByReplacingOccurrencesOfString:regExPattern withString:#"" options:NSRegularExpressionSearch range:NSMakeRange(0, asciiString.length)];
return simplifiedName;
}
Now, a predicate could be made to search in the simplified name:
NSPredicate *pred = [NSPredicate predicateWithFormat:#"self.simplifiedName = %#", searchString];
You would of course want to clean the search string using the same algorithm used to clean the name, so it would probably be a good idea to factor it out into a general method to be used in both places.
Last, the simplifiedName method can also be added by implementing a category to the model object class so you don't have to modify its code, which is handy in case your object class is defined in an auto-generated file by Core Data.
This may be a bit hacky, but you could just remove the comma from the search term.
Example:
searchText = [searchText stringByReplacingOccurrencesOfString:#"," withString:#""];
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:#"Name contains[cd] %#", searchText];
The best solution I found for this type of problem is to actually add an entry in each items dictionary that has the same name but will all punctuations, commas, dashes, etc. removed like in this answer

Writing an NSPredicate that returns true if condition is not met

I currently have the following piece of code
NSPredicate *pred = [NSPredicate predicateWithFormat:#"SELF contains '-'"];
[resultsArray filterUsingPredicate:pred];
This returns an array with the elements which contain '-'. I want to do the inverse of this so all elements not containing '-' are returned.
Is this possible?
I've tried using the NOT keyword in various locations but to no avail. (I didn't think it would work anyway, based on the Apple documentation).
To further this, is it possible to provide the predicate with an array of chars that I don't want to be in the elements of the array? (The array is a load of strings).
I'm not an Objective-C expert, but the documentation seems to suggest this is possible. Have you tried:
predicateWithFormat:"not SELF contains '-'"
You can build a custom predicate to negate the predicate that you already have. In effect, you're taking an existing predicate and wrapping it in another predicate that works like the NOT operator:
NSPredicate *pred = [NSPredicate predicateWithFormat:#"SELF contains '-'"];
NSPredicate *notPred = [NSCompoundPredicate notPredicateWithSubpredicate:pred];
[resultsArray filterUsingPredicate:pred];
The NSCompoundPredicate class supports AND, OR, and NOT predicate types, so you could go through and build a large compound predicate with all the characters you don't want in your array, then filter on it. Try something like:
// Set up the arrays of bad characters and strings to be filtered
NSArray *badChars = [NSArray arrayWithObjects:#"-", #"*", #"&", nil];
NSMutableArray *strings = [[[NSArray arrayWithObjects:#"test-string", #"teststring",
#"test*string", nil] mutableCopy] autorelease];
// Build an array of predicates to filter with, then combine into one AND predicate
NSMutableArray *predArray = [[[NSMutableArray alloc]
initWithCapacity:[badChars count]] autorelease];
for(NSString *badCharString in badChars) {
NSPredicate *charPred = [NSPredicate
predicateWithFormat:#"SELF contains '%#'", badCharString];
NSPredicate *notPred = [NSCompoundPredicate notPredicateWithSubpredicate:pred];
[predArray addObject:notPred];
}
NSPredicate *pred = [NSCompoundPredicate andPredicateWithSubpredicates:predArray];
// Do the filter
[strings filterUsingPredicate:pred];
I make no guarantees as to its efficiency, though, and it's probably a good idea to put the characters which are likely to eliminate the most strings from the final array first so that the filter can short-circuit as many comparisons as possible.
I'd recommend NSNotPredicateType as described in the Apple documentation.