NSPredicate for multi-word search - nspredicate

In my iOS application I have a really simple predicate for my fetch controller.
NSString *format = [NSString stringWithFormat:#"name like[c] '%#'", nameVar];
NSPredicate *predicate = [NSPredicate predicateWithFormat:format];
[fetchController setPredicate:predicate];
It performs basic case-insensitive name lookup. Now I'd like to change it so that I could put a number of words in search box (nameVar has the value from search box) separated by spaces and have the predicate filter the results matching all those keywords.
So if I have two names:
"John Smith" and "Mary Smith" and I search for: "Smith M" I would like to have only one result but a search like that: "Sm th ith" should return both values.
Does anyone have an idea how should this be implemented?

edit back on a regular computer...
So there are a couple things to be aware of:
You don't need to put quotes around the substitution placeholder in your format string. When the method is building the predicate, it's going to be creating an abstract syntax tree of NSExpression and NSPredicate (specifically NSComparisonPredicate and NSCompoundPredicate) objects. Your string will be placed into an NSExpression of type NSConstantValueExpressionType, meaning that it's already going to be interpreted as a regular string. Placing the single quotes in the format string will, in fact, make your predicate non-functional.
You're not limited to a single comparison in a predicate. From the sounds of it, you want to have as many comparisons as you have "words" in your search string (nameVar). In that case, we'll break the nameVar up into its constituent words, and create a comparison for each word. Once we've done that, we AND them together to create a single overarching predicate. The code below does exactly that.
original answer
You can do this by building your own NSCompoundPredicate:
NSString *nameVar = ...; //ex: smith m
NSArray *names = ...; //ex: John Smith, Mary Smith
NSArray *terms = [nameVar componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSMutableArray *subpredicates = [NSMutableArray array];
for(NSString *term in terms) {
if([term length] == 0) { continue; }
NSPredicate *p = [NSPredicate predicateWithFormat:#"name contains[cd] %#", term];
[subpredicates addObject:p];
}
NSPredicate *filter = [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];
[fetchController setPredicate:filter];
Warning: typed in a browser on my iPhone.

Related

NSPredicate issue with characters in filter string

Need some briljant mind here ;-)
Im using an NSPredicate to filter an NSArray with a UISearchBar. This works but it has one issue. For example:
Search: Loreal
In the NSArray the Loreal is a brand.name value in the Objective C example. But the exact string is:
"L'Oreal"
I would like that my NSPredicate would find this value based on the search: Loreal
It should ignore special characters..
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(SELF.name LIKE[c] %#) OR (ANY SELF.brands.name contains[c] %#) OR (ANY SELF.brands.name contains[cd] %#) OR (ANY SELF.brands.name MATCHES[cd] %#)",searchText,searchText,searchText,searchText];

Cocoa: Extracting "A" from "Æ"

I have a bunch of NSStrings from which I would like to grab the first character of and match them up in the range A-Z and # as a catch all for things that don't apply.
Different graphemes (I believe that's the correct word after some wiki'ing) have been giving me trouble. For example, I would like to extract A from "Æ".
I have taken a look at CFStringTransform, normalize and fold but none of had the desired effect.
Is there a reliable way of doing this? All the strings I'm working with are UTF8 if that makes a difference.
Æ cannot be broken down into components. It is not a compound glyph of A+E, but is a separate glyph. Compound glyphs are things like a+`
The thing about "Æ" is that it is an ascii character in itself. Not a combination of two different characters so you can't extract the A from it because it is only 1 Character.
Edit:
Although you could perform a check to see if the String equals "Æ" and if it does tell it to switch it with "A" or convert it to its dec, form and subtract 81 which would give you an "A".
Did you want to get rid of all æ?
This should work if you do.
NSString *string = #"Æaæbcdef";
string = [string stringByReplacingOccurrencesOfString:#"æ" withString:#"a"];
string = [string stringByReplacingOccurrencesOfString:#"Æ" withString:#"A"];
Edit
Rereading, you only seem to want the first character:
NSString *string = #"Æaæbcdef";
NSString *firstChar = [string substringToIndex:1];
firstChar = [firstChar stringByReplacingOccurrencesOfString:#"æ" withString:#"a"];
firstChar = [firstChar stringByReplacingOccurrencesOfString:#"Æ" withString:#"A"];
NSString *finalString = [NSString stringWithFormat:#"%#%#", firstChar, [string substringFromIndex:1]];

Split NSString preserving quoted substrings

I need to split up a string which is separated by commas while preserving any quoted substrings (which may have commas too).
String example:
NSString *str = #"One,Two,\"This is part three, I think\",Four";
for (id item in [str componentsSeparatedByString:#","])
NSLog(#"%#", item);
This returns:
One
Two
"This is part three
I think"
Four
The correct result (respecting quoted substrings) should be:
One
Two
"This is part three, I think"
Four
Is there a reasonable way to do this, without re-inventing or re-writing quote-aware parsing routines?
Let's think about this a different way. What you have is a comma-seperated string, and you want the fields in the string.
There's some code for that:
https://github.com/davedelong/CHCSVParser
And you'd do this:
NSString *str = #"One,Two,\"This is part three, I think\",Four";
NSArray *lines = [str CSVComponents];
for (NSArray *line in lines) {
for (NSString *field in line) {
NSLog(#"field: %#", field);
}
}
Here is a C# answer to the same problem.
C# Regex Split - commas outside quotes
You could probably use the same Regex in Objective-C
NSString split Regex with ,(?=(?:[^']*'[^']*')*[^']*$)

NSFileManager contentsOfDirectoryAtPath encoding problem with samba path

i mount a SMB path using this code
urlStringOfVolumeToMount = [urlStringOfVolumeToMount stringByAddingPercentEscapesUsingEncoding:NSMacOSRomanStringEncoding];
NSURL *urlOfVolumeToMount = [NSURL URLWithString:urlStringOfVolumeToMount];
FSVolumeRefNum returnRefNum;
FSMountServerVolumeSync( (CFURLRef)urlOfVolumeToMount, NULL, NULL, NULL, &returnRefNum, 0L);
Then, i get the content of some paths :
NSMutableArray *content = (NSMutableArray *)[[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error];
My problem is every path in "content" array containing special chars (ü for example) give me 2 chars encoded : ü becomes u¨
when i log bytes using :
[contentItem dataUsingEncoding:NSUTF8StringEncoding];
it gives me : 75cc88 which is u (75) and ¨(cc88)
What i expected is the ü char encoded in utf-8. In bytes, it should be c3bc
I've tried to convert my path using ISOLatin1 encoding, MacOSRoman... but as long as the content path already have 2 separate chars instead of one for ü, any conversion give me 2 chars encoded...
If someone can help, thanks
My configuration : localized in french and using snow leopard.
urlStringOfVolumeToMount = [urlStringOfVolumeToMount stringByAddingPercentEscapesUsingEncoding:NSMacOSRomanStringEncoding];
Unless you specifically need MacRoman for some reason, you should probably be using UTF-8 here.
NSMutableArray *content = (NSMutableArray *)[[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error];
My problem is every path in "content" array containing special chars (ü for example) give me 2 chars encoded : ü becomes u¨
You're expecting composed characters and getting decomposed sequences.
Since you're getting the pathnames from the file-system, this is not a problem: The pathnames are correct as you're receiving them, and as long as you pass them to something that does Unicode right, they will display correctly as well.
Well, four years later I'm struggling with the same thing but for åäö in my case.
Took a lot of time to find the simple solution.
NSString has the necessary comparator built in.
Comparing aString with anotherString where one comes from the array returned by NSFileManagers contentsOfDirectoryAtPath: is as simple as:
if( [aString compare:anotherString] == NSOrderedSame )
The compare method takes care of making both the strings into a comparable canonical format. In effect making them "if they look the same, they are the same"

How to find beginswith word in a string using NSPredicate?

I am searching for a solution on how to format NSPredicate to search correct word in a string of text.
Currently I am using this code:
NSPredicate *predicate = [NSPredicate predicateWithFormat:
#"(content CONTAINS[c] %#)", word];
but it returns wrong resuls if the word is short, for example:
if the word is 'ail' it will return all strings with words which include this 3 letters.. But I don't need such abstract search, so how to search words 'beginswith' my word in a string?
Simply use BEGINSWITH instead of CONTAINS.
Edit
If you need to search in every word of a string, there is a technique which was presented in one of the talks in WWDC 2010. The basic idea is to create a separate entity Word which contains a single word and a reference of the containing object (the entity you're searching). You then do the search on the Word entity and return the objects which are related to the found words.
The query would then look something like this (provided you have a many-to-one relationship between your entity and Word: "ANY words BEGINSWITH[c] %#"
This would mean that you have to setup the words when creating your objects though.
search first with BEGINSWITH for "Foo", this will find you the beginnings of a string
"Foo Word1 Word2", "Foo OtherWord1 OtherWord2" (will find 2)
and than CONTAINS for " Foo" (note the space before Foo), which will give you words that starts with "Foo" presided with a space
"Word1 Foo Word", "Some OtherWord FooWord", "Misc FooXWord" (will find 3)
Predicate :
NSPredicate *predicate = [NSPredicate predicateWithFormat:
#"(content BEGINSWITH[c] %#) OR (content CONTAINS[c] %#)", word, [NSString stringWithFormat:#" %#", word]];
If your text contains some punctuation chars between words you may use this predicate:
NSPredicate *predicate = [NSPredicate predicateWithFormat:
#"(name BEGINSWITH[c] %#) OR (name MATCHES[c] %#)",
query,
[NSString stringWithFormat:#".*[^\\w]%#.*", query]];
I've had the same issue and at first i thought your answers could help me, but they didn't. This one helped me, so...Take a look:
NSString *matchString = [NSString stringWithFormat: #".*\\b%#.*",searchText];
NSString *predicateString = #"keyword MATCHES[c] %#";
NSPredicate *predicate =[NSPredicate predicateWithFormat: predicateString, matchString];
Try using compound:
NSCompoundPredicate *bPredicate =  [NSCompoundPredicate orPredicateWithSubpredicates:#[[NSPredicate predicateWithFormat:#"content BEGINSWITH[cd] %#",_SearchBar.text], [NSPredicate predicateWithFormat:#"content CONTAINS[cd] %#", _SearchBar.text]]];

Resources