Shayno Shayno - 1 year ago 223
iOS Question

Using NSPredicate to filter NSArray and find similar strings

I've been trying to work out how to use NSPredicate but I am struggling to work out how to use "like".

For example, lets say I have an NSArray:

NSArray *array = [NSArray arrayWithObjects:@"Nick", @"Ben", @"Adam", @"Melissa", nil];

and I accidentally search for the word "Nink" instead of "Nick".

Can I use NSPredicate to return an array with the object "Nick"?

This is what I have tried so far:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF like[cd] %@", @"nink"];

[array filterUsingPredicate:bPredicate];

An empty array is returned.

Is this something that NSPredicate is capable of or am I wasting my time here?


What you are looking for is a custom predicate which uses a bounded Levenshtein Distance to filter out words that are sufficiently different from a target word.

Assuming you use an implementation of Levenshtein Distance as found in this gist, your code will look approximately like this:

NSPredicate *distancePredicate = [NSPredicate predicateWithBlock:^(NSString *name, NSDictionary<NSString *, id> *bindings) {
    // key is the string you're looking for (e.g. 'nink')
    NSString *key = bindings[@"key"];

    // Calculate the Levenshtein Distance. This may be different depending
    // on how you implement it. You may want to weight matchGain and
    // missingCost differently.
    NSInteger score = [key compareWithWord:name matchGain:0 missingCost:1];

    // Only include words that are "close enough", i.e. within two a letter
    // difference.
    return score < 2;

This predicate defines a general predicate "template", which you can then use to filter the array with the actual string you're looking for:

    NSDictionary<NSString *, id> *bindings = @{@"key": @"Nink"};
    NSMutableArray *array = [NSMutableArray arrayWithObjects:@"Nick", @"Ben", @"Adam", @"Melissa", nil];
    NSIndexSet *indices = [array indexesOfObjectsPassingTest:^(id object, NSUInteger index, BOOL *stop) {
        return [distancePredicate evaluateWithObject:object substitutionVariables:bindings];

    NSArray *results = [array objectsAtIndexes:indices];

BTW, there is nothing special about the word @"key"; you can change that to be any string identifying the substitution (e.g. @"name", @"term", etc., are all valid). The key you provide in the substitution variables is the key you should use to retrieve the value.