Searching Large Array Performance in Swift - ios

I have a a JSON file of all the airports in the world that I am trying to search in a function but it is very slow and am trying to improve its performance. There are 9500 entries in this JSON file( I would have used an web API but couldnt find a free one so I am using this JSON file). My array is like like:
var data = [Dictionary<String, AnyObject>]
Here is what one of the dictionaries looks like:
[DisplayText: YYZ, airportObject: {
0 = 193;
Altitude = 569;
City = Toronto;
Country = Canada;
DST = A;
IATA = YYZ;
ICAO = CYYZ;
Latitude = "43.677223";
Longitude = "-79.630556";
Name = "Lester B Pearson Intl";
TZ = "America/Toronto";
UTC = "-5";
}, DisplaySubText: Lester B Pearson Intl]
The goal is to have a TextField autocomplete with the name of the airport as the user types in the field. I wrote a function to filter these entries with the user's input. It works, however it is very slow and takes about 1 second per letter typed, and the cpu goes to 50% when typing.
Here is the function
func applyFilterWithSearchQuery(filter : String) -> [Dictionary<String, AnyObject>]
{
//let predicate = NSPredicate(format: "DisplayText BEGINSWITH[cd] \(filter)")
var lower = (filter as NSString).lowercaseString
var filteredData = data.filter({
if let match : AnyObject = $0["DisplayText"]{
//println("LCS = \(filter.lowercaseString)")
return (match as NSString).lowercaseString.hasPrefix((filter as NSString).lowercaseString)
}
else{
return false
}
})
return filteredData
}
How can I improve the performance of this function?

I ended up taking all the comments into account and came up with this.
I changed the logic so it only only runs applyFilterWithSearchQuery after 2 characters are typed instead of 1.
Cached the results after each result so only the sublist was searched
Changed the function to use NSPredicate as per below:
func applyFilterWithSearchQuery(filter : String) -> [Dictionary<String, AnyObject>]
{
let predicate = NSPredicate(format: "DisplayText BEGINSWITH[cd] %#", filter)
let filteredData = (self.data as NSArray).filteredArrayUsingPredicate(predicate!)
return filteredData as [Dictionary<String, AnyObject>]
}
Overall CPU time dropped down to 1% . Thanks to all those that had suggestions

Related

Applying filter and calculating Average on NSDictionary with JSON origin

I read some posts here and google about it, but still couldn't understand how to do a simple filter using the swift filter feature. I am new to Swift and functional programing, so forgive me if that is too basic.
I have the following JSON:
{
"-KjirKH7Bo7c5vq7ZH9N" = {
rank = 2;
placa = "xxx-0003";
uid = yNpL0uzI5LRj6etFGVgoWYEK2E52;
};
"-Kjiyi_i7FLl6dks6xKL" = {
rank = 5;
placa = "xxx-0003";
uid = yNpL0uzI5LRj6etFGVgoWYEK2E52;
};
}
I was able to create an array of the values with:
if let dict = snapshot.value as? NSDictionary{
let myArray = dict.map{$0.value} //array of values
}
Which creates this:
[{
rank = 5;
placa = "xxx-0003";
uid = yNpL0uzI5LRj6etFGVgoWYEK2E52;
}, {
rank = 2;
placa = "xxx-0003";
uid = yNpL0uzI5LRj6etFGVgoWYEK2E52;
}]
My goal now is:
Apply a filter to retrieve only items that the property "rank" is greater than 0.
After that I want to calculate the items rank average (in this example 2+5/2 = 3.5).
I have tried this:
myArray.filter{$0.rank > 0 }
But it fails with "Value of type 'Any' has no member 'rank'"
Any idea how I can filter this array?
I have tried with NSPredicate, but I am wondering if there is some way to take advantage of the native filter.
Looks like you have two issues:
myArray's items are of type Any and in reality they are dictionaries so you have to help compiler to understand that. Here is how to do it:
let myArray: [[String: Any]] = dict.map{ $0.value }
Because myArray contains dictionaries, you have to modify accessing values in filter:
myArray.filter{ (($0["rank"] as? Int) ?? 0) > 0 }
Hope it helps!
myArray.filter{ ((($0 as! NSDictionary)["rank"] as? Int) ?? 0) > 0 }
appending the answer of K.K Cast $0 to NSDictionary. I hope that will work

How to sort NSmutableArray from one of it indexes (lat lng coordinates) in Swift

I'm new in swift and I'd know how to do that in php, but I'm lost with all those dictionaries and I have no idea how to do that in swift 2. I've been googling for a while and didn't found what I need.
I'm parsing a jSon and storing it's values in an NSMutableDictionary in a loop and at the end of the loop I store the NSMutableDictionary in an NSMutableArray, so at the end I have an NSMutableArray with 43 elements, and each element have about 10 keys with their values. I need to sort those 43 elements from their "distance" key and sort them descending. I don't know if that is posible with this current approach. The value of the key "distance" is an int number (meters). I don't know if to use an NSMutableDictionary inside an NSMutable Array is the correct approach to do this but I'm using it because it is possible to have string keys, and not numbers indexes, so for me it's easier to access the key "distance" than the index 8...
First I load the jSon content:
private func parseJson(json : NSMutableArray, tableView : UITableView){
var c : Int = 0
for j in json {
var jsonValues = NSMutableDictionary()
//Create main value
guard let value = j.valueForKey("value")?.valueForKey("value")! else{
continue
}
//Get name
guard let Name : String = (value.valueForKey("Name")?.valueForKey("en") as? String) else {
continue
}
jsonValues["name"] = Name
//more code like this....
TableData.append(Name)
nsDict.insertObject(jsonValues, atIndex: c)
c += 1
}
this is my NSMutableArray content after being loaded:
And this is the code I have this far. Im trying to load the sorted content in a new array, but in this new array some keys are missing.
//Reorder Array by shop distance from user...
var sortDescriptor:NSSortDescriptor = NSSortDescriptor(key: "distance", ascending: true)
var sortedArray : NSArray = nsDict.sortedArrayUsingDescriptors([sortDescriptor])//Crashes
print(sortedArray)
I've managed to sort the array with this technique:
created a new class with for my array
import Foundation
class JsonArrayValues {
init(){
}
var name = String()
var distance = Float()
var lat = Float()
var lng = Float()
var openingTime = String()
var country = String()
var code = String()
var address = String()
var categories = String()
var city = String()
var type = String()
var brands = String()
}
I instantiated one before the loop:
var jsonArrData : [JsonArrayValues] = []
And another one inside the loop, in which I've added the values:
var c : Int = 0
for j in json {
var jsonValues : JsonArrayValues = JsonArrayValues()
//Get name
guard let Name : String = (value.valueForKey("Name")?.valueForKey("en") as? String) else {
continue
}
jsonValues.name = Name
//more code...
jsonArrData.append(jsonValues)
c += 1
}
And finally I've been able to call the function to reorder the array:
//Reorder Array by shop distance from user...
jsonArrData.sortInPlace({$0.distance < $1.distance})
One of your first steps in any non-trivial project really should be to spend some time looking around on github for tools that simplify your problem. In this case, you'd find there are so very many tools to simplify working with JSON in Swift. I'd suggest you look at EVReflection and Gloss particularly, although there are also many people who use SwiftyJSON.
You don't mention how you're accessing the network; you could look at AFNetworking or Alamofire. The latter also has AlamofireJsonToObjects to help.
I also found JSONExport to be incredibly useful.
You'd be spending a lot less time futzing with details as in this unfortunate question and more getting on with your larger goal.

Sort value in dictionary that resides within an array

I have an Array: var messageArray = [AnyObject]() and in that Array there is a single tuple that contains Dictionaries with 10 key/value paires (9 of them not important for the sort): var messageDetailDict = [String: AnyObject]()
Getting and setting those values all work correctly, however now I want to sort the Array by 1 of the values (not keys) of the Dictionary.
Example -> The Array has a tuple containing several Dictionaries:
The key in the Dictionary (which is the first element in the Array) is: 'ReceivedAt' which has a value of 21-03-2015
The key in the Dictionary (which is the second element in the Array) is: 'ReceivedAt' which has a value of 20-03-2015
The key in the Dictionary (which is the third element in the Array) is: 'ReceivedAt' which has a value of 15-03-2015
Now the Array should be sorted so that the values of 'ReceivedAt' will be sorted from earliest date, to the last date.
Hope this makes sense, but it's a bit difficult to explain. Thanks!
EDIT >>>>>
This is the println(messageArray) output:
[(
{
ConversationId = "94cc96b5-d063-41a0-ae03-6d1a868836fb";
Data = "Hello World";
Id = "eeb5ac08-209f-4ef0-894a-72e77f01b80b";
NeedsPush = 0;
ReceivedAt = "/Date(1439920899537)/";
SendAt = "/Date(1436620515000)/";
Status = 0;
},
{
ConversationId = "94cc96b5-d063-41a0-ae03-6d1a868836fb";
Data = "Hello World";
Id = "86b8766d-e4b2-4ef6-9112-ba9193048d9d";
NeedsPush = 0;
ReceivedAt = "/Date(1439921562909)/";
SendAt = "/Date(1436620515000)/";
Status = 0;
}
)]
And the received date is converted to a string with the following method (I do think however this is not important, as it is a time interval, and therefore OK to sort):
func getTimeStampFromAPIValue(dateTimeReceived: String) -> String {
let newStartIndex = advance(dateTimeReceived.startIndex, 6)
let newEndIndex = advance(dateTimeReceived.endIndex, -2)
let substring = dateTimeReceived.substringWithRange(newStartIndex..<newEndIndex) // ell
let receivedAtValueInInteger = (substring as NSString).doubleValue
let receivedAtValueInDate = NSDate(timeIntervalSince1970:receivedAtValueInInteger/1000)
//format date
var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "dd-MM-yy hh:mm"
var dateString = dateFormatter.stringFromDate(receivedAtValueInDate)
return dateString
}
Since the values of ReceivedAt are timestamps as strings you could apply the following algorithm:
var sortedArray = messageArray.sorted { (dict1, dict2) in
// Get the ReceivedAt value as strings
if let date1String = dict1["ReceivedAt"] as? String,
let date2String = dict2["ReceivedAt"] as? String {
// Compare the date strings to find the earlier of the two
return date1String.compare(date2String) == .OrderedAscending
}
// Couldn't parse the date, make an assumption about the order
return true
}
Try this, change OrderedAscending with OrderedDescending if need in inverse order
messageArray.sortInPlace {
($0["ReceivedAt"] as! NSDate).compare($1["ReceivedAt"] as! NSDate) == .OrderedAscending
}

Convert String Array to JSON or 2D Array SWIFT

I currently have a NSMutableArray "localArray" and I am trying to create that into a JSON Array or a 2D Array. I get this data my creating a database and running a query using a for loop on the database.
{
Food,
Burger,
3.99,
1.25,
POP,
Crush,
1.99,
.89,
and more.
}
The reason why I am looking for a JSON or 2d Array is I want to hold the data in the localArray in such a way that I can identify by type and then do something like .valueForKey("Name") or .valurForKey("Price") and add that to my tableview's cell text label or labels.
{
{
Type Food,
Name Burger,
Price 3.99,
Cost 1.25,
},
{
Type POP,
Name Crush,
Price 1.99,
Cost .89,
},
and more
}
I have already tried JSONSerialization, but that failed and also tried 2d Array but no luck.
Any help will be highly appreciated.
This is how I Query and add the data to localArray
let queryType = data.select(ada, code, name, proof, size, case_size, price)
.filter(bevType == type)
let rows = Array(queryType)
for row in rows{
let name = row[self.name]
let type = row[self.type]
let cost = row[self.cost]
let price = row[self.price]
localArray.addObject(name)
localArray.addObject(type)
localArray.addObject(cost)
localArray.addObject(price)
}
I solved it myself by creating a dictionary.
for row in rows{
var rDict: Dictionary = [String: String]()
rDict["Name"] = row[self.name]
rDict["Type"] = row[self.type]
rDict["Cost"] = row[self.cost]
rDict["Price"] = row[self.price]
localArray.addObject(rDict)
}
If fields are always repeating in count of 4, you can try doing this:
var array = [[String: AnyObject]]()
for var i = 0 ; i < array.count ; i += 4 {
var k = 0
var dict = [String: AnyObject]
dict["Type"] = array[i + k++]
dict["Name"] = array[i + k++]
dict["Price"] = array[i + k++]
dict["Cost"] = array[i + k]
array.append(dict)
}
Then extract dictionary from this swift array and use same keys to extract data from dictionary to be used in your cell like
let dict = array[indexPath.row]
cell.title = dict["Name"]

(Swift) How to do a fuzzy search of Core Data?

I'm trying to implement a search by Core Data key, in Swift. I want to get back relevant results, both exact and proximate, sorted in order of distance (from the closest match to the most distant).
For example, if someone searches for, say, "friend", I want the search results to look something like the following: "friend", "friends", "friendly", and "friand".
I've tried my best to implement such a search, but I'm fairly new to this sort of code, and my attempted implementation is not quite working for me at the moment. This is my code:
var request = NSFetchRequest(entityName: "db")
request.returnsObjectsAsFaults = false
request.predicate = NSPredicate(format: "dbKey MATCHES %#", ".*" + searchString + ".*")
var results: Array = context.executeFetchRequest(request, error: nil)!
if (results.count > 100) {
results.removeRange(Range(start: 100, end: results.count-1))
}
var sortedResults: Array = [results[0]]
for (var i = 1; i < results.count; ++i) {
if (countElements(results[i].valueForKey("dbKey") as String) < countElements(sortedResults[i-1].valueForKey("dbKey") as String)) {
var j = i-1
while (countElements(results[i].valueForKey("dbKey") as String) < countElements(sortedResults[j].valueForKey("dbKey") as String)) {
if (j != 0) {
--j
} else {
break
}
}
sortedResults.insert(results[i], atIndex: j)
} else {
sortedResults.append(results[i])
}
}
results = sortedResults
The problem is twofold:
Firstly, the results come back in no particular order, so if I search for "a", I'm going to get back a huge amount of words containing "a" long before I get the exact match. Since I cut off after the first 100 results, I never get the exact match to show up in my results.
Secondly, the ranking is approximate. This is probably a maths error on my part, but currently, many longer words show up before shorter words, so you don't see exact or near-exact matches at the top of the results list, as you should.
I tried to find a library which would incorporate search functionality of the kind I am looking for (something like Fuse.js), but I've had no luck. Any help would be most appreciated.
You could filter your results using different predicates and decide which one to use;
let result = NSPredicate(format: "dbKey CONTAINS[cd] %#", searchString)
let result = NSPredicate(format: "dbKey startsWith[cd] %#", searchString)
let result = NSPredicate(format: "dbKey LIKE '%#'", searchString)
For particular order, you should use NSSortDescriptor
Not an actual answer to your question, but a solution to what you're trying to do still: have a look at PermissiveResearch.
It would look something like this:
extension UIViewController: PermissiveResearchDelegate {
// Call this once
func rebuildDatabase() {
PermissiveResearchDatabase.sharedDatabase().delegate = self
for db in allDbs { // Assuming allDbs is your array of DB objects
PermissiveResearchDatabase.sharedDatabase().addManagedObject(db, forKey: "dbKey")
}
}
// A basic search method
func search(searchString: String) {
PermissiveResearchDatabase.sharedDatabase().searchString(searchString, withOperation: ScoringOperationTypeExact)
}
// PermissiveResearchDelegate method
func searchCompletedWithResults(results: [AnyObject]!) {
let pResults = results as! [PermissiveCoreDataObject]
for result in pResults {
let db = try! self.managedObjectContext.existingObjectWithID(result.objectID) as! DB
print("\(result.score) - \(db.dbKey)")
}
}
}

Resources