2

I don't understand the array method indexOfObject:inSortedRange:options:usingComparator:

More specifically, the indexOfObject property.

According to the docs the value passed in should be An object for which to search in the array. But this makes no sense.. if i already had a reference to the object, why would i be searching for it in an array? Does it mean the object type?

I have an array of objects, and all I have is a property of those objects. ie. I have an array of cars and I need to find the car object when I have the car ID of 12345.

What would I pass into the the method for the indexOfObject property? here is what I'm trying

   MyCarObject *searchObject;
   NSUInteger findIndex = [sortedArray indexOfObject:searchObject
                                       inSortedRange:searchRange
                                             options:NSBinarySearchingFirstEqual
                                     usingComparator:^(id obj1, id obj2)
                              {
                                return [obj1 compare:obj2];
                              }];

but this clearly isn't going to get an object based on the ID.. it appears like its going to give me the index of the property i already have a reference to, which seems pointless....

If this isn't the correct method, then what should I be using? I need to use a binary search on array of objects and pull out the reference to that object. And all I have is a property to compare.

4 Answers 4

2

This method returns you index of the object in a given array, which can be really useful sometimes. Objects in the array are compared using isEqual: method (which by default compares pointers). That is the reason the method has no chance to know you want it to be compared using some of your custom properties.

For finding specific object in an array by your own property, use can use

  • NSArrays method - (NSArray *)filteredArrayUsingPredicate: and a corresponding NSPredicate (there is a lot of questions on SO and tutorials for these)
  • own loop comparing any property you want with any value (Car ID of the object with the ID you are searching car by in your case)
Sign up to request clarification or add additional context in comments.

2 Comments

filteredArrayUsingPredicate was causing lots of slowdown because it searches from the start of the array to the end. I need a binary search for really large arrays. hence why I am trying to use this method
So you can just make sure you always have the array ordered by Car ID and then simply write a small method to find your object using binary search algorithm. Long story short: If you are not satisfied with NSPredicate, I think you have to write your own implementation for this
1

It is possible to bludgeon this API into doing what you want, although I do not really advocate this method. If you pass in the key you wish to search for, that will be passed into the comparator along with the element being compared against. However, the order in which these arguments are passed changes so you need to inspect the class of the comparator arguments at runtime to in order to differentiate them and perform your comparison.

- (BWCProductCategory *)categoryForID:(NSNumber *)ID categories:(NSArray *)categories {
    NSRange searchRange = NSMakeRange(0, categories.count);
    NSUInteger index = [categories indexOfObject:ID
                                   inSortedRange:searchRange
                                         options:NSBinarySearchingFirstEqual
                                 usingComparator:^NSComparisonResult(id obj1, id obj2) {
                                     if ([obj1 isKindOfClass:[BWCProductCategory class]]) {
                                         return [[(BWCProductCategory *)obj1 categoryID] compare:obj2];
                                     } else {
                                         return [obj1 compare:[(BWCProductCategory *)obj2 categoryID]];
                                     }
                                 }];

    return (index == NSNotFound) ? nil : categories[index];
}

This does function, however it feels quite awkward, plus I am not confident of the performance impact when performing large searches (although it is surely still less than an O(n) search). Perhaps you could build a nicer method on top of these which hides the kludge factor.

1 Comment

After 2 hours debugging this I noticed this too, the order of the comparator arguments switches seemingly randomly - why on earth this is I cannot imagine. This means if you are using this on a string array, you need a string comparison too just to determine which argument in the comparator is which!
0

I created an extension to Swift's Array to make Swift usage in this manner very clean.

import Foundation

extension Array where Element: AnyObject {

    public func indexOfObject<T: AnyObject>(obj: T, options opts: NSBinarySearchingOptions, usingComparator cmp: (T, Element) -> NSComparisonResult) -> Int {
        return (self as NSArray).indexOfObject(obj, inSortedRange: NSRange(0..<count), options: opts, usingComparator: { (a: AnyObject, b: AnyObject) -> NSComparisonResult in
            if a === obj {
                return cmp(a as! T, b as! Element)
            } else {
                var result = cmp(b as! T, a as! Element)

                if result == .OrderedDescending {
                    result = .OrderedAscending
                } else if result == .OrderedAscending {
                    result = .OrderedDescending
                }

                return result
            }
        })
    }
}

Here's an example usage:

class ItemWithProperty {
    var property: Int

    init(property: Int) {
        self.property = property
    }
}

let listOfItems = [ItemWithProperty(property: 1),
    ItemWithProperty(property: 20),
    ItemWithProperty(property: 30),
    ItemWithProperty(property: 45),
    ItemWithProperty(property: 45),
    ItemWithProperty(property: 45),
    ItemWithProperty(property: 60),
    ItemWithProperty(property: 77),
]

let indexOf20 = listOfItems.indexOfObject(20, options: .FirstEqual) { number, item in
    number.compare(item.property)
}
// returns 1

let indexOf25 = listOfItems.indexOfObject(25, options: .FirstEqual) { number, item in
    number.compare(item.property)
}
indexOf25 == NSNotFound
// comparison is true, number not found

let indexOfFirst45 = listOfItems.indexOfObject(45, options: .FirstEqual) { number, item in
    number.compare(item.property)
}
// returns 3

let indexOfLast45 = listOfItems.indexOfObject(45, options: .LastEqual) { number, item in
    number.compare(item.property)
}
// returns 5

let indexOf77 = listOfItems.indexOfObject(77, options: .FirstEqual) { number, item in
    number.compare(item.property)
}
// returns 7

Comments

0

The compare: method (which you are using for you binary search comparator) means different things depending on the object. NSString, for example, implements compare as a lexical compare. So, if you have a sorted NSArray of NSString, then what you will get back is the index in the list of a string matching the input string (or NSNotFound if not in the array).

For your object type (MyCarObject) you would implement your definition of compare: to determine the relative ordering of MyCarObjects. Then you could construct a new instance of a MyCarObject, and use this method to determine if an equivalent object (as determined by compare:) is already in the list.

Note that this method does a binary search, so the array must be kept sorted using the same comparator that you are using to search it. You can use NSBinarySearchingInsertionIndex to find the index at which to insert new elements to keep the list sorted.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.