What's most performant way in iOS to check if a string is one of a list of strings? - iphone

I want to see if stringA is equal to any of a list of strings -- string1, string2, string3. What's the most performant way to do the comparison?
Since my comparison list is rather small, I'm currently trying this:
- (BOOL) isStringInList:(NSString *)testString{
if ([testString caseInsensitiveCompare:#"string1"] == NSOrderedSame)
return YES;
else if ([testString caseInsensitiveCompare:#"string2"] == NSOrderedSame)
return YES;
else if ([testString caseInsensitiveCompare:#"string3"] == NSOrderedSame)
return YES;
return NO;
}
This obviously does not scale well if I have many strings to compare against. I'd prefer more of a method signature like this -(BOOL) isString:(NSString *)testString inList:(NSString *)listString where listString is a space-separated string of keywords.
Any thoughts on how to improve performance would be appreciated.

The most performant way is to construct an NSSet of the strings you want to compare against and use -member: to test. Once the set is constructed, this will be a constant-time test. If you have a space-separated list to start with, you can use
NSSet *set = [NSSet setWithArray:[listOfWords componentsSeparatedByString:#" "]]
Constructing the set will be linear on the size of the input string. If your set is the same every time, you can construct it once and hold on to the result. To do the actual test you can use
[set member:myWord]
If the result is nil, your word isn't in the set. If it's non-nil, it is. Note, this is a case-sensitive search. If you need case-insensitivity, then you should either lowercase or uppercase both the list of words and the input word before performing your test.

- (BOOL)isString:(NSString*)testString inList:(NSString*)listString
{
BOOL result = NO;
if (testString != nil)
{
NSRange range = [listString rangeOfString:testString];
result = (range.location != NSNotFound);
}
return result;
}

- (BOOL)isString:(NSString *)testString inList:(NSString *)spaceSeparatedStrings
{
NSArray *list = [spaceSeparatedStrings componentsSeparatedByString:#" "];
return [list containsObject:testString];
}
Note: the above will be case-sensative, unlike your example. For case-insensative, you will probably have to iterate over some portion of the NSArray.

According to this, containsObject is more efficient than I would have guessed. Good news.
A binary search tree might be better, but obviously, that would take some work.

Related

Can't do mathematical operations with int from NSUserDefaults

i have integer data, stored in NSUserDefaults, there is my code:
- (IBAction)addButton:(id)sender {
NSInteger oldValue = [[NSUserDefaults standardUserDefaults] integerForKey:#"myValue"];
NSString *string1=[addTextField text];
int add = [string1 floatValue];
int new = globalCalories;
int old=oldValue;
if(recomended.text==[NSString stringWithFormat:#"%i", oldValue]){
**self.day = (int)roundf(old-add);
dayLeft.text=[NSString stringWithFormat:#"%d", oldValue-add];
}**
else
{
self.day=(int)roundf(new-add);
dayLeft.text=[NSString stringWithFormat:#"%d", day];
}
}
I copied all of button action code, just in case, but i did mark with bold strings of code, that appear to not work. So, it suppose to do mathematical operations with stored data (oldValue), but when i launch programs, it dosnt, in fact, it does, but instead of valid value program "think" that oldValue is 0 (instead of valid value).
So, when it contain, for example, number 2000, and I launch program and enter in text field 500, it suppose to be 1500 (2000-500), but it shows -500.
You can convert recommended.text to integer by using this:
int recommendedValue = [recommended.text intValue];
then compare the numbers.
The problem is that == compared the address es of the NSString and not their values (see many SO questions). To compare strings use the isEqualToString: method.
However in this case it would be even better to compare the numbers ie convert recomended.text to the number and use a intValue method on dayLeft.

How to compare strings? (`==` returns wrong value)

I've got myself a piece of the iPhone SDK and been trying to make some simple apps. In this one, i want to compare the first character of self.label.string with the last one of ((UITextField *)sender).text. I decided to name them self.texty and self.input, respectively.
I would expect this if statement returning yes to me under certain circumstances, however I can't seem to get that done.
(in my case, my self.label.string was equal to 'hello!', while my self.input ended in an 'h'.)
self.input = [NSString stringWithFormat:#"%#", [((UITextField *)sender).text substringFromIndex:[((UITextField *)sender).text length]-1]];
self.texty = [NSString stringWithFormat:#"%#", [self.label.string substringToIndex:1]];
if (self.input == self.texty) {
return yes;
} else {
return no;
}
String comparison is not done with ==, but with one of the comparison methods of NSString.
For example:
if ([self.input compare:self.texty] == NSOrderedSame) {
if ([self.input isEqualToString:texty]) {
return yes;
} else {
return no;
}
EDIT:
Or a better version as the commenters noted:
return [self.input isEqualToString:texty];
If you're curious why the == operator doesn't work as expected, it's because you're actually comparing two scalar types (pointers to NSString objects) not the contents of the NSString objects themselves. As a result, it will return false unless the two compared NSStrings are actually the same instance in memory, regardless of the contents.

NSString internals - how does length works?

I've a question about NSString internals.
I want to check a string length and basically I wanted to know if a NSString knows its length / count each time / count & cache the result.
Should I store it's length and compute or call the length method each time ?
To test a string I can test against nil OR ask for it's length.
if (str != nil) {
// compute
}
if ([str length]) {
// compute
}
Which one is the fastest ?
Which one is the more memory efficient ?
Thanks
Checking for nil ("no object") is most definitely not the same as sending the length message to the (NSString) object. Only one of the conditional checks is valid to test for an "empty" string. (An "empty" string is an object and, therefore, not nil.)
The bigger question is: does NSString store a length or is it sentinel-terminated (like a "normal c string")? NSString stores the length as an internal property so it, length, is as O(1) operation.
Happy coding.
Here is how CFStringGetLength works:
(from http://opensource.apple.com/source/CF/CF-550.43/CFString.c)
/* Returns length; use __CFStrLength2 if contents buffer pointer has already been computed.
*/
CF_INLINE CFIndex __CFStrLength(CFStringRef str) {
if (__CFStrHasExplicitLength(str)) {
if (__CFStrIsInline(str)) {
return str->variants.inline1.length;
} else {
return str->variants.notInlineImmutable1.length;
}
} else {
return (CFIndex)(*((uint8_t *)__CFStrContents(str)));
}
}
So it should be O(1) for all cases.
The two -- testing a NSString pointer for nil and testing the length of an NSString -- are not in any way equivalent. An NSString object with a zero length can exist, and a pointer to it will not compare equal to nil.
To my knowledge (and I'd be quite surprised to discover I was wrong), the length of an NSString is stored within the object as an efficiently-referenced property. Caching the length would generally be unnecessary complexity.
NSString is immutable class, so the length stays the same all of the time.
Addendum: Testing against [string length] evaluates to 0/nil/NO in both cases (string being nil and string having zero length).

Objective C: Compare Array Element to String

Greetings,
I'm trying to simply compare a NSString to an NSArray.
Here is my code:
NSString *username=uname.text;
NSString *regex=#"^[a-zA-Z0-9-_.]{3,20}$";
NSArray *matchArray=nil;
matchArray=[username componentsMatchedByRegex:regex];
if(matchArray[0] == "asdf"){ //this line causes the problem!
NSLog(#"matchArray %#",matchArray);
}
I get an "invalid operands to binary ==" error.
How can I compare the string?
Many thanks in advance,
You are trying to compare an NSString to a C string (char *), which is wrong. matchArray is an NSArray so you cannot treat it as a C array either, you have to use its objectAtIndex: method and pass in the index.
Use this instead:
if ([[matchArray objectAtIndex:0] isEqualToString:#"asdf"]) {
NSLog(#"matchArray %#", matchArray);
}
Addressing your comments, the reason why isEqualToString: does not show up in autocomplete is because Xcode cannot guess that matchArray contains NSStrings (it only knows it contains ids, that is, arbitrary Objective-C objects). If you really wanted to be sure, you can perform an explicit cast, but it doesn't matter if you don't:
if ([(NSString *)[matchArray objectAtIndex:0] isEqualToString:#"asdf"]) {
NSLog(#"matchArray %#", matchArray);
}
you want to use -objectAtIndex to get the array element. NOT the C array accessor syntax
try to use:
[[matchArray objectAtIndex:0] isEqualToString:#"asdf"];
anyway the string "asdf" should be #"asdf"

curious what is wrong with this if statement?

Could someone explain what is wrong with this "if statement"? I get:
"lvalue required as left operand of assignment".
This does not work:
if ([[prepDataArray objectAtIndex:iNDEX] boolValue] = YES) {
NSLog(#"HARD");
}
While this works:
diffQ = [[prepDataArray objectAtIndex:iNDEX] boolValue];
if (diffQ = YES) {
NSLog(#"HARD");
}
I do realize where the problem are and that the 'lvalue' indicate that i need to have something different on the left side but i do not grasp why and how to do what i want inside the 'if' statement as tried in the first example.
Would appreciate if someone nice could give me a hint :-)
if ([[prepDataArray objectAtIndex:iNDEX] boolValue] == YES) {
NSLog(#"HARD");
}
it's == not =
The first one doesn't work because you try to assign a BOOL (YES) to a message. The second one works because you try to assign a BOOL to diffQ. This is correct, but not the result you expect (comparing diffQ to YES)
Common programming error ;) I've done this a millions times
I completely agree with what #thomas said above, but let me add.
Don't compare a bool to YES. It's not that the if construct requires
if( some comparison statement ) {
....
}
That's not the case. The if construct has the following form:
if( a boolean value) {
...
}
It just happens that a comparison statement yields a boolean, so you put that in the if statement.
In your case, boolValue already gives you a bool. You don't need to compare that against YES or NO. That's like doing YES==YES or YES==NO and it's completely redundant.
Just do
if ([[prepDataArray objectAtIndex:iNDEX] boolValue]) {
NSLog(#"HARD");
}