0

I have an NSArray with objects and a C array with doubles. I would like to sort the NSArray objects based on the C array values.

The only way I could think of is below (based on this SO question):

NSArray *sortedArray;
sortedArray = [origArray sortedArrayUsingComparator:^NSComparisonResult(id a, id b) 
{
    // Getting c array values at the same indices as the objects being compared
    NSNumber *aValue = @(cArray[[origArray indexOfObject:a]]);
    NSNumber *bValue = @(cArray[[origArray indexOfObject:b]]);
    // Returning comparison result
    return [aValue compare:bValue];
}];

But I think that the indexOfObject: part is a bit costly. Is that so?

If so, is there any faster way of doing this?

Edit:

To provide a bit more context, this sorting is part of a fitness evaluation for an optimization algorithm. In my program, I have done a basic profiling and the fitness evaluation code turns out to be the bottleneck. As such, I am trying to optimize it.

Also, the number of elements in the arrays is expected to be from around 100 to 10k max.

Community
  • 1
  • 1
insys
  • 1,243
  • 12
  • 25

4 Answers4

1

Don't optimize the sort until you know it's a problem. First you need to answer some questions. How many values are going to be in your array? How long can the user be expected to wait?

After that, you should test the current solution. Does the user have to wait too long when sorting the expected number of values?

Next, answer other architectural questions. For example, can you sort the array in the background while the user is doing other work?

If you do all of this and you discover a problem, then you can start to think of how to optimize.

The first thing that comes to mind is mapping the arrays into a dictionary, sorting the dictionary, and mapping back the sorted array.

First things first, get a mapping category (here is one I found at NSArray Equivalent of Map).

@interface NSArray (Map)
- (NSArray *)mapObjectsUsingBlock:(id (^)(id obj, NSUInteger idx))block;
@end

@implementation NSArray (Map)
- (NSArray *)mapObjectsUsingBlock:(id (^)(id obj, NSUInteger idx))block {
    NSMutableArray *result = [NSMutableArray arrayWithCapacity:[self count]];
    [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        [result addObject:block(obj, idx)];
    }];
    return result;
}
@end

Once you have your mapping method of choice, then run the new sort.

// Step 1) Map into an array of a dictionaries
NSArray *dicts = [origArray mapObjectsUsingBlock:^(id obj, NSUInteger idx) {
    return @{ @"value": obj, @"sortValue": @(cArray[idx]) };
}];

// Step 2) Sort the dictionaries
NSArray *sortedDicts = [dicts sortedArrayUsingComparator:^NSComparisonResult(id a, id b) 
{
    return [a[@"sortValue"] compare:b[@"sortValue"]];
}];

// Step 3) Map back to the sorted array
sortedArray = [sortedDicts mapObjectsUsingBlock:^(id obj, NSUInteger idx) {
    return obj[@"value"];
}];

After all of that, then you need to repeat your testing to answer the question, "Does the user have to wait too long when sorting the expected number of values?" If the answer is still yes, the user has to wait too long, then you once again need to look for a new solution.

Community
  • 1
  • 1
Jeffery Thomas
  • 40,388
  • 8
  • 88
  • 114
1

There's no sense in speculating over a potential performance problem without having a real one.

If you can measure an actual problem with your code (e.g. by using Time Profiler in Instruments) you'll get an idea of where the bottle neck might be. In your code there are several spots that might slow the sorting down (boxing each number to calculate the NSComparisonResult, retain/release overhead from ARC just for accessing the objects from the array, ...).

There are a couple of ways to enhance performance but they depend upon the actual data (number of objects in origArray, source of the C array of doubles, ...).

If I'd take a guess at what might seem the candidate for performance enhancement, I'd say the general problem of having two arrays where the elements form a tuple instead of just one array of tuples should be eliminated. Here's a C function that sorts the array with a small overhead:

NSArray *sortedObjects(double valuesArray[], NSArray *objects)
{
    NSUInteger count = [objects count];
    struct tuple {
        double value;
        __unsafe_unretained id object;
    } tupleArray[count];

    for (NSUInteger i = 0; i < count; ++i)
        tupleArray[i] = (struct tuple){ valuesArray[i], objects[i] };

    qsort_b(tupleArray, count, sizeof tupleArray[0], ^int(const void *a, const void *b) {
        double v = ((const struct tuple *)a)->value - ((const struct tuple *)b)->value;
        return v == 0 ? 0 : v > 0 ? 1 : -1;
    });

    __unsafe_unretained id objectsArray[count];
    for (NSUInteger i = 0; i < count; ++i)
        objectsArray[i] = tupleArray[i].object;

    return [[NSArray alloc] initWithObjects:objectsArray count:count];
}
Nikolai Ruhe
  • 79,600
  • 16
  • 173
  • 195
  • Thank you for the answer and the clear and elegant example. I have added some information in my question for clarification. If I understand correctly, the general approach here would be somehow creating a tuple (either through a struct, or through associated objects or regular objects, as @JeremyP suggested) of the object-sorting value pair. – insys Oct 15 '13 at 11:57
1

If so, is there any faster way of doing this?

Well the obvious question is why aren't the double values properties of your objects?

If you absolutely can't have a property that is the double value (and actually, you always can by using associated objects), you can create wrapper objects with one property that is the original object and one property that is the double value and sort an array of those. This is, however, quite expensive (lots of allocations) and so you'll definitely want to do the profiling suggested by the other two answers first.

JeremyP
  • 80,230
  • 15
  • 117
  • 158
  • Thank you for the reference to associated objects, I was not aware it. With respect to the second part of your answer, I would like to avoid creating wrapper objects, because there is a need to sort different objects with different sorting values every time (therefore wrappers cannot be cached). As such I think (as you pointed out) that there will be a significant overhead in this approach. – insys Oct 15 '13 at 11:44
  • On the other hand, I believe it would still be better than the approach I presented in my question. – insys Oct 15 '13 at 11:54
0

If your single concern is the overhead of indexOfObject: you might want to use indexOfObjectIdenticalTo:. It's omitting the isEqual: message to every element and might be way faster if the array has many elements.

Nikolai Ruhe
  • 79,600
  • 16
  • 173
  • 195