Writing an NSPredicate for 'CONTAINED_BY' text query - ios

I am trying to construct a query of a Core Data store which retrieves an entity's attribute values when they occur in longer string;
i.e, instead of seeking instances where attribute value contains a (shorter) string :
request.predicate = [NSPredicate predicateWithFormat:#"carBrand contains[c] 'merced'"]
I want to find instances (of the entity) whose attribute values are found 'contained in' an arbitrary (longer) string :
NSString* textString = #"Elaine used to drive Audis, but now owns a Mercedes";
request.predicate = [NSPredicate predicateWithFormat:#"%# contains[c] carBrand", textString ];
(ie. retrieve array holding objects with carBrand = #"Audi" and carBrand = #"Mercedes")
In my attempts, NSPredicate doesn't seem to like expressions with the attribute name on the right hand side and throws an error...
[__NSCFConstantString countByEnumeratingWithState:objects:count:]:
unrecognized selector sent to instance 0x
...is there a way of constructing such a query with the attribute name on the left hand side - a 'contained_by' query, as it were?
PS. Searching SO, I've only found solutions by splitting the text into component words which, in my scenario, would be less than ideal! Is this the only type of approach that's viable?

Build a regex string with your array and use MATCHES in your predicate.
[NSPredicate predicateWithFormat:#"%# MATCHES '*(Audi|Mercedes)*'", testString];
To filter cars based on their brand:
NSArray *brands = [#"Audi", #"Mercedes"];
[NSPrediate predicateWithFormat:#"carBrand IN %#", brands];

Decided to try implementing a componentsSeparatedByString approach to build a NSCompoundPredicate
//find alphabetic words omitting standard plurals
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"(?:[^a-z]*)([a-z]+?)(?:s)?\\W+" options:NSRegularExpressionCaseInsensitive error:nil];
//separate with pipe| and split into array
NSString *split = [regex stringByReplacingMatchesInString:textString options:0 range:NSMakeRange(0, speciesString.length) withTemplate:#"$1|"];
NSArray *words = [split componentsSeparatedByString:#"|"];
//build predicate List
NSMutableArray *predicateList = [NSMutableArray array];
for (NSString *word in words) {
if ([word length] > 2) {
NSPredicate *pred = [NSPredicate predicateWithFormat:#"brandName beginswith[c] %#", word];
[predicateList addObject:pred];
}
}
//CarBrand* object;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"CarBrand" inManagedObjectContext:self.managedObjectContext];
request.predicate = [NSCompoundPredicate orPredicateWithSubpredicates:predicateList];
NSError *error =nil;
NSArray *results = [self.managedObjectContext executeFetchRequest:request error:&error];
This retrieves the instances found in the text;
eg1:
#"Elaine used to drive Audis, but now owns a Mercedes";
gives array of objects whose .brandname = "Audi", "Mercedes", respectively .
eg2: #"Among the cars stolen were a Ford Mondeo, a Fiat 500C and an
Alfa-Romeo Spyder"
yields .brandname = "Ford", "Fiat",and "Alfa Romeo"(NB no '-') respectively.
I'm not yet accepting my own answer as it seems too much of a workaround and won't easily extend to (for example) extracting brand-name AND, say, model.
Hopefully, someone will have a better solution!

Related

CompoundPredicate NSPredicate

I have for example three NSPredicates.
NSPredicate *pred1 = [NSpredicate ...];
NSPredicate *pred2 = [NSpredicate ...];
NSPredicate *pred3 = [NSpredicate ...];
And I have from server string how I need combine predicates.
For Example
(1 OR 2 OR 3)
(1 OR 3) AND 2
3 OR (1 AND 2)
How can I combine my three predicates on the right conditions and given the brackets?
Thank you
You can always create nested NSCompoundPredicate like this
NSPredicate *orPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:#[firstPredicate, secondPredicate]];
NSPredicate *andPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:#[thirdPredicate, fourthPredicate]];
NSPredicate *finalPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:#[orPredicate, andPredicate]];
One approach is to make a "truth table inputs" from your predicates, make a combined predicate based on the string, and evaluate it using truth table data.
In order for the predicate to work on a truth table you need to convert (1 OR 2) AND 3 to (SELF[1]==YES OR SELF[2]==YES) AND SELF[3]==YES format. You can do it with regular expressions:
NSString *combine = #"(1 OR 2) AND 3";
NSError *err;
NSRegularExpression *regex = [NSRegularExpression
regularExpressionWithPattern:#"\\d+"
options:0
error:&err];
NSString *subst = [regex
stringByReplacingMatchesInString:combine
options:0
range:NSMakeRange(0, combine.length)
withTemplate:#"SELF[$0]==YES"];
NSLog(#"%#", subst);
Next, you can build NSPredicate:
NSPredicate *pred = [NSPredicate predicateWithFormat:subst];
Finally, you need to build your truth table. Evaluate predicates one by one, and place the results into NSArray. Place an unused element at index zero, because your substitution strings use one-based indexing.
Now you are ready to use the predicate that you prepared earlier:
NSArray *data1 = #[#(NO), #(YES), #(NO), #(YES)];
NSArray *data2 = #[#(NO), #(NO), #(NO), #(YES)];
NSLog(#"%d", [pred evaluateWithObject:data1]); // Prints 1
NSLog(#"%d", [pred evaluateWithObject:data2]); // Prints 0
I used fixed values for my truth tables data1 and data2. You would need to evaluate predicates pred1..predN, and put the results into positions 1..N of the NSArray.
you can use this code ex:
NSPredicate *p1 = [NSPredicate ];
NSPredicate *p2 = [NSPredicate "];
NSPredicate *p3 = [NSPredicate "];
in final predicate
NSPredicate *p = [NSCompoundPredicate andPredicateWithSubpredicates: #[p1, p2,p3]];
this work like (p1 AND p2 AND p3)
you can also use orPredicateWithSubpredicates,notPredicateWithSubpredicate with nscompoundpredicate

NSPredicate to check whether a string of numbers separated by comma contains a number

I have a string say, "0,1,2,3,4,5,6,7,8,9,10,11" (Assumed to be months index string) in my Core Data. I want to use a predicate to fetch whether that field contains a number say, string contains '0'.
We cannot use 'CONTAINS' since '0' is also present in '10'. I need to fetch the object using NSPredicate to avoid loops from Core Data.
Update:
I just want to test whether '0,1,2,3,4,5,6,7,8,9,10,11' contains '0' or not using NSPredicate.
Solved
The "MATCHES" operator of NSPredicate can be used to compare against a regular expression:
NSString *searchTerm = #"0";
NSString *regex = [NSString stringWithFormat:#"(.*,)?%#(,.*)?", searchTerm];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"strIndex MATCHES %#", regex];
This worked fine for me. Thank you #skagedal for the suggestion.
I got this answer from Form NSPredicate from string that contains id's.
#bunty's answer is directly correct, but there is a meta answer.
You should probably have a Months entity that contains the 12 months of the year in the data store. Then, have a one-to-many (ordered or not depending on needs) relationship from the thing that contains that string to the Months entity.
Then, you simply follow the relationship (i.e. myThing.months) to fetch the months that the entity needs.
For large databases, fetches using string matching predicates are going to be quite slow and it really is a bit of anti-pattern.
As bunty writes, you can use regular expressions. If I understand the question, you want a predicate that looks for a specific string of digits – but only if not directly preceded or followed by other digits. This can be achieved with what in regular expressions is called look-behind and look-ahead assertions. More specifically, negative look-behind/look-ahead assertions.
Take a look at the syntax for regular expressions used by NSPredicate. To search for the number 1, without also finding 11 and 10, use the regular expression:
(?<!\d)1(?!\d)
So you can build your Core Data predicate with something like:
NSString *month = #"11"; // the number you're looking for
NSString *regex = [NSString stringWithFormat:#"(?<!\d)%#(?!\d)", month];
NSPredicate *myTest = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", regex];
This will not be a very effective search, but if that's what your Core Data model looks like and you can't easily change it, it just might work fine. If you can, I'd go with bbum's suggestion and reorganize your model.
You can check numbers separated by comma by using regular expression as below:
NSString *str = #"0,1,2,3,4,5,6,7,8,9,10,11";
NSString *regex = #"(\\d+)(,\\s*\\d+)*";
NSPredicate *myTest = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", regex];
if ([myTest evaluateWithObject: str])
NSLog(#"matched");
else
NSLog(#"not matched");
So use this predicate while fetching data from core data with field name as below:
NSError* error = nil;
NSString *regex = #"(\\d+)(,\\s*\\d+)*";
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"TableName" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"MonthFieldName MATCHES %#", regex];
[fetchRequest setPredicate:predicate];
NSArray* arrRecords = [context executeFetchRequest:fetchRequest error:&error];

use NSPredicate to filter object

Here say I have a array of objects with two attributes:
// array of object
NSArray *objects
// object
NSString *primaryTag;
NSArray *secondaryTag;
Since what I want is when the this object contains the givenTag, it could be passed to a new array called results;
Here is my codes:
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:#"primaryTag == %# || secondaryTag CONTAINS[c] %#", givenTag, givenTag];
results = [objects filteredArrayUsingPredicate:resultPredicate];
It seems that the primaryTag works well, but the secondaryTag doesn't work, can someone help me out. I am not that familiar with NSPredicate filtering. Thanks in advance.
The most efficient way to do that is with a NSCompoundPredicate like so:
NSArray *subPredicates = #[tag1, tag2, tag3];
NSPredicate *compoundPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
Your question is a little unclear so you might also want:
andPredicateWithSubpredicates
Depending on exactly what the nature of the result set you are looking for.
See Apple Docs here: NSCompoundPredicate Docs
i implemented the following custom class:
#interface CustomObject : NSObject
#property (copy, nonatomic) NSString *primaryTag;
#property (strong, nonatomic) NSArray *secondaryTag;
#end
and overrode it's description method for the NSLog statement to print something we understand:
- (NSString *)description {
return [NSString stringWithFormat:#"primaryTag: %#, secondaryTag: %#", _primaryTag, [_secondaryTag componentsJoinedByString:#", "]];
}
then i created some objects from the custom class and added them to an array:
NSMutableArray *objects = [NSMutableArray array];
CustomObject *obj1 = [CustomObject new];
obj1.primaryTag = #"stringToSearchFor";
obj1.secondaryTag = #[#"notTheStringToSearchFor", #"somethingElse"];
[objects addObject:obj1];
CustomObject *obj2 = [CustomObject new];
obj2.primaryTag = #"differentString";
obj2.secondaryTag = #[#"nothingWeAreLookingFor"];
[objects addObject:obj2];
CustomObject *obj3 = [CustomObject new];
obj3.primaryTag = #"anotherOne";
obj3.secondaryTag = #[#"whoCaresForThisString", #"stringToSearchFor"];
[objects addObject:obj3];
finally i created a string to search for and the predicate:
NSString *givenTag = #"stringToSearchFor";
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:#"primaryTag == %# || secondaryTag CONTAINS[c] %#", givenTag, givenTag];
when i log out the result i get the correct results:
NSLog(#"%#", [objects filteredArrayUsingPredicate:resultPredicate]);
logs:
(
"primaryTag: stringToSearchFor, secondaryTag: notTheStringToSearchFor, somethingElse",
"primaryTag: anotherOne, secondaryTag: whoCaresForThisString, stringToSearchFor"
)
which is obj1 and obj3. correct! if it does not work for you there's gotta be something else wrong with your code...
If my understanding of the original question is incorrect, please let me know, and I will adjust my answer.
Problem: You have an array of objects with 2 properties. One is primaryTag, which is a string. The second is an array of secondaryTags, which is a collection of strings. You want to filter all objects where either the primaryTag matches, or where the search string matches one of the secondaryTags.
Answer The proper way to match strings is via MATCHES or CONTAINS.
NSPredicate *pPredicate =
[NSPredicate predicateWithFormat:#"%K CONTAINS[cd] %#",
#"primaryTag", searchString];
NSPredicate *sPredicate =
[NSPredicate
predicateWithFormat:#"SUBQUERY(%K, $st, $st CONTAINS[cd] %#).#count > 0",
#"secondaryTags", searchString];
NSCompoundPredicate *searchPredicate =
[NSCompoundPredicate orPredicateWithSubPredicates:#[ pPredicate, sPredicate ]];
How it works: The first predicate is a straightforward match. You can replace CONTAINS with MATCHES, if that better fits the kind of comparison you wish to make. The [cd] suffix means case-insensitive and diacritic-insensitive. It's normal to include those when searching/filtering, but again, it's up to you. Instead of embedding the property name in the predicate format string, I use %K and a replacement parameter. In production code, that replacement parameter would be a constant.
The second predicate is a little trickier. It uses a SUBQUERY() to filter the secondaryTags array, and returns the object as matching if at least one secondary tag matches the search string. SUBQUERY() is a function with 3 parameters. The first is the collection being searched. The second is a temporary variable that represents each item in the collection, in turn; it is used in the 3rd parameter. The 3rd parameter is a regular predicate. Each item in the collection that matches the filter is included in the output of SUBQUERY(). At the end, the matching secondary tags are counted (via #count), and if the count is greater than zero, the original object is considered to have matched, so will be included in the filtered output.
Finally, we combine these two predicates into one searchPredicate, which can now be used to filter your array of objects.
I seen this issue,
My normal approch is to use the NSPredicate twice,
So that I can track the result at every steps:
Option 1:
NSPredicate *resultPredicate1 = [NSPredicate predicateWithFormat:#"primaryTag == %#", givenTag];
results1 = [objects filteredArrayUsingPredicate:resultPredicate1];
NSPredicate *resultPredicate2 = [NSPredicate predicateWithFormat:#"secondaryTag CONTAINS[c] %#", givenTag];
finalResults = [results1 filteredArrayUsingPredicate:resultPredicate2];
Option 2:
Use NSCompoundPredicate to compound multiple filtering. You can easily find many examples on google and stackOverFlow.
Hope this will help,
Thanks

NSPredicate match some words not word to word

I am taking some text from a UITextView and using NSPredicate to take that text and see if it matches the data in my data model and database. currently I have to type in exactly word by word to see if it matches. I am using it like this:
- (IBAction)viewResults:(id)sender {
NSEntityDescription *entitydesc = [NSEntityDescription entityForName:#"Chapters" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
[request setEntity:entitydesc];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"theverse CONTAINS[cd] %#",self.textViewVerse.text];
[request setPredicate:predicate];
NSError *error;
NSArray *matchingData = [context executeFetchRequest:request error:&error];
if(matchingData.count <=0)
{
self.theResults.text = #"NO Verse found";
}
else {
NSString *theVerse = nil;
NSString *chapterNumber = nil;
NSString *chapterName = nil;
NSString *verseNumber = nil;
for (NSManagedObject *obj in matchingData){
theVerse = [obj valueForKey:#"theverse"];
chapterNumber = [obj valueForKey:#"chapternumber"];
chapterName = [obj valueForKey:#"chaptername"];
verseNumber = [obj valueForKey:#"versenumber"];
}
self.theResults.text = [NSString stringWithFormat: #"Chapter Name: %# \n\nChapter Number: %# \n\nVerse Number: %# \n\nVerse: %#",chapterName, chapterNumber, verseNumber, theVerse];
}
}
So if I type in "His wealth and his children will not benefit him" and hit the viewResults button, it will find the verse in the database and display it with its relevant details. However, if I type in "His wealth and his children will not benefit himself" it will display "NO Verse found". Is there a way I can view the verse without having to type in word to word or a word which does not match and still find what I want to find?
You might want to look at the discussion (particularly the comments) that I had on a somewhat similar question on detecting combinations of strings
In your case, you could map all similar words to a unique key, then search for combinations of keys.

how to find a particular string from a list of strings?

I have to get a particular string from a list of strings using NSPredicate (CoreData). For eg:
if I have the following array from CoreData
Helloworld
helloworld1234
helloworld 12345
from the above list I need only 1 and 3 one as result.
I have tried the following but i get all the three as result
NSPredicate *predicate =[NSPredicate predicateWithFormat:#"contactName CONTAINS [c]%#",[arySeperator objectAtIndex:1]];
NSArray *filteredArray1 = [arrayContacts filteredArrayUsingPredicate:predicate];
Where I am going wrong?
To find all entries that contain the given text as a "whole word", you can use the
predicate "MATCHES" operator, which matches against a regular expression, and the
word boundary pattern \b:
NSString *searchWord = #"Helloworld";
NSString *pattern = [NSString stringWithFormat:#".*\\b%#\\b.*", searchWord];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"contactName MATCHES[c] %#", pattern];
This should also work if you add this predicate to the Core Data fetch request,
instead of filtering an already fetched array of managed objects.
LIKE
The left hand expression equals the right-hand expression: ? and * are allowed as wildcard characters, where ? matches 1 character and * matches 0 or more characters.
NSString *textSearch = #"Raja";
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:#"firstname LIKE '*%1$#*' OR lastname LIKE '*%1$#*'", textSearch];
You can use below code to get if a string is present in an NSArray:
NSArray* yourArray = [NSArray arrayWithObjects: #"Str1", #"Str2", #"Str3", nil];
if ( [yourArray containsObject: yourStringToFind] ) {
// do found
} else {
// do not found
}

Resources