Customized sort in NSFetchedResultsController
After spending hours, I found that it was not possible to do customized sort in NSFetchedResultsController backed by SQLite from following article.
NSFetchedResultsController custom sort not getting called
However, I couldn't find actual solution for my problem.
Here's what I am trying to do.
Background:
I have a English dictionary database (just a simple list of words - very large) in CoreData. Words are shown in UITableView using NSFetchedResultsController.
UITableView has an associated Search Bar. When an user inputs a string in Search Bar, UITableView shows a list of words filtered.
Why do I need customized sort:
When a user inputs a string, let's say it was bre
, I change it to regular expression, b.*r.*e.*
and use it as a NSPredicate, then, do performFetch. So that all words like 'bare' and 'break' will be shown in the table view.
By default, the words are shown as a alphabetical order. Therefore, bare
will come before break
.
I want break
to come before bare
on the searched list because the first three character of break
exactly matches to what the user input.
Possible ideas:
bre.*
, br.+e.*
, b.+r.+e.*
in order and combine them. Both ideas don't look very neat.
I appreciate if you can suggest any known neat & typical solution for this kind of problem.
Another approach you could consider is using a transient property to decorate the results to indicate the type of match, and then using that transient property as the first sort descriptor.
This requires looping through all the results, comparing the strings again, and setting the attributes. If you expect a long set of results, using an array might be just as easy.
If you need to handle large result sets efficiently, I'd suggest using two sort descriptors, one which returns only exact matches, and another which returns only non-exact matches. Then display the results from the first followed by the results from the second. With a compound predicate that should be possible to accomplish.
Wow, this problem was annoying.
My setup is as follows. I have a search that takes input and looks for users by matching username or full name. The server was already returning the appropriate order, but since i'm using NSFetchedResultsController I need some sort descriptor Here is what I did that seems to be working well. I added a new property to my user entity called matchScore
and during CRUD from the server I get the MIN()
Levenshtein Distance score between the query <-> username and query <-> full name
I now have a sort descriptor that will order by the closest matching results from the server with the user's query. The code is rubymotion, but should still be readable.
sortDescriptors = []
sortDescriptors << NSSortDescriptor.sortDescriptorWithKey("matchScore", ascending:true)
With the new sort descriptor I can now fetch "less than ideal" results and still keep closest matches first. I can now avoid some of @Jaemin's potential solutions which involved complicated result aggregation to get around custom sorts not working.
request.predicate = NSPredicate.predicateWithFormat("(username MATCHES[cd] %@) OR (username BEGINSWITH[cd] %@) OR (name CONTAINS[cd] %@)", argumentArray:[searchString, searchString, searchString])
The match score is now generated on CRUD from the server.
usersContext.performBlock(lambda{
restUsers.each do |restUser|
user = User.entityWithRestModel(restUser, usersContext)
user.matchScore = [query.compareWithWord(user.username, matchGain:10, missingCost:1), query.compareWithWord(user.name, matchGain:10, missingCost:1].min
puts "u:#{user.username} <-> q:#{query} score:#{user.matchScore}"
end
})
Here is the NSString
category that I am using to get the Levenshtein distance. https://gist.github.com/iloveitaly/1515464