Why %# behave differently between predicateWithFormat and stringWithFormat? - objective-c

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.

Related

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

How to filter NSDictionary by substring in its keys?

Say I have an NSDictionary and its keys are like this:
#"123_000"
#"223_000"
#"123_111"
#"223_111"
and so on. I want to get a NSArray or NSSet of all values whose corresponding keys contain substring #"123".
Of course I can just loop over the NSDictionary, but I suspect that there must be a less code heavy approach, probably involving KVC or NSPredicate, but I'm not really good in either of them.
Yes, use NSPredicate.
First get allKeys from the dictionary. Then use filteredArrayUsingPredicate: to get the list of keys you want. Then use objectsForKeys:notFoundMarker: with your resulting array of keys to get the associated objects (the not found marker isn't an issue as we know all keys exist).
To correct the answer of VaaChar:
In my example, I have a dictionary with keys and values only (vegan : 1, steak : 2, ...)
To check if my string is within these keys, I use:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF CONTAINS %#", keyword];
NSArray *categories = [[self.categoriesAndIDs allKeys] filteredArrayUsingPredicate:predicate];
SELF represents the current object of the given array, in my case [self.categoriesAndIDs allKeys] so every string within the array will be checked for my "keyword", e.g. "steak" or "st" and the result will be an array with a string called "steak".
You could use something like this:
NSArray *resultArray = [[mainDict allValues] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"self.#allKeys CONTAINS[c] %#", #"123"]];
"self.#allKeys" should return all keys of your dictionary and "CONTAINS[c] %#" checks if the key contains "123".

NSPredicate predicateWithFormat not evaluating format specifier

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.

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

NSPredicate with 64-bit integers?

I'm trying to use NSPredicate with 64-bit numbers, but for some reason it's failing. I create a predicate with the following string:
[NSString stringWithFormat:#"UID == %qu", theUID]
And it always fails. Yet if I loop through my objects and compare if ( UID == theUID ) I find one that is fine. I'm really confused as to why this isn't working correctly.
Does the above look correct? My predicates work fine for other integers or even strings. But this seems to be failing.
Thanks!
Edit: So strange... so I create my predicate by doing:
NSPredicate *myPredicate = [NSPredicate predicateWithFormat:myString];
Now when I print myString, and then print myPredicate (by doing NSLog(#"%#", blah);) I get:
String: UID == 17667815990388404861 Predicate: UID ==
9223372036854775807
It's the same string, why are these different?
Possibly theUID is not an unsigned value?
A more robust way to build predicates would be to pass in an NSNumber:
NSNumber *theUIDNum = [NSNumber numberWithUnsignedLongLong:theUID];
[NSString stringWithFormat:#"UID == %#", theUID]
This gives you the bonus ability to debug and print out the value of theUINum to make sure it got the transition right.
In your NSPredicate you can use something like this
let myNSNumber = NSNumber(value: theUID)
let predicate = NSPredicate(format: "UID = %i", myNSNumber.int64Value)