case insensitive string search - iphone - ios

I am looking for a way to perform a case insensitive string search within another string in Objective-C. I could find ways to search case sensitive strings, and to compare insensitive case, but not searching + case insensitive.
Examples of the search that I would like to perform:
"john" within "i told JOHN to find me a good search algorithm"
"bad IDEA" within "I think its a really baD idea to post this question"
I prefer to stick to only NSStrings.

NSRange r = [MyString rangeOfString:#"Boo" options:NSCaseInsensitiveSearch];
Feel free to encapsulate that into a method in a category over NSString, if you do it a lot. Like this:
#interface NSString(MyExtensions)
-(NSRange)rangeOfStringNoCase:(NSString*)s;
#end
#implementation NSString(MyExtensions)
-(NSRange)rangeOfStringNoCase:(NSString*)s
{
return [self rangeOfString:s options:NSCaseInsensitiveSearch];
}
#end
Your code might become more readable with this. Then again, less readable for those unfamiliar.

mistagged as c#?
heres some advice for objc
NSRange textRange = [[string lowercaseString] rangeOfString:[substring lowercaseString]];
if(textRange.location != NSNotFound)
{
//Does contain the substring
}
which i got from google at this webpage:
http://www.developers-life.com/does-a-nsstring-contain-a-substring.html
does that help your scenario?

If you're using ios 8, you can use NSString's localizedCaseInsensitiveContainsString
- (BOOL)localizedCaseInsensitiveContainsString:(NSString *)aString
localizedCaseInsensitiveContainsString: is the case-insensitive variant. Note that it takes the current locale into effect as well. Locale-independent case-insensitive operation, and other needs can be achieved by calling rangeOfString:options:range:locale: directly.

Here is a self-container solution for Swift:
private func containsKeyword(text: NSString, keyword: String) -> Bool
{
return text.rangeOfString(keyword, options:NSStringCompareOptions.CaseInsensitiveSearch).location != NSNotFound
}

Related

iOS: Is this a good way to check if JSON dictionary object is an NSString?

I want to check if a JSON object is an NSString and if it isn't, assign it a default string. My ultimate goal is to prevent crashing and assign the properties a proper value no matter what. This is an example of a data model I am using where dict is the JSON dictionary the API returns.
Data *data = [[self alloc] init];
data.name = [NSString validateString:dict[#"name"] defaultString:#""];
data.status = [NSString validateString:dict[#"status"] defaultString:#"OPEN"];
Here is the category method validateString I am using.
+ (NSString *)validateString:(NSString *)aString defaultString:(NSString *)defaultString {
if ([aString isKindOfClass:[NSString class]]) {
return aString;
}
return defaultString;
}
It makes no sense, and is very bad practice, to cast (NSString *)aString and then ask if this is in fact an NSString.
Also, what if it is nil?
All you know when you fetch from a dictionary is that you get an id. Do not assume more than that.
I would suggest writing very plainly: say what you mean, and mean what you say. That is the best practice in Objective-C. Otherwise, dynamic typing and "nil trickery" can lead you into subtle errors. You might not have any trouble in this particular case, but bad habits are bad habits, and it is best not to let them form in the first place. I'd rewrite like this:
+ (NSString *) checkType:(nullable id)obj defaultString:(NSString *)def {
if (obj == nil || ![obj isKindOfClass:[NSString class]]) {
return def;
}
return obj;
}
Like mentioned in other comments: if you want to prevent crashes, you also need to check if it's nil, specially if there is a chance to port your code to Swift in the future.
Just to clarify my last sentence, the line below works in Objective-C even if aString is nil:
if ([aString isKindOfClass:[NSString class]]) {
That's because, in the way Objective-C was made, calling a function on a nil object returns nil, so the if will be considered false, and the function will return defaultString. Yeah... that's certainly a bad idea when they created Objetive-C, since this leads to lots of errors. More details about that behaviour below:
https://stackoverflow.com/a/2696909
Anyway, it's also a good practice to only cast an object after checking its type, so I would recommend adapting your function to this:
+ (NSString *)validateString:(id)obj defaultString:(NSString *)defaultString {
if (obj != nil && [obj isKindOfClass:[NSString class]]) {
return (NSString*)obj;
}
return defaultString;
}
Every object that implements NSObject* has isKindOfClass: (and NSDictionary* only stores objects that implement NSObject*), so we don't need to check if the object responds to it. Also, even if we wanted, respondsToSelector: is also an NSObject* function.
Still, the method that you are using still works. The revised function above is just adapted to better practices and to avoid problems in case you ever need to port this code to Swift (or any other language) in the future.
EDIT: Updated code based in #matt's suggestion.

Can one define NSFound macro?

This may sound like a silly question but Apple provides us with NSNotFound but why didn't they provide one called NSFound? Is there a way one can define a NSFound macro on their own?
The reason I am asking all this is that in order for me to check if a string "contains" a certain character I have to do double negative i.e.
if ([XML rangeOfString:#"error" options:NSCaseInsensitiveSearch].location != NSNotFound)
{
//server is down
}
else
{
//server is up
}
At least for me this would have been so much easier to read if I could simply do this instead
if ([XML rangeOfString:#"error" options:NSCaseInsensitiveSearch].location == NSFound)
{
//server is down
}
else
{
//server is up
}
If I want to define NSFound or SAMFound, how would I go about doing that?
Your issue is really with the design pattern methods like rangeOfString follow - using a single return value for both valid results, of which there are many, and failure indications, of which there is one. You can test for a single failure value with a comparison to a constant, NSNotFound in this case, but you cannot likewise test for many possible values with a simple comparison - instead you use the "double negative" you don't like.
If you find it too ugly change it... Maybe:
#interface NSString (SamExtras)
- (BOOL) SAMcontainsString:(NSString *)string options:(NSStringCompareOptions)options;
#end
#implementation NSString (SamExtras)
- (BOOL) SAMcontainsString:(NSString *)string options:(NSStringCompareOptions)options
{
return [self rangeOfString:string options:options].location != NSNotFound;
}
#end
Which would allow you to use:
if ([XML SAMcontainsString:#"error" options:NSCaseInsensitiveSearch])
{
//server is down
}
else
{
//server is up
}
with no double negative. You can write the category once and use it in all your projects.
HTH
Double Negative doesn't have the consequences in code as it does in grammar.
The reason they provide a not found, as opposed to a found version, is simply the not found value is a single (supposedly invalid) value and everything else is valid. It's therefore simpler to define this single, invalid value.
Also it makes more sense (more efficient, avoiding a double-search and less code) to store the NSRange in a local variable in order to firstly test for validity and then to use the value:
NSRange range = [XML rangeOfString:#"error" options:NSCaseInsensitiveSearch];
if (range.location != NSNotFound) {
// Do thing with range
} else {
// Complain
}
There is nothing whatever wrong with your original test:
if ([XML rangeOfString:#"error" options:NSCaseInsensitiveSearch].location != NSNotFound) {
If all you need to know is whether XML contains the string #"error", that test answers the question and is a perfectly legitimate and idiomatic way to ask it. Observe that even the documentation tells you that containsString: is nothing but a front for calling rangeOfString:options:!
If you really want to know what the positive version would be, it would be to test the length of the returned range and see if it is the same as the length of #"error". The length of a not-found range is 0.

NSString: Best way to check if string contains another string with special format?

I have a string representing fruits, separated by dot:
apple.orange.banana.watermelon
and each fruit may have a tag:
apple.orange.[old]banana.[juicy]watermelon
also there may be fruit names which are partially overlapping:
apple.orange.[old]banana.[juicy]strawberry.[fresh]berry
also the tags may be same as fruit names:
apple.orange.[old]banana.[berry]strawberry.[fresh]berry
I need to check if such a string contains a specified fruit, so given the above string and a fruit name, say "berry", I want to know the string contains "berry" and of course it should not tell me YES if "berry"'s not there but "strawberry" is.
A quick way came up in my mind is:
use componentsSeparatedByString to get an array of components (fruit names with tags)
go through each component, check if it has a tag (ie square brackets), remove it if YES
then check the remaining string is exactly the given fruit name
I cannot just use the whole string with rangeOfString because "berry" is a substring of "strawberry", I can't even first get components then check substring for each component because tags may be the same as fruit names.
I wonder is there any better way to do this? Better in terms of memory footprint and/or speed?
Thanks!
There are plenty of ways to do this.
You could use a regular expression.
You could split the string into parts.
But instead, let's notice that, when “berry” (the desired fruit) isn't the first or last fruit in the string, it's got to appear as either ]berry. or as .berry., and neither of those can match if “berry” is a substring.
So we can solve this problem quite simply putting a . on each end of the string before searching it for one of those patterns:
BOOL stringContainsFruit(NSString *string, NSString *fruit) {
string = [NSString stringWithFormat:#".%#.", string];
NSString *dotFruit = [NSString stringWithFormat:#".%#.", fruit];
if ([string rangeOfString:dotFruit].location != NSNotFound) {
return YES;
}
NSString *bracketFruit = [NSString stringWithFormat:#"]%#.", fruit];
if ([string rangeOfString:bracketFruit].location != NSNotFound) {
return YES;
}
return NO;
}

Check if Text Field NEARLY Matches Set Text? iOS

Does anyone now how I can check if a text field NEARLY matches a set text?
I know how to check if it exactly matches, but i want it to know if its even close to the set text
So if they type HELLO WORD it indicates its close but not exact match?
if (([textfield.text isEqual:#"HELLO WORLD"]))
{
NSLog(#"Correct");
} else {
NSLog(#"Incorrect");
}
This library may be of use to you. And since it's open source, you can check the source to see how it's done. :)
Use this
For Case Insensitive :
if( [textfield.text caseInsensitiveCompare:#"My Case sensitiVE"] == NSOrderedSame ) {
// strings are equal except for possibly case
}
For Case Sensitive :
if([textfield.text isEqualToString:#"My Case sensitiVE"]) {
// Case sensitive Compare
}
You can compare each index of two string and see how many difference is there. And you should define your "nearly match", it may be difference in single character or in multiple character. And decide if you should accept it or reject it.
If you like algorithm Longest Common Subsequence is a key to your goal.. :)
use
NSString caseInsensitiveCompare:
or
- (NSComparisonResult)compare:(NSString *)aString
options:(NSStringCompareOptions)mask`
NSString *string = #"HELLO WORLD I AM JACK";
if ([string rangeOfString:#"HELLO WORLD"].location == NSNotFound) {
NSLog(#"string does not contain HELLO WORLD");
} else {
NSLog(#"string contains HELLO WORLD!");
}

Case insensitive compare against bunch of strings

What would be the best method to compare an NSString to a bunch of other strings case insensitive? If it is one of the strings then the method should return YES, otherwise NO.
Here's a little helper function:
BOOL isContainedIn(NSArray* bunchOfStrings, NSString* stringToCheck)
{
for (NSString* string in bunchOfStrings) {
if ([string caseInsensitiveCompare:stringToCheck] == NSOrderedSame)
return YES;
}
return NO;
}
Of course this could be greatly optimized for different use cases.
If, for example, you make a lot of checks against a constant bunchOfStrings you could use an NSSet to hold lower case versions of the strings and use containsObject::
BOOL isContainedIn(NSSet* bunchOfLowercaseStrings, NSString* stringToCheck)
{
return [bunchOfLowercaseStrings containsObject:[stringToCheck lowercaseString]];
}
Just to add a few additions to Nikolai's answer:
NSOrderedSame is defined as 0
typedef NS_ENUM(NSInteger, NSComparisonResult) {NSOrderedAscending = -1L, NSOrderedSame, NSOrderedDescending};
So if you call caseInsensitiveCompare: on a nil object you would get nil. Then you compare nil with NSOrderSame (which is 0) you would get a match which of course is wrong.
Also you will have to check if parameter passed to caseInsensitiveCompare: has to be not nil. From the documentation:
This value must not be nil. If this value is nil, the behavior is
undefined and may change in future versions of OS X.

Resources