NSPredicate: how to treat strings as numbers? - ios

I am building a compound NSPredicate to make a fetch request, using core data on sqlite, within iOS app. Everything already works fine, but I am not being able to include the last condition. The reason is quite simple: I need to check if the value, stored as a string, is within certain float bounds. The problem is that the conditions are checked on alphabetical order basis, and not according its float value. Here it is the code:
NSString * conditionToBeAdded = [NSString stringWithFormat:#" && (%# >= \"""%#\""") && (%# <= \"""%#\""")", propertyName, myMinFloat, propertyName, myMaxFloat];
stringForPredicate = [stringForPredicate stringByAppendingString:conditionToBeAdded];
NSPredicate * myPredicate = [NSPredicate predicateWithFormat:stringForPredicate];
Does anybody know how to build an NSPredicate object able to check string values as numbers, to be used within a coredata request?
Thank you.

Since NSPredicates allow you to use everything which matches Key-Value Coding approach you can call on NSStrings methods like: intValue or floatValue
NSString * conditionToBeAdded = [NSString stringWithFormat:#" && (propertyName.floatValue >= %f ) && (propertyName.floatValue <= %f)", myMinFloat, myMaxFloat];
stringForPredicate = [stringForPredicate stringByAppendingString:conditionToBeAdded];
NSPredicate * myPredicate = [NSPredicate predicateWithFormat:stringForPredicate];
I hope that helps.

You will want to change that string to a number and fix it properly. String searching is SLOW and should be avoided. Putting numbers into strings is a big performance hit with no gain.
As for your actual predicate, you can do a < or > comparison on strings. I suspect all of those quotation marks you have in the predicate are throwing things off. Quotation marks are not needed in predicates unless you are setting a constant which in this case you are not. Remove them and try it again.

Related

how to compare only selected strings in a text with given input in objective-c

i know that the question sounds wears.I couldn't find a better way to put it so i will take my time to explain the question i m struggling with.
I have an iPhone app that takes input from user.And i got a plist ( i will convert it to a online database soon) What i currently do is this. I compare my input string with ingredients part of items in my plist.
This is the plist format
<array>
<dict>
<key>category</key>
<string>desert</string>
<key>numberOfPerson</key>
<string>3</string>
<key>recipeImage</key>
<string>asd.jpg</string>
<key>time</key>
<string>15</string>
<key>recipeName</key>
<string>Puding</string>
<key>recipeDetail</key>
i compare the input with recipeIngredients.But what my codes do is not what i need.If the comparison turns true i just list every item from my plist that contain the input ingredients.I can filter through selected recipes but what i want is this: Unless there is a full match up with input and ingredients i do not want to show it.
The problem is this. I got my recipe ingredients like this format 1 spoon of sugar, 1 spoon of salt, 100g chicken.
The user enter inputs like - salt , sugar. chicken so i can not fully compare it.It will never be the same so i can not show anything.
How can i accomplish this.
i m open for any kind of suggestions.
This is how i compare
results = [arrayOfPlist filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
NSDictionary *_dataRow = (NSDictionary *)evaluatedObject;
return ([[[_dataRow valueForKey:#"recipeIngredients"] lowercaseString] rangeOfString:[searchText lowercaseString]].location != NSNotFound);
}]];
where searchText is my input.
First of all, you'll never know if there is a typo in user input.
But what you can do is before you compare two strings, you can do a little bit trimming for a given character set.
There is a method in NSString class called :
- (NSString *)stringByTrimmingCharactersInSet:(NSCharacterSet *)set
If you want to get rid of . or - characters, you need to specify them in your character set. Than, you can compare two strings.
Using -[NSPredicate predicateWithFormat:] you can do database-esque string comparisons. For instance, you could try
[NSPredicate predicateWithFormat:#"recipeIngredients CONTAINS[cd] %#", searchText]
Check out https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Predicates/Articles/pSyntax.html the section called "String Comparisons"
EDIT: if the user will be searching multiple things at once, like "chicken, noodle," you can be a little more fancy and do:
NSArray *tokens = [[searchText componentsSeparatedByCharactersInSet:NSCharacterSet.alphanumericCharacterSet.invertedSet] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"length > 0"];
NSPredicate *searchPredicate = [NSPredicate predicateWithFormat:#"recipeIngredient CONTAINS[cd] (ANY %#)", tokens]
You should split up the searchText into an array by using -componentsSeparatedByString:#",", and then loop through the array to see if the recipeIngredients contains any of the ingredients in the searchText array. In order to work out if the query contains every single ingredient, you can create an integer inside of the block and increment it everytime you have a match. If the number of matches is equal to the number of ingredients, then you can go from there.
The code below builds up a predicate that boils down to "ingredients contains sugar and ingredients contains chocolate"
NSArray* recipes = #[
#{#"recipeIngredients": #"sugar flour chocolate"},
#{#"recipeIngredients": #"sugar chocolate"},
#{#"recipeIngredients": #"flour chocolate"},
#{#"recipeIngredients": #"chocolate"},
];
NSString* search = #"sugar, chocolate";
// split the ingredients we have into an array of strings separated by ',' or ' '
NSArray* haves = [search componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#", "]];
// Build a list of recipeIngredients CONTAINS have
NSMutableArray* ands = [NSMutableArray new];
for(NSString* have in haves)
{
if(have.length > 0)
{
[ands addObject:[NSPredicate predicateWithFormat:#"recipeIngredients CONTAINS[cd] %#", have]];
}
}
// String all the haves into a single long ... AND ... AND ... predicate
NSPredicate* predicate = [NSCompoundPredicate andPredicateWithSubpredicates:ands];
// Apply the predicate
NSArray* filtered = [recipes filteredArrayUsingPredicate:predicate];

issue in NSPredicate for NSString

In my application I am using CoreData.Amount values are stored in coreData as NSString. In my Interface Builder i have two textfields.When i enter any amount on my textfields This amounts will accept as Minimum Value and maximum Value.I want to get all amounts between minimum and maximum amounts i have entered.
What will be the solution.I just goes through this way Example It Not be worked on my project because i want to convert string to integer on my NSPredicate method.
My code is
NSPredicate *p1= [NSPredicate predicateWithFormat:#"(amount.intValue => %#) && (amount.intValue <=%#)", text1.intValue,text2.intValue]];
Note that amount is stored as NSString in coredata
NSPredicate *p1= [NSPredicate predicateWithFormat:#"(amount.intValue => %#) && (amount.intValue <=%#)", text1.intValue,text2.intValue]];
You shouldn't be comparing numbers and strings. Compare one or the other. In this case, you want to compare numbers. You should change your source data stored in the model to be a number.
=> should be >=
%d is an integer parameter format, and text1.intValue returns an integer. Using %# expects an on jest and won't do what you want.
Log the contents of the predicate so you can see what it contains. But mainly, change the type of the data in the model.
As you store amount as NSString, you need to pass it as NSNumber when doing the evaluation.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.intValue <= $max.intValue AND SELF.intValue >= $min.intValue"];
Now you can evaluate the amount to see if it is in range
if ([predicate evaluateWithObject:[NSNumber numberWithInt:amount.intValue] substitutionVariables:#{#"max":[NSNumber numberWithInt:text1.intValue], #"min":[NSNumber numberWithInt:text2.intValue]}])
{
//Got the correct amount
}
Currect Answer is
NSPredicate *p1= [NSPredicate predicateWithFormat:#"(amount >= %d) && (amount <= %d)", text1.intValue, text2.intValue]];

NSPredicate issue with greater than equals

I have a simple NSPredicate which is not giving correct result
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:#"SELF.data.squareFootage >= %#", filterSquareFootage];
filteredArray = [[filteredArray filteredArrayUsingPredicate:resultPredicate] mutableCopy];
Strangely this works for all 6000>=250, 7000>=250, 8000>=6000. But as soon as squareFootage==10000 the predicate for 10000>=any smaller value is false.
Note: 10000 for squareFootage is a max value fetched using a UISlider.If i reduce value to any smaller value, the predicate gives true as result
Am i missing something here and using predicate incorrectly?
I assume that your property is stored as string and not as number, therefore
the values are compared as strings:
"10000" < "250" < "6000"
Using NSNumber instead of NSString (or alternatively some scalar type as NSInteger) should fix the problem.
If you cannot change the data type of the squareFootage property, then the following
predicate should work:
[NSPredicate predicateWithFormat:#"SELF.data.squareFootage.intValue >= %d", [filterSquareFootage intValue]]
YOU CAN"T COMPARE STRINGS USING =< . if SELF.data.squareFootage is a NSNumber try converting filterSquareFootage to NSnumber or int value and compare them like that.

NSPredicate crash on CONTAINS?

I'm trying to do a simple predicate filter on an array of objects.
The objects in the array have 2 properties, displayValue and value. I am trying to filter based on a search string and I get a crash.
NSPredicate *pred = [NSPredicate predicateWithFormat:#"displayValue CONTAINS[cd] %#", searchString];
NSArray *results = [_data filteredArrayUsingPredicate:pred];
what exactly is incorrect about this format that it causes a Can't use in/contains operator with collection 100 (not a collection) crash?
I was able to reproduce your problem. This happens if the displayValue of one of the objects
is not a NSString, but some different type.
From your error message, I assume that you assigned an NSNumber, for example
obj.displayValue = #100;
somewhere in your code. The "CONTAINS" predicate works only with strings, so you must assign
only string values to the property.
Therefore, you should define the type of the property as
NSString * instead of id, and check the assignments to that property.
If you really need to keep the id type and store different kinds of objects in that property,
then you cannot use the "CONTAINS" operator in the predicate. A direct comparison
with #"displayValue == %#" would work, however.
UPDATE: As a workaround, you could use the description method, which converts any object
to a string, in particular it converts a NSNumber to its string representation. So the following could work:
[NSPredicate predicateWithFormat:#"displayValue.description CONTAINS[cd] %#", searchString];
The drawback is that the exact description format is not documented.
Another solution could be to use a block-based predicate, where you can check the type
of each object and perform the appropriate comparison.

NSPredicate for two NSNumber arrays

I have a bit of a hard time writing a predicate for my search functionality and thought you'd be bale to help. So basically I have two arrays of NSNumbers. I want my predicate to satisfy the following:
If a number's integerValue in array A matches any integerValue in array B.
I don't want to use any sort of loop for this solution. Here's what I have so far
ANY integerValue == ANY //how do I pass the entire array here and ask for the integerValue of each member?
The ANY operator will handle that.
Since it is a bit difficult to say from your question which of the arrays is "self" in normal predicate parlance, I'll write it without a self:
NSArray *arrayA = #[#2, #3, #7];
NSArray *arrayB = #[#2, #4, #9];
NSPredicate *pred = [NSPredicate predicateWithFormat: #"ANY %# IN %#", arrayA, arrayB];
Due to the lack of a "self", it will have to be evaluated with nil as the object, but that works fine:
BOOL matched = [pred evaluateWithObject: nil];
If you prefer to have a "self" in the predicate, you can just enter it:
NSPredicate *pred = [NSPredicate predicateWithFormat: #"ANY self IN %#", arrayB];
BOOL matched = [pred evaluateWithObject: arrayA];
The result is the same.
A small conceptual comment
The predicate above evaluates to true if any integer is included in both arrays, which is how I read your question.
This means that, conceptually speaking, you seem to be testing whether two sets of numbers intersect each other. NSSet's method intersectsSet: checks that, so another way to do the test would be to keep your numbers as sets and test for intersection:
matched = [setA intersectsSet: setB];
I know it's not precisely what you asked for (predicates and all) but another way is to use NSArray's - (id) firstObjectCommonWithArray:(NSArray *)otherArray, which would return nil if no common object can be found.
BOOL arraysIntersect = [array1 firstObjectCommonWithArray:array2] != nil;
One caveat though is that it would use its own object equality rules when comparing two objects, meaning if two objects are NSNumber instances, it will compare them using NSNumber's compare: method. But the same goes for the predicate-based solution proposed so far.

Resources