Search code examples
objective-cnsarraynsnumber

Pick closest NSNumber from array


I have an array with a bunch of NSNumbers. From an UISlider I get a certain value when the user stops dragging it. I would like to get the closes number from the array.

So for instance, if the user drags the UISlider to 13, and the NSArray contains the NSNumbers with 10 and 15; I want to get 15 from the array.

Example of array:

NSArray *values = [NSArray arrayWithObjects:[NSNumber numberWithInt:15],
                    [NSNumber numberWithInt:20],
                    [NSNumber numberWithInt:30],
                    [NSNumber numberWithInt:45],
                    [NSNumber numberWithInt:60],
                    [NSNumber numberWithInt:90],
                    [NSNumber numberWithInt:110], nil];

How do I get the correct number from the array?


Solution

  • In your post, the array is sorted. If it's always sorted, you can use binary search. NSArray has a convenient method for that:

    CGFloat targetNumber = mySlider.value;
    NSUInteger index = [values indexOfObject:@(targetNumber)
        inSortedRange:NSMakeRange(0, values.count)
        options:NSBinarySearchingFirstEqual | NSBinarySearchingInsertionIndex
        usingComparator:^(id a, id b) {
            return [a compare:b];
        }];
    

    Now there are four possibilities:

    1. Every element of values is larger than targetNumber: index is zero.
    2. Every element of values is smaller than targetNumber: index is values.count.
    3. values contains targetNumber: index is the index of targetNumber in values.
    4. index is the index of the smallest element of values that is greater than targetNumber.

    I've cleverly listed the cases in the order we'll handle them. Here's case 1:

    if (index == 0) {
        return [values[0] floatValue];
    }
    

    Here's case 2:

    if (index == values.count) {
        return [[values lastObject] floatValue];
    }
    

    We can handle cases 3 and 4 together:

    CGFloat leftDifference = targetNumber - [values[index - 1] floatValue];
    CGFloat rightDifference = [values[index] floatValue] - targetNumber;
    if (leftDifference < rightDifference) {
        --index;
    }
    return [values[index] floatValue];