views:

506

answers:

5

Hi,

I have an NSArray of NSDictionaries. I need to check if there's at least one occurrence of an object for a key of the NSDictionary in the NSArray. I do this by using

int i;
for (i=0;i< [myArray count];i++)
{
    if ([[[myArray objectAtIndex: i] objectForKey: myKey] isEqualToString: myString]) {
        found = YES;
        break;
    } else {
        found = NO;
    }
}

But I have a suspicion that there's a better/faster alternative for it...

Thanks

+2  A: 

Using == to check string equality might cause unexpected behavior, because you're actually comparing pointers (that can be okay if you're sure that you're dealing with a single string object pointed to by two pointers). isEqualToString: is probably what you want instead.

You can use "fast enumeration" to simplify things slightly:

bool found = NO;

for (NSDictionary *dict in myArray) {
    found = [[dict objectForKey:myKey] isEqualToString:myString];

    if (found)
        break;
}

It's only "faster" in the sense that it's fewer words to write; the execution speed is the same.

Frank Schmitt
Thanks for your quick reply. I changed it to isEqualToString.
“It's only "faster" in the sense that it's fewer words to write; the execution speed is the same.” Not necessarily. If the array's implementation is a linked list, then enumeration, whether by fast enumeration or NSEnumerator, will be faster than iterating up or down to an increasing index. (Not that you should know or care how the array is implemented.)
Peter Hosey
Note that it should be "for (NSDictionary *dict in myArray)" with an * before dict.
Fast enumeration will perform slightly better because the NSFastEnumeration protocol uses a C array to enumerate the objects. This will be more efficient than sending an -objectAtIndex: message each time through the loop.
Preston
+4  A: 

This is as fast as you can get it with your current data structures. You're doing an O(1) lookup for each dictionary in the array. If you have a huge number of dictionaries, this might get expensive, so you could consider (depending very much on the semantics of your data) keeping a separate lookaside NSSet that contains the set of string objects comprising all the values in the dictionaries. Then you can check once in that set for existence.

Tell us more about the form of the data for more insight...

Also be careful with the == operator with NSStrings. If you're actually checking to see whether the text of the string is equal, you should use -isEqualToString: instead, as your form will just do a reference comparison.

quixoto
Thanks. I changed it to -isEqualToString:. Well, the array can be infinitely long but will mostly have less than 10 objects. The dictionaries always have 4 pairs. One empty string (I might change it to nil later), one image, one path (also string) and another string.I guess it's not worth using something else like this if the count is mostly low. I asked the question to see if there was a method for it or something like that. It performs fine on a low count.The two other comments said something about fast enumeration that is neat to have.Thanks again.
Yeah, there's no silver bullet data structure for what you want; you just get Arrays, Sets and Dictionaries as building blocks. If your list is really generally around 10 items, then yeah, iterating through that is basically free and you don't need to complexify things by doing anything special. Your implementation is fine, and the `for..in` syntax is a nice bonus.
quixoto
+4  A: 

Yes. Use "fast enumeration", commonly also known as for-in loop:

for (NSDictionary* dict in myArray) {

Also, to compare NSString's, use -isEqualToString:.

   if ([[dict objectForKey: myKey] isEqualToString:myString]) {

That said, there is no algorithmic improvement to this (i.e. this method is already the best.)

KennyTM
Thank you. I changed it to isEqualToString. I had never used the fast enumeration before.
+2  A: 

You should use fast enumeration, which will iterate through the objects using a C array behind the scenes. Right now, you're calling both -objectAtIndex: and -count each time through the loop.

You might also check out NSPredicate if myKey is a string. My gut tells me it would be slower, but you never know if it might benefit from internal optimization for NSDictionary:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%@ LIKE %@", myKey, myString];
BOOL found = ([[myArray filteredArrayUsingPredicate:predicate] count] > 0);
Preston
+1  A: 

You could be much more succinct with Key-Value Coding:

[[myArray valueForKey:myKey] containsObject:myString];

It's not necessarily faster (I suspect it will be slower), but speed is not always the primary concern. Whether speed is critical in a particular case is a matter for profiling to decide.

Chuck