31

Solution: I have marked @BlackRider's answer as correct as it is the most versatile especially for complex comparisons however there are other very good answers and comments. I would encourage anyone with the same or similar question to review them and evaluate the best course of action for your specific situation.

In my situation, I am actually not using BlackRider's solution in my implementation. I have elected to use my own solution (see Edit #2 below) with help from @JoshCaswell's comments as well as @voromax's suggestion of indexesOfObjectsWithOptions:passingTest: due to the fact that my comparisons are very simple in this situation.

Thanks to everyone who answered and provided insight.


I am looking for an efficient way to retrieve an object from an NSArray based on a property of that object (a unique identifier, in this case). In C#.NET using Linq I would do something like

MyObject obj = myList.Single(o => o.uuid == myUUID);

I am also wondering if there is an efficient way to get an array of objects matching a non-unique property. Again, with Linq it would look like

List<MyObject> objs = myList.Where(o => o.flag == true).ToList();

Of course I can write loops to do this but they would not be reusable and I'm suspicious of their performance.

Finding an object with a unique ID:

-(MyObject*)findObjectWithUUID:(NSString*)searchUUID{
    for (MyObject* obj in _myArray){
        if([obj.uuid isEqualToString: searchUUID])
            return obj;
    }
}

Finding an array of objects:

-(NSArray*)findObjectsWithFlag:(BOOL)f{
    NSMutableArray* arr = [NSMutableArray array];
    for (MyObject* obj in _myArray){
        if(obj.flag == f)
            [arr addObject:obj];
    }
    return arr;
}

-- EDIT --

Luckily in the first situation the object I am looking for has a unique identifier and I know there will only be one. I came up with a solution to implement isEqual on my object which will be invoked by indexOfObject:

- (BOOL)isEqual:(id)object{
    return [self.uuid isEqualToString: ((MyObject*)object).uuid];
}

And then create a "fake" lookup object and use that to find the real one

MyObject *lookupObject = [[MyObject alloc] init];
lookupObject.uuid = searchUUID;
MyObject *actualObject = 
    [_myArray objectAtIndex:[_myArray indexOfObject:lookupObject]];

This is essentially the same as the for-in loop I posted above, but might be more readable & be more reusable. Of course, this only works for finding one unique object and does not address the second half of my question.

-- EDIT 2 --

Checking Class and implementing hash as recommended in comments.

- (BOOL)isEqual:(id)object{
    return [object isKindOfClass:[MyObject class]] && 
           [self.uuid isEqualToString: ((MyObject*)object).uuid];
}

- (NSUInteger)hash{
    return [self.uuid hash];
}
Uptown Apps
  • 707
  • 1
  • 5
  • 11
  • 3
    The code you posted is about as efficient as it gets. Using things like predicates is going to be much slower than a purpose built loop. – rmaddy Nov 16 '13 at 23:13
  • possible duplicate of [Fast way to search the properties of objects in an NSArray](http://stackoverflow.com/questions/12099867/fast-way-to-search-the-properties-of-objects-in-an-nsarray) – jscs Nov 16 '13 at 23:53
  • Thank you @JoshCaswell. `[NSPredicate]` has been suggested below as well but @rmaddy mentioned performance concerns with it. I am curious how it compares to direct loops as well as `[indexesOfObjectsPassingTest:]`.I have edited my question with a possible solution for the first part of the question regarding a single object with a unique identifier. Are there any issues that may arise from this implementation? – Uptown Apps Nov 17 '13 at 00:24
  • Overriding `isEqual:` requires overriding `hash` as well: http://www.mikeash.com/pyblog/friday-qa-2010-06-18-implementing-equality-and-hashing.html Defining equality so narrowly as you have for your objects will limit their ability to be put into other kinds of collections: being used as dictionary keys, e.g. – jscs Nov 17 '13 at 02:50

5 Answers5

41

You can use [NSPredicate], which gives you a query-like syntax for search. Check out this page for the predicate syntax description. Here's a simple example:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"propertyName == %@", @"value"];
NSArray *filteredArray = [myArray filteredArrayUsingPredicate:predicate];

As to performance, I think your solution is OK since any search in an array needs to iterate through all the elements anyway, and then, for each object, compare the value of a field against the value you search for. You can optimize repeat searches within the same data, e.g. by creating and populating a dictionary that maps values of some field to the matching objects (or collections of objects, if the mapping is one to many).

TotoroTotoro
  • 17,524
  • 4
  • 45
  • 76
  • How does `NSPredicate` filtering compare with `indexesOfObjectsPassingTest:` that was mentioned in another answer performance wise? – Uptown Apps Nov 17 '13 at 00:28
  • 1
    @UptownApps the difference is negligible for practical reasons in most situations. If you're familiar with the Big-O notation, it is O(n) for the NSPredicate solution, the code you've posted in your question, and indexesOfObjectsPassingTest. – TotoroTotoro Nov 17 '13 at 19:04
  • But predicates can't be checked at compile time. I consider that unsafe. – User Dec 04 '14 at 16:59
14

You may also look at modern block syntax: indexOfObjectWithOptions:passingTest: or indexesOfObjectsWithOptions:passingTest: which support concurrency and search order.

Muntashir Akon
  • 8,740
  • 2
  • 27
  • 38
voromax
  • 3,369
  • 2
  • 30
  • 53
10

I was intrigued by rmaddys comment so I've checked the difference between looping and predicate.

Let's assume a simple object with NSString property. I've inserted it into array 10 000 times , every time with different property value.

In the worst case scenario when desired object was on the last position of the array, loop approach was 3.5x faster than NSPredicate (0.39s vs 0.11s, arraySize = 10000, 10 iterations, iPad Mini)

Code I used for reference: pastebin

Szymon Kuczur
  • 5,783
  • 3
  • 16
  • 14
4

I know its related with NSArray but if we do it using Swift and using the swift Array which is a struct, then that will be lot easier.

Swift 2.2 / Swift 3.0 / Swift 4.x Working fine on all versions

Lets assume we have a custom model class

class User {
    var userId = 0
    var userName = ""
} 

And lets assume we have an array named as usersArray which has custom objects of User class.

And we want to fetch an object from this array with userId = 100 for example:-

let filteredArray = usersArray.filter({$0.userId == 100}) 

This filtered array will contain all the custom objects which have userId as 100

print(filteredArray[0].userName) //will print the name of the user with userId = 100
Rajan Maheshwari
  • 14,465
  • 6
  • 64
  • 98
3

just for those who are interested, I've found the fastest way to search through NSArray is by using a for loop on a background thread. using the [self performSelectorInBackground...] method. In an NSArray of 10000 custom objects I searched through the whole thing thoroughly in around 1 second. On the main thread it took around 10 seconds or more.

RASS
  • 61
  • 1
  • 6
  • @Rajan Maheshwari million dollar thanks bro for saving my time and efforts!!!! cleaver answer it works perfectly for model class also..!!! – Pravin Kamble Oct 22 '16 at 06:49