Search code examples
iosswiftsortingnsmanagedobjectpredicate

"[NSManagedObject]" sort based on matching string


I have a [NSManagedObject] that I would like to sort based on a closest matching string.

I have looked into using sortInPlace, but it looks like it will only compare values and sort them.

I have also looked at using a predicate, but I can't seem to use predicates on [NSManagedObject].

I'm trying this:

if let query = searchController.searchBar.text {
       let predicate = NSPredicate(format: "title contains[c] %@", query)
        self.objects.//using predicate here doesn't work


    }

Any ideas what I need to do??


Solution

  • As @Sulthan mentioned, NSManagedObjects are usually sorted using an NSSortDescriptor which provides only alphabetical ordering when used with an NSFetchRequest.

    There are other ways to sort the results by string proximity. Usually this is done using a prebuilt index where each NSManagedObject is a node in a tree or graph. Implementing such an index is not overly complex, but does require some background knowledge.

    If your data set is not too large, you may be able to sort the objects as an array, as you have shown in your code. A key element of sorting here is defining the rules for determining how close a candidate string matches the query.

    One way to do this is uses the Levenshtein distance to calculate matches. Other ways of matching could also work, such as measuring the longest matching substring for example.

    1. Calculate the Levenshtein distance between the query, and each of the candidates. The Levenshtein distance is, roughly speaking, a measurement of how different one string is from another. A distance of 0 means the strings are identical. The greater the distance, the more different the strings are.
    2. Filter out candidates where the Levenshtein distance is the same as the word length (meaning the words share no similarities)
    3. Order the results by the Levenshtein distance.

    Here is an example of how this might work on an array of strings:

    let data = ["shore", "show", "sheep", "shop", "ship", "shape", "cape", "cope", "cap", "apple", "nape"]
    
    let query = "ape"
    
    // Calculate how closely each item matches the query.
    // This creates an array of tuples of the form (word, distance). 
    let proximity = data.map() { 
        return ($0, levenshtein($0, bStr: query)) 
    }
    
    // Remove items which are completely different. 
    let filtered = proximity.filter() { 
        return $0.1 < $0.0.characters.count  
    }
    
    // Order items by proximity to the query.
    // This places close matches at the top.
    let sorted = filtered.sort() { 
        return $0.1 < $1.1 
    }
    
    // Filter tuples to return the data.
    let result = sorted.map() { 
        return $0.0 
    }
    
    print(result)
    

    I tested this example with a Levenshtein implementation on GitHub. It should be possible to easily transfer this to sort an array of NSManagedObjects.